package gitengine import ( "errors" "reflect" "testing" ) type fakeRunner struct { outputs []string errs []error calls []fakeCall } type fakeCall struct { workdir string args []string } func (f *fakeRunner) Run(workdir string, args ...string) (string, error) { f.calls = append(f.calls, fakeCall{ workdir: workdir, args: append([]string(nil), args...), }) callIndex := len(f.calls) - 1 var output string if callIndex < len(f.outputs) { output = f.outputs[callIndex] } var err error if callIndex < len(f.errs) { err = f.errs[callIndex] } return output, err } func TestStatusClean(t *testing.T) { runner := &fakeRunner{outputs: []string{"\n"}} result, err := Status(runner, "/repo") if err != nil { t.Fatalf("Status: %v", err) } if !result.Clean { t.Fatal("expected clean status") } if !reflect.DeepEqual(runner.calls[0].args, []string{"status", "--porcelain"}) { t.Fatalf("args: %#v", runner.calls[0].args) } } func TestCloneFetchCheckout(t *testing.T) { runner := &fakeRunner{} if err := Clone(runner, CloneOptions{ RemoteURL: "ssh://git.example/nomadcode.git", WorktreePath: "/workspace/nomadcode/000", Branch: "develop", }); err != nil { t.Fatalf("Clone: %v", err) } if err := Fetch(runner, "/workspace/nomadcode/000", ""); err != nil { t.Fatalf("Fetch: %v", err) } if err := Checkout(runner, "/workspace/nomadcode/000", "feature/a"); err != nil { t.Fatalf("Checkout: %v", err) } want := []fakeCall{ {args: []string{"clone", "--branch", "develop", "ssh://git.example/nomadcode.git", "/workspace/nomadcode/000"}}, {workdir: "/workspace/nomadcode/000", args: []string{"fetch", "--prune", "origin"}}, {workdir: "/workspace/nomadcode/000", args: []string{"checkout", "feature/a"}}, } if !reflect.DeepEqual(runner.calls, want) { t.Fatalf("calls:\n got %#v\nwant %#v", runner.calls, want) } } func TestCommitAndPush(t *testing.T) { runner := &fakeRunner{} if err := Commit(runner, "/repo", CommitOptions{Message: "sync roadmap", All: true}); err != nil { t.Fatalf("Commit: %v", err) } if err := Push(runner, "/repo", "", "develop"); err != nil { t.Fatalf("Push: %v", err) } want := []fakeCall{ {workdir: "/repo", args: []string{"add", "-A"}}, {workdir: "/repo", args: []string{"commit", "-m", "sync roadmap"}}, {workdir: "/repo", args: []string{"push", "origin", "develop"}}, } if !reflect.DeepEqual(runner.calls, want) { t.Fatalf("calls:\n got %#v\nwant %#v", runner.calls, want) } } func TestHeadRevision(t *testing.T) { runner := &fakeRunner{outputs: []string{"abc123\n"}} got, err := HeadRevision(runner, "/repo") if err != nil { t.Fatalf("HeadRevision: %v", err) } if got != "abc123" { t.Fatalf("revision: got %q", got) } } func TestChangedFiles(t *testing.T) { runner := &fakeRunner{outputs: []string{"README.md\nservices/core/main.go\n\n"}} files, err := ChangedFiles(runner, "/repo", "abc", "def") if err != nil { t.Fatalf("ChangedFiles: %v", err) } want := []string{"README.md", "services/core/main.go"} if !reflect.DeepEqual(files, want) { t.Fatalf("files: got %#v want %#v", files, want) } } func TestChangedFilesRequiresRevisions(t *testing.T) { _, err := ChangedFiles(&fakeRunner{}, "/repo", "", "def") if !errors.Is(err, ErrInvalidGitInput) { t.Fatalf("expected ErrInvalidGitInput, got %v", err) } } func TestChangedFilesWithStatus(t *testing.T) { runner := &fakeRunner{outputs: []string{"A\tREADME.md\nM\tservices/core/main.go\nD\told.md\nR100\tfrom.md\tto.md\n"}} files, err := ChangedFilesWithStatus(runner, "/repo", "abc", "def") if err != nil { t.Fatalf("ChangedFilesWithStatus: %v", err) } want := []ChangedFile{ {Path: "README.md", ChangeType: "added"}, {Path: "services/core/main.go", ChangeType: "modified"}, {Path: "old.md", ChangeType: "deleted"}, {Path: "to.md", ChangeType: "renamed"}, } if !reflect.DeepEqual(files, want) { t.Fatalf("files:\n got %#v\nwant %#v", files, want) } }