package sshpush import ( "context" "crypto/sha256" "encoding/hex" "errors" "fmt" "os" "strings" "testing" "golang.org/x/crypto/ssh" ) func TestSha256Hex(t *testing.T) { got := sha256Hex([]byte("hello")) want := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" if got != want { t.Errorf("sha256Hex = %q, want %q", got, want) } } func TestSplitDir(t *testing.T) { dir, base := splitDir("/etc/orca/foo.conf") if dir != "/etc/orca" || base != "/etc/orca" { t.Errorf("splitDir(/etc/orca/foo.conf) = (%q,%q), want (/etc/orca,/etc/orca)", dir, base) } dir, base = splitDir("foo.conf") if dir != "" || base != "." { t.Errorf("splitDir(foo.conf) = (%q,%q), want (\"\",.)", dir, base) } } func TestRandomToken(t *testing.T) { a := randomToken(8) b := randomToken(8) if a == b { t.Error("randomToken returned same value twice") } if len(a) != 16 { // 8 bytes hex = 16 chars t.Errorf("randomToken(8) len = %d, want 16", len(a)) } } // --- WriteFileIdempotent tests via the fake SSH server --- func TestWriteFileIdempotent_WritesWhenFileMissing(t *testing.T) { srv := newFakeSSHServer(t) defer srv.close() tr := realTransport(t, srv) defer tr.Close() content := []byte("first content\n") written, err := tr.WriteFileIdempotent(context.Background(), srv.addr(), "/etc/orca/a.conf", content, 0o644) if err != nil { t.Fatalf("WriteFileIdempotent: %v", err) } if !written { t.Error("written=false, want true (file was missing)") } srv.mu.Lock() got := srv.files["/etc/orca/a.conf"] srv.mu.Unlock() if got != string(content) { t.Errorf("remote file = %q, want %q", got, string(content)) } } func TestWriteFileIdempotent_SkipsWhenContentMatches(t *testing.T) { srv := newFakeSSHServer(t) defer srv.close() content := []byte("same content\n") srv.mu.Lock() srv.files["/etc/orca/b.conf"] = string(content) srv.mu.Unlock() tr := realTransport(t, srv) defer tr.Close() written, err := tr.WriteFileIdempotent(context.Background(), srv.addr(), "/etc/orca/b.conf", content, 0o644) if err != nil { t.Fatalf("WriteFileIdempotent: %v", err) } if written { t.Error("written=true, want false (content matched)") } } func TestWriteFileIdempotent_WritesWhenContentDiffers(t *testing.T) { srv := newFakeSSHServer(t) defer srv.close() srv.mu.Lock() srv.files["/etc/orca/c.conf"] = "old content\n" srv.mu.Unlock() tr := realTransport(t, srv) defer tr.Close() newContent := []byte("new content\n") written, err := tr.WriteFileIdempotent(context.Background(), srv.addr(), "/etc/orca/c.conf", newContent, 0o644) if err != nil { t.Fatalf("WriteFileIdempotent: %v", err) } if !written { t.Error("written=false, want true (content differed)") } srv.mu.Lock() got := srv.files["/etc/orca/c.conf"] srv.mu.Unlock() if got != string(newContent) { t.Errorf("remote file = %q, want %q", got, string(newContent)) } } func TestWriteFile_DelegatesToIdempotent(t *testing.T) { srv := newFakeSSHServer(t) defer srv.close() tr := realTransport(t, srv) defer tr.Close() content := []byte("delegated\n") if err := tr.WriteFile(context.Background(), srv.addr(), "/etc/orca/d.conf", content, 0o600); err != nil { t.Fatalf("WriteFile: %v", err) } srv.mu.Lock() got := srv.files["/etc/orca/d.conf"] srv.mu.Unlock() if got != string(content) { t.Errorf("remote file = %q, want %q", got, string(content)) } } // --- Pure-logic idempotency tests via mock session (no SSH server) --- // mockHashSession returns the hash of the file matching the sha256sum // command's path argument; for write commands (mkdir + cat >), it // records the write. This lets us test the idempotency decision logic // without a real SSH server. type mockHashSession struct { files map[string]string out []byte err error cmd string writeHook func(cmd string) } func (m *mockHashSession) CombinedOutput(cmd string) ([]byte, error) { m.cmd = cmd c := strings.TrimSpace(cmd) if strings.HasPrefix(c, "sha256sum ") { rest := strings.TrimSpace(strings.TrimPrefix(c, "sha256sum ")) rest = strings.TrimSuffix(rest, " 2>/dev/null") rest = strings.TrimSpace(rest) path := unquote(rest) content, ok := m.files[path] if !ok { return []byte(""), nil } sum := sha256.Sum256([]byte(content)) return []byte(hex.EncodeToString(sum[:]) + " " + path + "\n"), nil } if strings.HasPrefix(c, "mkdir -p ") && strings.Contains(c, "cat >") { if m.writeHook != nil { m.writeHook(c) } return nil, nil } return m.out, m.err } func (m *mockHashSession) Close() error { return nil } func TestWriteFileIdempotent_MockSkip(t *testing.T) { srv := newFakeSSHServer(t) defer srv.close() tr := realTransport(t, srv) defer tr.Close() files := map[string]string{"/x/f": "match"} var writes int tr.SetSessionFactory(func(c *ssh.Client) (sshSession, error) { return &mockHashSession{ files: files, writeHook: func(string) { writes++ }, }, nil }) client, err := tr.dial(srv.addr()) if err != nil { t.Fatalf("dial: %v", err) } tr.pool.Store(srv.addr(), client) written, err := tr.WriteFileIdempotent(context.Background(), srv.addr(), "/x/f", []byte("match"), 0o644) if err != nil { t.Fatalf("WriteFileIdempotent: %v", err) } if written { t.Error("written=true, want false (hash matched)") } if writes != 0 { t.Errorf("writes = %d, want 0 (no write on hash match)", writes) } } func TestWriteFileIdempotent_MockWrite(t *testing.T) { srv := newFakeSSHServer(t) defer srv.close() tr := realTransport(t, srv) defer tr.Close() files := map[string]string{"/x/f": "old"} var writes int tr.SetSessionFactory(func(c *ssh.Client) (sshSession, error) { return &mockHashSession{ files: files, writeHook: func(string) { writes++ }, }, nil }) client, err := tr.dial(srv.addr()) if err != nil { t.Fatalf("dial: %v", err) } tr.pool.Store(srv.addr(), client) written, err := tr.WriteFileIdempotent(context.Background(), srv.addr(), "/x/f", []byte("new"), 0o644) if err != nil { t.Fatalf("WriteFileIdempotent: %v", err) } if !written { t.Error("written=false, want true (hash differed)") } if writes != 1 { t.Errorf("writes = %d, want 1", writes) } } func TestWriteFileIdempotent_MockMissingFile(t *testing.T) { srv := newFakeSSHServer(t) defer srv.close() tr := realTransport(t, srv) defer tr.Close() files := map[string]string{} var writes int tr.SetSessionFactory(func(c *ssh.Client) (sshSession, error) { return &mockHashSession{ files: files, writeHook: func(string) { writes++ }, }, nil }) client, err := tr.dial(srv.addr()) if err != nil { t.Fatalf("dial: %v", err) } tr.pool.Store(srv.addr(), client) written, err := tr.WriteFileIdempotent(context.Background(), srv.addr(), "/x/new", []byte("fresh"), 0o644) if err != nil { t.Fatalf("WriteFileIdempotent: %v", err) } if !written { t.Error("written=false, want true (file missing)") } if writes != 1 { t.Errorf("writes = %d, want 1", writes) } } func TestRemoteSHA256_ParsesDigest(t *testing.T) { srv := newFakeSSHServer(t) defer srv.close() srv.mu.Lock() srv.files["/x/h"] = "abc" srv.mu.Unlock() tr := realTransport(t, srv) defer tr.Close() got, err := tr.remoteSHA256(context.Background(), srv.addr(), "/x/h") if err != nil { t.Fatalf("remoteSHA256: %v", err) } want := fmt.Sprintf("%x", sha256.Sum256([]byte("abc"))) if got != want { t.Errorf("remoteSHA256 = %q, want %q", got, want) } } func TestRemoteSHA256_MissingFile(t *testing.T) { srv := newFakeSSHServer(t) defer srv.close() tr := realTransport(t, srv) defer tr.Close() got, err := t_remoteSHA256_noErr(t, tr, srv.addr(), "/missing") if err != nil { t.Fatalf("remoteSHA256: %v", err) } if got != "" { t.Errorf("remoteSHA256 = %q, want empty for missing file", got) } } func t_remoteSHA256_noErr(t *testing.T, tr *Transport, peer, path string) (string, error) { t.Helper() return tr.remoteSHA256(context.Background(), peer, path) } func TestWriteFile_ModeApplied(t *testing.T) { srv := newFakeSSHServer(t) defer srv.close() tr := realTransport(t, srv) defer tr.Close() if err := tr.WriteFile(context.Background(), srv.addr(), "/m/f", []byte("mode"), 0o755); err != nil { t.Fatalf("WriteFile: %v", err) } srv.mu.Lock() got := srv.files["/m/f"] srv.mu.Unlock() if got != "mode" { t.Errorf("content = %q, want mode", got) } } func TestWriteFileIdempotent_ExecError(t *testing.T) { srv := newFakeSSHServer(t) defer srv.close() tr := realTransport(t, srv) defer tr.Close() tr.SetSessionFactory(func(c *ssh.Client) (sshSession, error) { return &mockSession{err: fmt.Errorf("%w: boom", ErrPermanent)}, nil }) client, err := tr.dial(srv.addr()) if err != nil { t.Fatalf("dial: %v", err) } tr.pool.Store(srv.addr(), client) _, err = tr.WriteFileIdempotent(context.Background(), srv.addr(), "/x/f", []byte("z"), 0o644) if err == nil { t.Fatal("expected error, got nil") } } func TestParseMode(t *testing.T) { for _, tc := range []struct { in string want os.FileMode }{ {"0644", 0o644}, {"0755", 0o755}, {"0600", 0o600}, {"bad", 0o644}, {"", 0o644}, {"0", 0o644}, } { got := parseMode(tc.in) if got != tc.want { t.Errorf("parseMode(%q) = %o, want %o", tc.in, got, tc.want) } } } var _ = errors.New