From aaa57d093bb2ba8fdb32dd35fff9b1e7efa2d383 Mon Sep 17 00:00:00 2001 From: Gary Holtz Date: Thu, 29 May 2025 21:51:17 -0500 Subject: [PATCH 1/3] chore: Adding more mocks --- pkg/git_mock/git_interface.go | 199 ++++++- pkg/git_mock/git_interface_mock_for_test.go | 328 +++++++++++- pkg/git_mock/git_interface_test.go | 546 +++++++++++++++++++- 3 files changed, 1029 insertions(+), 44 deletions(-) diff --git a/pkg/git_mock/git_interface.go b/pkg/git_mock/git_interface.go index 829f4ac4a..4fe1d2f68 100644 --- a/pkg/git_mock/git_interface.go +++ b/pkg/git_mock/git_interface.go @@ -15,18 +15,41 @@ import ( //go:generate go run go.uber.org/mock/mockgen@v0.4.0 -typed -destination=./git_interface_mock_for_test.go -package=git_mock gitlab.com/gitlab-org/cli/pkg/git_mock GitInterface -const DefaultRemote = "origin" +const ( + DefaultRemote = "origin" + DefaultBranch = "master" +) // ErrNotOnAnyBranch indicates that the user is in detached HEAD state var ErrNotOnAnyBranch = errors.New("you're not on any Git branch (a 'detached HEAD' state).") +// Ref represents a git commit reference +type Ref struct { + Hash string + Name string +} + +// Commit represents a git commit +type Commit struct { + Sha string + Title string +} + type ( GitInterface interface { CheckoutBranch(branch string) error + CheckoutNewBranch(branch string) error CurrentBranch() (string, error) DeleteLocalBranch(branch string) error GetDefaultBranch(remote string) (string, error) - RemoteBranchExists(branch string) (bool, error) + RemoteBranchExists(remote string, branch string) (bool, error) + UncommittedChangeCount() (int, error) + GitUserName() (string, error) + LatestCommit(ref string) (*Commit, error) + Commits(baseRef, headRef string) ([]*Commit, error) + CommitBody(sha string) (string, error) + Push(remote string, ref string) error + HasLocalBranch(branch string) (bool, error) } StandardGitRunner struct { @@ -58,42 +81,152 @@ func (g *StandardGitRunner) CurrentBranch() (string, error) { return "", fmt.Errorf("unknown error getting current branch: %v - %s", err, stderr) } -// GetDefaultBranch finds and returns the remote's default branch +// GetDefaultBranch gets the default branch for a remote func (g *StandardGitRunner) GetDefaultBranch(remote string) (string, error) { stdout, stderr, err := g.runGitCommand("remote", "show", remote) if err != nil { - return "", fmt.Errorf("could not find default branch: %v - %s", err, stderr) + return DefaultBranch, fmt.Errorf("could not get default branch for remote %s: %v - %s", remote, err, stderr) } - return ParseDefaultBranch(stdout) -} -// RemoteBranchExists returns a boolean if the remote branch exists -func (g *StandardGitRunner) RemoteBranchExists(branch string) (bool, error) { - _, stderr, err := g.runGitCommand("ls-remote", "--exit-code", "--heads", DefaultRemote, branch) + defaultBranch, err := parseDefaultBranch(stdout) if err != nil { - return false, fmt.Errorf("could not find remote branch: %v - %s", err, stderr) + return DefaultBranch, fmt.Errorf("could not parse default branch for remote %s: %v", remote, err) } - return true, nil + + return defaultBranch, nil } // DeleteLocalBranch deletes a local git branch func (g *StandardGitRunner) DeleteLocalBranch(branch string) error { _, stderr, err := g.runGitCommand("branch", "-D", branch) if err != nil { - return fmt.Errorf("could not delete local branch: %v - %s", err, stderr) + return fmt.Errorf("could not delete local branch %s: %v - %s", branch, err, stderr) } return nil } -// CheckoutBranch checks out a branch in the current git repository +// CheckoutBranch switches to an existing branch func (g *StandardGitRunner) CheckoutBranch(branch string) error { _, stderr, err := g.runGitCommand("checkout", branch) if err != nil { - return fmt.Errorf("could not checkout branch: %v - %s", err, stderr) + return fmt.Errorf("could not checkout branch %s: %v - %s", branch, err, stderr) } return nil } +// RemoteBranchExists checks if a remote branch exists +func (g *StandardGitRunner) RemoteBranchExists(remote string, branch string) (bool, error) { + _, _, err := g.runGitCommand("ls-remote", "--exit-code", "--heads", remote, branch) + return err == nil, nil // this is either true or false, really +} + +// CheckoutNewBranch creates and checks out a new branch +func (g *StandardGitRunner) CheckoutNewBranch(branch string) error { + _, stderr, err := g.runGitCommand("checkout", "-b", branch) + if err != nil { + return fmt.Errorf("could not create new branch: %v - %s", err, stderr) + } + return nil +} + +// UncommittedChangeCount returns the number of uncommitted changes +func (g *StandardGitRunner) UncommittedChangeCount() (int, error) { + stdout, stderr, err := g.runGitCommand("status", "--porcelain") + if err != nil { + return 0, fmt.Errorf("could not get status: %v - %s", err, stderr) + } + + lines := strings.Split(string(stdout), "\n") + count := 0 + for _, l := range lines { + if l != "" { + count++ + } + } + return count, nil +} + +// GitUserName gets the git user name +func (g *StandardGitRunner) GitUserName() (string, error) { + stdout, stderr, err := g.runGitCommand("config", "user.name") + if err != nil { + return "", fmt.Errorf("could not get user name: %v - %s", err, stderr) + } + return string(stdout), nil +} + +// LatestCommit gets the latest commit for a ref +func (g *StandardGitRunner) LatestCommit(ref string) (*Commit, error) { + stdout, stderr, err := g.runGitCommand("show", "-s", "--format=%h %s", ref) + if err != nil { + return &Commit{}, fmt.Errorf("could not get latest commit: %v - %s", err, stderr) + } + + split := strings.Fields(string(stdout)) + + if len(split) != 2 { + return &Commit{}, fmt.Errorf("could not parse commit for %s: unexpected format '%s'", ref, string(stdout)) + } + + return &Commit{ + Sha: split[0], + Title: split[1], + }, nil +} + +// Commits gets commits between two refs +func (g *StandardGitRunner) Commits(baseRef, headRef string) ([]*Commit, error) { + stdout, stderr, err := g.runGitCommand( + "-c", "log.ShowSignature=false", + "log", "--pretty=format:%H,%s", + "--cherry", fmt.Sprintf("%s...%s", baseRef, headRef)) + if err != nil { + return []*Commit{}, fmt.Errorf("could not get commits: %v - %s", err, stderr) + } + + var commits []*Commit + for _, line := range outputLines(stdout) { + split := strings.SplitN(line, ",", 2) + if len(split) != 2 { + continue + } + commits = append(commits, &Commit{ + Sha: split[0], + Title: split[1], + }) + } + + if len(commits) == 0 { + return commits, fmt.Errorf("could not find any commits between %s and %s.", baseRef, headRef) + } + + return commits, nil +} + +// CommitBody gets the body of a commit +func (g *StandardGitRunner) CommitBody(sha string) (string, error) { + stdout, stderr, err := g.runGitCommand("-c", "log.ShowSignature=false", "show", "-s", "--pretty=format:%b", sha) + if err != nil { + return "", fmt.Errorf("could not get commit body: %v - %s", err, stderr) + } + return string(stdout), nil +} + +// Push publishes a git ref to a remote +func (g *StandardGitRunner) Push(remote string, ref string) error { + _, stderr, err := g.runGitCommand("push", remote, ref) + if err != nil { + return fmt.Errorf("could not push: %v - %s", err, stderr) + } + return nil +} + +// HasLocalBranch checks if a local branch exists +func (g *StandardGitRunner) HasLocalBranch(branch string) (bool, error) { + _, _, err := g.runGitCommand("rev-parse", "--verify", "refs/heads/"+branch) + return err == nil, nil +} + // runGitCommand executes a git command with proper environment setup and returns stdout/stderr func (g *StandardGitRunner) runGitCommand(args ...string) ([]byte, string, error) { cmd := exec.Command(g.gitBinary, args...) @@ -110,19 +243,35 @@ func (g *StandardGitRunner) runGitCommand(args ...string) ([]byte, string, error return stdout.Bytes(), stderr.String(), err } -func ParseDefaultBranch(output []byte) (string, error) { - var headBranch string +// parseDefaultBranch parses the default branch from git remote output +func parseDefaultBranch(output []byte) (string, error) { + lines := strings.Split(string(output), "\n") - for _, o := range strings.Split(string(output), "\n") { - o = strings.TrimSpace(o) - r, err := regexp.Compile(`(HEAD branch:)\s+`) - if err != nil { - return "master", err + // try to find "HEAD branch:" line + headBranchRegex := regexp.MustCompile(`HEAD branch:\s+(.+)`) + for _, line := range lines { + if matches := headBranchRegex.FindStringSubmatch(line); len(matches) > 1 { + return strings.TrimSpace(matches[1]), nil } - if r.MatchString(o) { - headBranch = strings.TrimPrefix(o, "HEAD branch: ") - break + } + + // otherwise, look for branch marked with (HEAD) + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.Contains(line, "(HEAD)") { + parts := strings.Fields(line) + if len(parts) > 0 { + return parts[0], nil + } } } - return headBranch, nil + + // couldn't find HEAD branch(?) + return "", errors.New("could not determine default branch from remote output") +} + +// outputLines splits output into lines +func outputLines(output []byte) []string { + lines := strings.TrimSuffix(string(output), "\n") + return strings.Split(lines, "\n") } diff --git a/pkg/git_mock/git_interface_mock_for_test.go b/pkg/git_mock/git_interface_mock_for_test.go index 1832064e8..a1acb9fe0 100644 --- a/pkg/git_mock/git_interface_mock_for_test.go +++ b/pkg/git_mock/git_interface_mock_for_test.go @@ -1,12 +1,12 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: gitlab.com/gitlab-org/cli/pkg/git (interfaces: GitInterface) +// Source: gitlab.com/gitlab-org/cli/pkg/git_mock (interfaces: GitInterface) // // Generated by this command: // -// mockgen -typed -destination=./git_interface_mock_for_test.go -package=git gitlab.com/gitlab-org/cli/pkg/git GitInterface +// mockgen -typed -destination=./git_interface_mock_for_test.go -package=git_mock gitlab.com/gitlab-org/cli/pkg/git_mock GitInterface // -// Package git is a generated GoMock package. +// Package git_mock is a generated GoMock package. package git_mock import ( @@ -76,6 +76,122 @@ func (c *MockGitInterfaceCheckoutBranchCall) DoAndReturn(f func(string) error) * return c } +// CheckoutNewBranch mocks base method. +func (m *MockGitInterface) CheckoutNewBranch(arg0 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckoutNewBranch", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// CheckoutNewBranch indicates an expected call of CheckoutNewBranch. +func (mr *MockGitInterfaceMockRecorder) CheckoutNewBranch(arg0 any) *MockGitInterfaceCheckoutNewBranchCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckoutNewBranch", reflect.TypeOf((*MockGitInterface)(nil).CheckoutNewBranch), arg0) + return &MockGitInterfaceCheckoutNewBranchCall{Call: call} +} + +// MockGitInterfaceCheckoutNewBranchCall wrap *gomock.Call +type MockGitInterfaceCheckoutNewBranchCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceCheckoutNewBranchCall) Return(arg0 error) *MockGitInterfaceCheckoutNewBranchCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceCheckoutNewBranchCall) Do(f func(string) error) *MockGitInterfaceCheckoutNewBranchCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceCheckoutNewBranchCall) DoAndReturn(f func(string) error) *MockGitInterfaceCheckoutNewBranchCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// CommitBody mocks base method. +func (m *MockGitInterface) CommitBody(arg0 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CommitBody", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CommitBody indicates an expected call of CommitBody. +func (mr *MockGitInterfaceMockRecorder) CommitBody(arg0 any) *MockGitInterfaceCommitBodyCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitBody", reflect.TypeOf((*MockGitInterface)(nil).CommitBody), arg0) + return &MockGitInterfaceCommitBodyCall{Call: call} +} + +// MockGitInterfaceCommitBodyCall wrap *gomock.Call +type MockGitInterfaceCommitBodyCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceCommitBodyCall) Return(arg0 string, arg1 error) *MockGitInterfaceCommitBodyCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceCommitBodyCall) Do(f func(string) (string, error)) *MockGitInterfaceCommitBodyCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceCommitBodyCall) DoAndReturn(f func(string) (string, error)) *MockGitInterfaceCommitBodyCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Commits mocks base method. +func (m *MockGitInterface) Commits(arg0, arg1 string) ([]*Commit, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Commits", arg0, arg1) + ret0, _ := ret[0].([]*Commit) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Commits indicates an expected call of Commits. +func (mr *MockGitInterfaceMockRecorder) Commits(arg0, arg1 any) *MockGitInterfaceCommitsCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Commits", reflect.TypeOf((*MockGitInterface)(nil).Commits), arg0, arg1) + return &MockGitInterfaceCommitsCall{Call: call} +} + +// MockGitInterfaceCommitsCall wrap *gomock.Call +type MockGitInterfaceCommitsCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceCommitsCall) Return(arg0 []*Commit, arg1 error) *MockGitInterfaceCommitsCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceCommitsCall) Do(f func(string, string) ([]*Commit, error)) *MockGitInterfaceCommitsCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceCommitsCall) DoAndReturn(f func(string, string) ([]*Commit, error)) *MockGitInterfaceCommitsCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // CurrentBranch mocks base method. func (m *MockGitInterface) CurrentBranch() (string, error) { m.ctrl.T.Helper() @@ -192,19 +308,174 @@ func (c *MockGitInterfaceGetDefaultBranchCall) DoAndReturn(f func(string) (strin return c } +// GitUserName mocks base method. +func (m *MockGitInterface) GitUserName() (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GitUserName") + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GitUserName indicates an expected call of GitUserName. +func (mr *MockGitInterfaceMockRecorder) GitUserName() *MockGitInterfaceGitUserNameCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitUserName", reflect.TypeOf((*MockGitInterface)(nil).GitUserName)) + return &MockGitInterfaceGitUserNameCall{Call: call} +} + +// MockGitInterfaceGitUserNameCall wrap *gomock.Call +type MockGitInterfaceGitUserNameCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceGitUserNameCall) Return(arg0 string, arg1 error) *MockGitInterfaceGitUserNameCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceGitUserNameCall) Do(f func() (string, error)) *MockGitInterfaceGitUserNameCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceGitUserNameCall) DoAndReturn(f func() (string, error)) *MockGitInterfaceGitUserNameCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// HasLocalBranch mocks base method. +func (m *MockGitInterface) HasLocalBranch(arg0 string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasLocalBranch", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasLocalBranch indicates an expected call of HasLocalBranch. +func (mr *MockGitInterfaceMockRecorder) HasLocalBranch(arg0 any) *MockGitInterfaceHasLocalBranchCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasLocalBranch", reflect.TypeOf((*MockGitInterface)(nil).HasLocalBranch), arg0) + return &MockGitInterfaceHasLocalBranchCall{Call: call} +} + +// MockGitInterfaceHasLocalBranchCall wrap *gomock.Call +type MockGitInterfaceHasLocalBranchCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceHasLocalBranchCall) Return(arg0 bool, arg1 error) *MockGitInterfaceHasLocalBranchCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceHasLocalBranchCall) Do(f func(string) (bool, error)) *MockGitInterfaceHasLocalBranchCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceHasLocalBranchCall) DoAndReturn(f func(string) (bool, error)) *MockGitInterfaceHasLocalBranchCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// LatestCommit mocks base method. +func (m *MockGitInterface) LatestCommit(arg0 string) (*Commit, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LatestCommit", arg0) + ret0, _ := ret[0].(*Commit) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LatestCommit indicates an expected call of LatestCommit. +func (mr *MockGitInterfaceMockRecorder) LatestCommit(arg0 any) *MockGitInterfaceLatestCommitCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LatestCommit", reflect.TypeOf((*MockGitInterface)(nil).LatestCommit), arg0) + return &MockGitInterfaceLatestCommitCall{Call: call} +} + +// MockGitInterfaceLatestCommitCall wrap *gomock.Call +type MockGitInterfaceLatestCommitCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceLatestCommitCall) Return(arg0 *Commit, arg1 error) *MockGitInterfaceLatestCommitCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceLatestCommitCall) Do(f func(string) (*Commit, error)) *MockGitInterfaceLatestCommitCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceLatestCommitCall) DoAndReturn(f func(string) (*Commit, error)) *MockGitInterfaceLatestCommitCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Push mocks base method. +func (m *MockGitInterface) Push(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Push", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Push indicates an expected call of Push. +func (mr *MockGitInterfaceMockRecorder) Push(arg0, arg1 any) *MockGitInterfacePushCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Push", reflect.TypeOf((*MockGitInterface)(nil).Push), arg0, arg1) + return &MockGitInterfacePushCall{Call: call} +} + +// MockGitInterfacePushCall wrap *gomock.Call +type MockGitInterfacePushCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfacePushCall) Return(arg0 error) *MockGitInterfacePushCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfacePushCall) Do(f func(string, string) error) *MockGitInterfacePushCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfacePushCall) DoAndReturn(f func(string, string) error) *MockGitInterfacePushCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // RemoteBranchExists mocks base method. -func (m *MockGitInterface) RemoteBranchExists(arg0 string) (bool, error) { +func (m *MockGitInterface) RemoteBranchExists(arg0, arg1 string) (bool, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RemoteBranchExists", arg0) + ret := m.ctrl.Call(m, "RemoteBranchExists", arg0, arg1) ret0, _ := ret[0].(bool) ret1, _ := ret[1].(error) return ret0, ret1 } // RemoteBranchExists indicates an expected call of RemoteBranchExists. -func (mr *MockGitInterfaceMockRecorder) RemoteBranchExists(arg0 any) *MockGitInterfaceRemoteBranchExistsCall { +func (mr *MockGitInterfaceMockRecorder) RemoteBranchExists(arg0, arg1 any) *MockGitInterfaceRemoteBranchExistsCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoteBranchExists", reflect.TypeOf((*MockGitInterface)(nil).RemoteBranchExists), arg0) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoteBranchExists", reflect.TypeOf((*MockGitInterface)(nil).RemoteBranchExists), arg0, arg1) return &MockGitInterfaceRemoteBranchExistsCall{Call: call} } @@ -220,13 +491,52 @@ func (c *MockGitInterfaceRemoteBranchExistsCall) Return(arg0 bool, arg1 error) * } // Do rewrite *gomock.Call.Do -func (c *MockGitInterfaceRemoteBranchExistsCall) Do(f func(string) (bool, error)) *MockGitInterfaceRemoteBranchExistsCall { +func (c *MockGitInterfaceRemoteBranchExistsCall) Do(f func(string, string) (bool, error)) *MockGitInterfaceRemoteBranchExistsCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceRemoteBranchExistsCall) DoAndReturn(f func(string, string) (bool, error)) *MockGitInterfaceRemoteBranchExistsCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// UncommittedChangeCount mocks base method. +func (m *MockGitInterface) UncommittedChangeCount() (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UncommittedChangeCount") + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UncommittedChangeCount indicates an expected call of UncommittedChangeCount. +func (mr *MockGitInterfaceMockRecorder) UncommittedChangeCount() *MockGitInterfaceUncommittedChangeCountCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UncommittedChangeCount", reflect.TypeOf((*MockGitInterface)(nil).UncommittedChangeCount)) + return &MockGitInterfaceUncommittedChangeCountCall{Call: call} +} + +// MockGitInterfaceUncommittedChangeCountCall wrap *gomock.Call +type MockGitInterfaceUncommittedChangeCountCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceUncommittedChangeCountCall) Return(arg0 int, arg1 error) *MockGitInterfaceUncommittedChangeCountCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceUncommittedChangeCountCall) Do(f func() (int, error)) *MockGitInterfaceUncommittedChangeCountCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockGitInterfaceRemoteBranchExistsCall) DoAndReturn(f func(string) (bool, error)) *MockGitInterfaceRemoteBranchExistsCall { +func (c *MockGitInterfaceUncommittedChangeCountCall) DoAndReturn(f func() (int, error)) *MockGitInterfaceUncommittedChangeCountCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/pkg/git_mock/git_interface_test.go b/pkg/git_mock/git_interface_test.go index 6f7355871..178cb72df 100644 --- a/pkg/git_mock/git_interface_test.go +++ b/pkg/git_mock/git_interface_test.go @@ -84,9 +84,9 @@ func TestGetDefaultBranch(t *testing.T) { name: "remote show command fails", remote: "origin", setupMock: func(m *MockGitInterface, remote string) { - m.EXPECT().GetDefaultBranch(remote).Return("", errors.New("could not find default branch")) + m.EXPECT().GetDefaultBranch(remote).Return(DefaultBranch, errors.New("could not find default branch")) }, - expectedBranch: "", + expectedBranch: DefaultBranch, expectedError: errors.New("could not find default branch"), }, } @@ -116,16 +116,18 @@ func TestGetDefaultBranch(t *testing.T) { func TestRemoteBranchExists(t *testing.T) { tests := []struct { name string + remote string branch string - setupMock func(*MockGitInterface, string) + setupMock func(*MockGitInterface, string, string) expected bool expectedError error }{ { name: "branch exists", + remote: "origin", branch: "feature", - setupMock: func(m *MockGitInterface, branch string) { - m.EXPECT().RemoteBranchExists(branch).Return(true, nil) + setupMock: func(m *MockGitInterface, remote string, branch string) { + m.EXPECT().RemoteBranchExists(remote, branch).Return(true, nil) }, expected: true, expectedError: nil, @@ -133,8 +135,8 @@ func TestRemoteBranchExists(t *testing.T) { { name: "branch does not exist", branch: "BranchDoesNotExist", - setupMock: func(m *MockGitInterface, branch string) { - m.EXPECT().RemoteBranchExists(branch).Return(false, errors.New("could not find remote branch")) + setupMock: func(m *MockGitInterface, remote string, branch string) { + m.EXPECT().RemoteBranchExists(remote, branch).Return(false, errors.New("could not find remote branch")) }, expected: false, expectedError: errors.New("could not find remote branch"), @@ -148,9 +150,9 @@ func TestRemoteBranchExists(t *testing.T) { mockGit := NewMockGitInterface(ctrl) - tt.setupMock(mockGit, tt.branch) + tt.setupMock(mockGit, tt.remote, tt.branch) - exists, err := mockGit.RemoteBranchExists(tt.branch) + exists, err := mockGit.RemoteBranchExists(tt.remote, tt.branch) if tt.expectedError != nil { require.Error(t, err) @@ -190,7 +192,6 @@ func TestDeleteLocalBranch(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // Setup the mock controller ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -281,3 +282,528 @@ func TestNewStandardGitRunner(t *testing.T) { }) } } + +func TestCheckoutNewBranch(t *testing.T) { + tests := []struct { + name string + branch string + setupMock func(*MockGitInterface, string) + expectedError error + }{ + { + name: "successful new branch creation", + branch: "feature", + setupMock: func(m *MockGitInterface, branch string) { + m.EXPECT().CheckoutNewBranch(branch).Return(nil) + }, + expectedError: nil, + }, + { + name: "branch creation fails", + branch: "feature", + setupMock: func(m *MockGitInterface, branch string) { + m.EXPECT().CheckoutNewBranch(branch).Return(errors.New("could not create new branch")) + }, + expectedError: errors.New("could not create new branch"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit, tt.branch) + + err := mockGit.CheckoutNewBranch(tt.branch) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestUncommittedChangeCount(t *testing.T) { + tests := []struct { + name string + setupMock func(*MockGitInterface) + expectedCount int + expectedError error + }{ + { + name: "no uncommitted changes", + setupMock: func(m *MockGitInterface) { + m.EXPECT().UncommittedChangeCount().Return(0, nil) + }, + expectedCount: 0, + expectedError: nil, + }, + { + name: "three uncommitted changes", + setupMock: func(m *MockGitInterface) { + m.EXPECT().UncommittedChangeCount().Return(3, nil) + }, + expectedCount: 3, + expectedError: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit) + + count, err := mockGit.UncommittedChangeCount() + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.expectedCount, count) + }) + } +} + +func TestLatestCommit(t *testing.T) { + tests := []struct { + name string + ref string + setupMock func(*MockGitInterface, string) + expectedCommit *Commit + expectedError error + }{ + { + name: "successful commit retrieval", + ref: "HEAD", + setupMock: func(m *MockGitInterface, ref string) { + m.EXPECT().LatestCommit(ref).Return(&Commit{ + Sha: "abc123", + Title: "Fix bug in feature", + }, nil) + }, + expectedCommit: &Commit{ + Sha: "abc123", + Title: "Fix bug in feature", + }, + expectedError: nil, + }, + { + name: "commit not found", + ref: "nonexistent", + setupMock: func(m *MockGitInterface, ref string) { + m.EXPECT().LatestCommit(ref).Return(&Commit{}, errors.New("could not get latest commit")) + }, + expectedCommit: &Commit{}, + expectedError: errors.New("could not get latest commit"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit, tt.ref) + + commit, err := mockGit.LatestCommit(tt.ref) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.expectedCommit, commit) + }) + } +} + +func TestPush(t *testing.T) { + tests := []struct { + name string + remote string + ref string + setupMock func(*MockGitInterface, string, string) + expectedError error + }{ + { + name: "successful push", + remote: "origin", + ref: "main", + setupMock: func(m *MockGitInterface, remote, ref string) { + m.EXPECT().Push(remote, ref).Return(nil) + }, + expectedError: nil, + }, + { + name: "push fails", + remote: "origin", + ref: "main", + setupMock: func(m *MockGitInterface, remote, ref string) { + m.EXPECT().Push(remote, ref).Return(errors.New("could not push")) + }, + expectedError: errors.New("could not push"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit, tt.remote, tt.ref) + + err := mockGit.Push(tt.remote, tt.ref) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestGitUserName(t *testing.T) { + tests := []struct { + name string + setupMock func(*MockGitInterface) + expectedName string + expectedError error + }{ + { + name: "successful user name retrieval", + setupMock: func(m *MockGitInterface) { + m.EXPECT().GitUserName().Return("John Doe", nil) + }, + expectedName: "John Doe", + expectedError: nil, + }, + { + name: "user name not configured", + setupMock: func(m *MockGitInterface) { + m.EXPECT().GitUserName().Return("", errors.New("could not get user name")) + }, + expectedName: "", + expectedError: errors.New("could not get user name"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit) + + name, err := mockGit.GitUserName() + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.expectedName, name) + }) + } +} + +func TestCommits(t *testing.T) { + tests := []struct { + name string + baseRef string + headRef string + setupMock func(*MockGitInterface, string, string) + expectedCommits []*Commit + expectedError error + }{ + { + name: "successful commits retrieval", + baseRef: "main", + headRef: "feature", + setupMock: func(m *MockGitInterface, baseRef, headRef string) { + m.EXPECT().Commits(baseRef, headRef).Return([]*Commit{ + {Sha: "abc123", Title: "Add feature"}, + {Sha: "def456", Title: "Fix bug"}, + }, nil) + }, + expectedCommits: []*Commit{ + {Sha: "abc123", Title: "Add feature"}, + {Sha: "def456", Title: "Fix bug"}, + }, + expectedError: nil, + }, + { + name: "no commits between refs", + baseRef: "main", + headRef: "main", + setupMock: func(m *MockGitInterface, baseRef, headRef string) { + m.EXPECT().Commits(baseRef, headRef).Return([]*Commit{}, errors.New("could not find any commits between main and main.")) + }, + expectedCommits: []*Commit{}, + expectedError: errors.New("could not find any commits between main and main."), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit, tt.baseRef, tt.headRef) + + commits, err := mockGit.Commits(tt.baseRef, tt.headRef) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.expectedCommits, commits) + }) + } +} + +func TestCommitBody(t *testing.T) { + tests := []struct { + name string + sha string + setupMock func(*MockGitInterface, string) + expectedBody string + expectedError error + }{ + { + name: "successful commit body retrieval", + sha: "abc123", + setupMock: func(m *MockGitInterface, sha string) { + m.EXPECT().CommitBody(sha).Return("This is the commit body\n\nWith multiple lines", nil) + }, + expectedBody: "This is the commit body\n\nWith multiple lines", + expectedError: nil, + }, + { + name: "empty commit body", + sha: "def456", + setupMock: func(m *MockGitInterface, sha string) { + m.EXPECT().CommitBody(sha).Return("", nil) + }, + expectedBody: "", + expectedError: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit, tt.sha) + + body, err := mockGit.CommitBody(tt.sha) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.expectedBody, body) + }) + } +} + +func TestHasLocalBranch(t *testing.T) { + tests := []struct { + name string + branch string + setupMock func(*MockGitInterface, string) + expected bool + expectedError error + }{ + { + name: "branch exists", + branch: "feature", + setupMock: func(m *MockGitInterface, branch string) { + m.EXPECT().HasLocalBranch(branch).Return(true, nil) + }, + expected: true, + expectedError: nil, + }, + { + name: "branch does not exist", + branch: "nonexistent", + setupMock: func(m *MockGitInterface, branch string) { + m.EXPECT().HasLocalBranch(branch).Return(false, nil) + }, + expected: false, + expectedError: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit, tt.branch) + + exists, err := mockGit.HasLocalBranch(tt.branch) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.expected, exists) + }) + } +} + +func Test_parseDefaultBranch(t *testing.T) { + tests := []struct { + name string + input string + expectedBranch string + expectedError error + }{ + { + name: "parses HEAD branch from standard output", + input: `* remote origin + Fetch URL: git@gitlab.com:gitlab-org/cli.git + Push URL: git@gitlab.com:gitlab-org/cli.git + HEAD branch: main + Remote branches: + main tracked + dev tracked`, + expectedBranch: "main", + expectedError: nil, + }, + { + name: "handles extra spaces around HEAD branch", + input: `* remote origin + Fetch URL: git@gitlab.com:example/repo.git + Push URL: git@gitlab.com:example/repo.git + HEAD branch: main + Remote branches: + main tracked`, + expectedBranch: "main", + expectedError: nil, + }, + { + name: "parses branch name with dashes", + input: `* remote origin + Fetch URL: git@gitlab.com:example/repo.git + Push URL: git@gitlab.com:example/repo.git + HEAD branch: release-2.0 + Remote branches: + release-2.0 tracked`, + expectedBranch: "release-2.0", + expectedError: nil, + }, + { + name: "parses branch name with slashes", + input: `* remote origin + Fetch URL: git@gitlab.com:example/repo.git + Push URL: git@gitlab.com:example/repo.git + HEAD branch: feature/new-feature + Remote branches: + feature/new-feature tracked`, + expectedBranch: "feature/new-feature", + expectedError: nil, + }, + { + name: "falls back to parsing (HEAD) marker", + input: `* remote origin + Fetch URL: git@gitlab.com:example/repo.git + Push URL: git@gitlab.com:example/repo.git + Remote branches: + main (HEAD) + dev tracked`, + expectedBranch: "main", + expectedError: nil, + }, + { + name: "handles (HEAD) marker with tracked", + input: `* remote origin + Fetch URL: git@gitlab.com:example/repo.git + Push URL: git@gitlab.com:example/repo.git + Remote branches: + master tracked (HEAD) + dev tracked`, + expectedBranch: "master", + expectedError: nil, + }, + { + name: "errors when no HEAD branch found", + input: `* remote origin + Fetch URL: git@gitlab.com:example/repo.git + Push URL: git@gitlab.com:example/repo.git + Remote branches: + main tracked + dev tracked`, + expectedBranch: "", + expectedError: errors.New("could not determine default branch from remote output"), + }, + { + name: "errors on empty input", + input: "", + expectedBranch: "", + expectedError: errors.New("could not determine default branch from remote output"), + }, + { + name: "errors on malformed output", + input: `This is not valid git remote output +Just some random text`, + expectedBranch: "", + expectedError: errors.New("could not determine default branch from remote output"), + }, + { + name: "handles Windows-style line endings", + input: "* remote origin\r\n Fetch URL: git@gitlab.com:example/repo.git\r\n Push URL: git@gitlab.com:example/repo.git\r\n HEAD branch: main\r\n Remote branches:\r\n main tracked", + expectedBranch: "main", + expectedError: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + branch, err := parseDefaultBranch([]byte(tt.input)) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.expectedBranch, branch) + }) + } +} -- GitLab From 1c67ff83912e8f5e91ce539c577b024014e5ffa7 Mon Sep 17 00:00:00 2001 From: Gary Holtz Date: Tue, 17 Jun 2025 21:34:46 -0500 Subject: [PATCH 2/3] wip --- pkg/git_mock/git_interface.go | 142 ++++++ pkg/git_mock/git_interface_mock_for_test.go | 389 ++++++++++++++++ pkg/git_mock/git_interface_test.go | 473 ++++++++++++++++++++ 3 files changed, 1004 insertions(+) diff --git a/pkg/git_mock/git_interface.go b/pkg/git_mock/git_interface.go index 4fe1d2f68..6b76424dd 100644 --- a/pkg/git_mock/git_interface.go +++ b/pkg/git_mock/git_interface.go @@ -43,13 +43,23 @@ type ( DeleteLocalBranch(branch string) error GetDefaultBranch(remote string) (string, error) RemoteBranchExists(remote string, branch string) (bool, error) + GetRemoteURL(remoteAlias string) (string, error) + ShowRefs(ref ...string) ([]Ref, error) + ListRemotes() ([]string, error) + Config(name string) (string, error) UncommittedChangeCount() (int, error) GitUserName() (string, error) LatestCommit(ref string) (*Commit, error) Commits(baseRef, headRef string) ([]*Commit, error) CommitBody(sha string) (string, error) Push(remote string, ref string) error + SetUpstream(remote string, branch string) error HasLocalBranch(branch string) (bool, error) + AddRemote(name, u string) error + SetRemoteResolution(name, resolution string) error + SetRemoteConfig(remote, key, value string) error + SetConfig(key, value string) error + GetAllConfig(key string) ([]byte, error) } StandardGitRunner struct { @@ -129,6 +139,49 @@ func (g *StandardGitRunner) CheckoutNewBranch(branch string) error { return nil } +// GetRemoteURL gets the URL for a remote +func (g *StandardGitRunner) GetRemoteURL(remoteAlias string) (string, error) { + return g.Config("remote." + remoteAlias + ".url") +} + +// ShowRefs resolves fully-qualified refs to commit hashes +func (g *StandardGitRunner) ShowRefs(ref ...string) ([]Ref, error) { + args := append([]string{"show-ref", "--verify", "--"}, ref...) + stdout, _, err := g.runGitCommand(strings.Join(args, " ")) + + var refs []Ref + for _, line := range outputLines(stdout) { + parts := strings.SplitN(line, " ", 2) + if len(parts) < 2 { + continue + } + refs = append(refs, Ref{ + Hash: parts[0], + Name: parts[1], + }) + } + + return refs, err +} + +// ListRemotes lists all git remotes +func (g *StandardGitRunner) ListRemotes() ([]string, error) { + stdout, stderr, err := g.runGitCommand("remote", "-v") + if err != nil { + return nil, fmt.Errorf("could not list remotes: %v - %s", err, stderr) + } + return outputLines(stdout), nil +} + +// Config gets a git config value +func (g *StandardGitRunner) Config(name string) (string, error) { + stdout, _, err := g.runGitCommand("config", name) + if err != nil { + return "", fmt.Errorf("unknown config key: %s", name) + } + return utils.FirstLine(stdout), nil +} + // UncommittedChangeCount returns the number of uncommitted changes func (g *StandardGitRunner) UncommittedChangeCount() (int, error) { stdout, stderr, err := g.runGitCommand("status", "--porcelain") @@ -221,12 +274,101 @@ func (g *StandardGitRunner) Push(remote string, ref string) error { return nil } +// SetUpstream sets the upstream (tracking) of a branch +func (g *StandardGitRunner) SetUpstream(remote string, branch string) error { + _, stderr, err := g.runGitCommand("branch", "--set-upstream-to", fmt.Sprintf("%s/%s", remote, branch)) + if err != nil { + return fmt.Errorf("could not set upstream: %v - %s", err, stderr) + } + return nil +} + // HasLocalBranch checks if a local branch exists func (g *StandardGitRunner) HasLocalBranch(branch string) (bool, error) { _, _, err := g.runGitCommand("rev-parse", "--verify", "refs/heads/"+branch) return err == nil, nil } +// AddRemote adds a new git remote and auto-fetches objects from it +func (g *StandardGitRunner) AddRemote(name, u string) error { + _, stderr, err := g.runGitCommand("remote", "add", "-f", name, u) + if err != nil { + return fmt.Errorf("could not add remote: %v - %s", err, stderr) + } + return nil +} + +// SetRemoteResolution sets the remote resolution +func (g *StandardGitRunner) SetRemoteResolution(name, resolution string) error { + return g.SetRemoteConfig(name, "glab-resolved", resolution) +} + +// SetRemoteConfig sets a remote config value +func (g *StandardGitRunner) SetRemoteConfig(remote, key, value string) error { + return g.SetConfig(fmt.Sprintf("remote.%s.%s", remote, key), value) +} + +// SetConfig sets a git config value +func (g *StandardGitRunner) SetConfig(key, value string) error { + found, err := g.configValueExists(key, value) + if err != nil { + return err + } + if found { + return nil + } + _, stderr, err := g.runGitCommand("config", "--add", key, value) + if err != nil { + return fmt.Errorf("setting git config: %v - %s", err, stderr) + } + return nil +} + +// GetAllConfig returns all values for a config key +func (g *StandardGitRunner) GetAllConfig(key string) ([]byte, error) { + err := g.assertValidConfigKey(key) + if err != nil { + return nil, err + } + + stdout, stderr, err := g.runGitCommand("config", "--get-all", key) + if err != nil { + // git-config will exit with 1 in almost all cases, but only when it prints + // out things it is an actual error that is worth mentioning. + if stderr == "" { + return nil, nil + } + return nil, fmt.Errorf("getting Git configuration value: %v - %s", err, stderr) + } + return stdout, nil +} + +// Helper methods +func (g *StandardGitRunner) configValueExists(key, value string) (bool, error) { + output, err := g.GetAllConfig(key) + if err == nil { + return g.outputContainsLine(output, value), nil + } + return false, err +} + +func (g *StandardGitRunner) outputContainsLine(output []byte, needle string) bool { + for _, line := range outputLines(output) { + if line == needle { + return true + } + } + return false +} + +func (g *StandardGitRunner) assertValidConfigKey(key string) error { + s := strings.Split(key, ".") + if len(s) < 2 { + return fmt.Errorf("incorrect Git configuration key.") + } + return nil +} + // runGitCommand executes a git command with proper environment setup and returns stdout/stderr func (g *StandardGitRunner) runGitCommand(args ...string) ([]byte, string, error) { cmd := exec.Command(g.gitBinary, args...) diff --git a/pkg/git_mock/git_interface_mock_for_test.go b/pkg/git_mock/git_interface_mock_for_test.go index a1acb9fe0..f03f5789b 100644 --- a/pkg/git_mock/git_interface_mock_for_test.go +++ b/pkg/git_mock/git_interface_mock_for_test.go @@ -38,6 +38,44 @@ func (m *MockGitInterface) EXPECT() *MockGitInterfaceMockRecorder { return m.recorder } +// AddRemote mocks base method. +func (m *MockGitInterface) AddRemote(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddRemote", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddRemote indicates an expected call of AddRemote. +func (mr *MockGitInterfaceMockRecorder) AddRemote(arg0, arg1 any) *MockGitInterfaceAddRemoteCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRemote", reflect.TypeOf((*MockGitInterface)(nil).AddRemote), arg0, arg1) + return &MockGitInterfaceAddRemoteCall{Call: call} +} + +// MockGitInterfaceAddRemoteCall wrap *gomock.Call +type MockGitInterfaceAddRemoteCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceAddRemoteCall) Return(arg0 error) *MockGitInterfaceAddRemoteCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceAddRemoteCall) Do(f func(string, string) error) *MockGitInterfaceAddRemoteCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceAddRemoteCall) DoAndReturn(f func(string, string) error) *MockGitInterfaceAddRemoteCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // CheckoutBranch mocks base method. func (m *MockGitInterface) CheckoutBranch(arg0 string) error { m.ctrl.T.Helper() @@ -192,6 +230,45 @@ func (c *MockGitInterfaceCommitsCall) DoAndReturn(f func(string, string) ([]*Com return c } +// Config mocks base method. +func (m *MockGitInterface) Config(arg0 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Config", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Config indicates an expected call of Config. +func (mr *MockGitInterfaceMockRecorder) Config(arg0 any) *MockGitInterfaceConfigCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Config", reflect.TypeOf((*MockGitInterface)(nil).Config), arg0) + return &MockGitInterfaceConfigCall{Call: call} +} + +// MockGitInterfaceConfigCall wrap *gomock.Call +type MockGitInterfaceConfigCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceConfigCall) Return(arg0 string, arg1 error) *MockGitInterfaceConfigCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceConfigCall) Do(f func(string) (string, error)) *MockGitInterfaceConfigCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceConfigCall) DoAndReturn(f func(string) (string, error)) *MockGitInterfaceConfigCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // CurrentBranch mocks base method. func (m *MockGitInterface) CurrentBranch() (string, error) { m.ctrl.T.Helper() @@ -269,6 +346,45 @@ func (c *MockGitInterfaceDeleteLocalBranchCall) DoAndReturn(f func(string) error return c } +// GetAllConfig mocks base method. +func (m *MockGitInterface) GetAllConfig(arg0 string) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAllConfig", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAllConfig indicates an expected call of GetAllConfig. +func (mr *MockGitInterfaceMockRecorder) GetAllConfig(arg0 any) *MockGitInterfaceGetAllConfigCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllConfig", reflect.TypeOf((*MockGitInterface)(nil).GetAllConfig), arg0) + return &MockGitInterfaceGetAllConfigCall{Call: call} +} + +// MockGitInterfaceGetAllConfigCall wrap *gomock.Call +type MockGitInterfaceGetAllConfigCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceGetAllConfigCall) Return(arg0 []byte, arg1 error) *MockGitInterfaceGetAllConfigCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceGetAllConfigCall) Do(f func(string) ([]byte, error)) *MockGitInterfaceGetAllConfigCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceGetAllConfigCall) DoAndReturn(f func(string) ([]byte, error)) *MockGitInterfaceGetAllConfigCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // GetDefaultBranch mocks base method. func (m *MockGitInterface) GetDefaultBranch(arg0 string) (string, error) { m.ctrl.T.Helper() @@ -308,6 +424,45 @@ func (c *MockGitInterfaceGetDefaultBranchCall) DoAndReturn(f func(string) (strin return c } +// GetRemoteURL mocks base method. +func (m *MockGitInterface) GetRemoteURL(arg0 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRemoteURL", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRemoteURL indicates an expected call of GetRemoteURL. +func (mr *MockGitInterfaceMockRecorder) GetRemoteURL(arg0 any) *MockGitInterfaceGetRemoteURLCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRemoteURL", reflect.TypeOf((*MockGitInterface)(nil).GetRemoteURL), arg0) + return &MockGitInterfaceGetRemoteURLCall{Call: call} +} + +// MockGitInterfaceGetRemoteURLCall wrap *gomock.Call +type MockGitInterfaceGetRemoteURLCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceGetRemoteURLCall) Return(arg0 string, arg1 error) *MockGitInterfaceGetRemoteURLCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceGetRemoteURLCall) Do(f func(string) (string, error)) *MockGitInterfaceGetRemoteURLCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceGetRemoteURLCall) DoAndReturn(f func(string) (string, error)) *MockGitInterfaceGetRemoteURLCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // GitUserName mocks base method. func (m *MockGitInterface) GitUserName() (string, error) { m.ctrl.T.Helper() @@ -425,6 +580,45 @@ func (c *MockGitInterfaceLatestCommitCall) DoAndReturn(f func(string) (*Commit, return c } +// ListRemotes mocks base method. +func (m *MockGitInterface) ListRemotes() ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListRemotes") + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListRemotes indicates an expected call of ListRemotes. +func (mr *MockGitInterfaceMockRecorder) ListRemotes() *MockGitInterfaceListRemotesCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListRemotes", reflect.TypeOf((*MockGitInterface)(nil).ListRemotes)) + return &MockGitInterfaceListRemotesCall{Call: call} +} + +// MockGitInterfaceListRemotesCall wrap *gomock.Call +type MockGitInterfaceListRemotesCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceListRemotesCall) Return(arg0 []string, arg1 error) *MockGitInterfaceListRemotesCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceListRemotesCall) Do(f func() ([]string, error)) *MockGitInterfaceListRemotesCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceListRemotesCall) DoAndReturn(f func() ([]string, error)) *MockGitInterfaceListRemotesCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // Push mocks base method. func (m *MockGitInterface) Push(arg0, arg1 string) error { m.ctrl.T.Helper() @@ -502,6 +696,201 @@ func (c *MockGitInterfaceRemoteBranchExistsCall) DoAndReturn(f func(string, stri return c } +// SetConfig mocks base method. +func (m *MockGitInterface) SetConfig(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetConfig", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetConfig indicates an expected call of SetConfig. +func (mr *MockGitInterfaceMockRecorder) SetConfig(arg0, arg1 any) *MockGitInterfaceSetConfigCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetConfig", reflect.TypeOf((*MockGitInterface)(nil).SetConfig), arg0, arg1) + return &MockGitInterfaceSetConfigCall{Call: call} +} + +// MockGitInterfaceSetConfigCall wrap *gomock.Call +type MockGitInterfaceSetConfigCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceSetConfigCall) Return(arg0 error) *MockGitInterfaceSetConfigCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceSetConfigCall) Do(f func(string, string) error) *MockGitInterfaceSetConfigCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceSetConfigCall) DoAndReturn(f func(string, string) error) *MockGitInterfaceSetConfigCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// SetRemoteConfig mocks base method. +func (m *MockGitInterface) SetRemoteConfig(arg0, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetRemoteConfig", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetRemoteConfig indicates an expected call of SetRemoteConfig. +func (mr *MockGitInterfaceMockRecorder) SetRemoteConfig(arg0, arg1, arg2 any) *MockGitInterfaceSetRemoteConfigCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRemoteConfig", reflect.TypeOf((*MockGitInterface)(nil).SetRemoteConfig), arg0, arg1, arg2) + return &MockGitInterfaceSetRemoteConfigCall{Call: call} +} + +// MockGitInterfaceSetRemoteConfigCall wrap *gomock.Call +type MockGitInterfaceSetRemoteConfigCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceSetRemoteConfigCall) Return(arg0 error) *MockGitInterfaceSetRemoteConfigCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceSetRemoteConfigCall) Do(f func(string, string, string) error) *MockGitInterfaceSetRemoteConfigCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceSetRemoteConfigCall) DoAndReturn(f func(string, string, string) error) *MockGitInterfaceSetRemoteConfigCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// SetRemoteResolution mocks base method. +func (m *MockGitInterface) SetRemoteResolution(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetRemoteResolution", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetRemoteResolution indicates an expected call of SetRemoteResolution. +func (mr *MockGitInterfaceMockRecorder) SetRemoteResolution(arg0, arg1 any) *MockGitInterfaceSetRemoteResolutionCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRemoteResolution", reflect.TypeOf((*MockGitInterface)(nil).SetRemoteResolution), arg0, arg1) + return &MockGitInterfaceSetRemoteResolutionCall{Call: call} +} + +// MockGitInterfaceSetRemoteResolutionCall wrap *gomock.Call +type MockGitInterfaceSetRemoteResolutionCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceSetRemoteResolutionCall) Return(arg0 error) *MockGitInterfaceSetRemoteResolutionCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceSetRemoteResolutionCall) Do(f func(string, string) error) *MockGitInterfaceSetRemoteResolutionCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceSetRemoteResolutionCall) DoAndReturn(f func(string, string) error) *MockGitInterfaceSetRemoteResolutionCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// SetUpstream mocks base method. +func (m *MockGitInterface) SetUpstream(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetUpstream", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetUpstream indicates an expected call of SetUpstream. +func (mr *MockGitInterfaceMockRecorder) SetUpstream(arg0, arg1 any) *MockGitInterfaceSetUpstreamCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUpstream", reflect.TypeOf((*MockGitInterface)(nil).SetUpstream), arg0, arg1) + return &MockGitInterfaceSetUpstreamCall{Call: call} +} + +// MockGitInterfaceSetUpstreamCall wrap *gomock.Call +type MockGitInterfaceSetUpstreamCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceSetUpstreamCall) Return(arg0 error) *MockGitInterfaceSetUpstreamCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceSetUpstreamCall) Do(f func(string, string) error) *MockGitInterfaceSetUpstreamCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceSetUpstreamCall) DoAndReturn(f func(string, string) error) *MockGitInterfaceSetUpstreamCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// ShowRefs mocks base method. +func (m *MockGitInterface) ShowRefs(arg0 ...string) ([]Ref, error) { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range arg0 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ShowRefs", varargs...) + ret0, _ := ret[0].([]Ref) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ShowRefs indicates an expected call of ShowRefs. +func (mr *MockGitInterfaceMockRecorder) ShowRefs(arg0 ...any) *MockGitInterfaceShowRefsCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShowRefs", reflect.TypeOf((*MockGitInterface)(nil).ShowRefs), arg0...) + return &MockGitInterfaceShowRefsCall{Call: call} +} + +// MockGitInterfaceShowRefsCall wrap *gomock.Call +type MockGitInterfaceShowRefsCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGitInterfaceShowRefsCall) Return(arg0 []Ref, arg1 error) *MockGitInterfaceShowRefsCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGitInterfaceShowRefsCall) Do(f func(...string) ([]Ref, error)) *MockGitInterfaceShowRefsCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGitInterfaceShowRefsCall) DoAndReturn(f func(...string) ([]Ref, error)) *MockGitInterfaceShowRefsCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // UncommittedChangeCount mocks base method. func (m *MockGitInterface) UncommittedChangeCount() (int, error) { m.ctrl.T.Helper() diff --git a/pkg/git_mock/git_interface_test.go b/pkg/git_mock/git_interface_test.go index 178cb72df..1c6589f3f 100644 --- a/pkg/git_mock/git_interface_test.go +++ b/pkg/git_mock/git_interface_test.go @@ -283,6 +283,479 @@ func TestNewStandardGitRunner(t *testing.T) { } } +func TestGetRemoteURL(t *testing.T) { + tests := []struct { + name string + remoteAlias string + setupMock func(*MockGitInterface, string) + expectedURL string + expectedError error + }{ + { + name: "successful remote URL retrieval", + remoteAlias: "origin", + setupMock: func(m *MockGitInterface, remoteAlias string) { + m.EXPECT().GetRemoteURL(remoteAlias).Return("https://github.com/user/repo.git", nil) + }, + expectedURL: "https://github.com/user/repo.git", + expectedError: nil, + }, + { + name: "remote URL not found", + remoteAlias: "invalid", + setupMock: func(m *MockGitInterface, remoteAlias string) { + m.EXPECT().GetRemoteURL(remoteAlias).Return("", errors.New("unknown config key: remote.invalid.url")) + }, + expectedURL: "", + expectedError: errors.New("unknown config key: remote.invalid.url"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit, tt.remoteAlias) + + url, err := mockGit.GetRemoteURL(tt.remoteAlias) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.expectedURL, url) + }) + } +} + +func TestShowRefs(t *testing.T) { + tests := []struct { + name string + refs []string + setupMock func(*MockGitInterface, []string) + expectedRefs []Ref + expectedError error + }{ + { + name: "successful refs retrieval", + refs: []string{"refs/heads/main"}, + setupMock: func(m *MockGitInterface, refs []string) { + args := make([]any, len(refs)) + for i, ref := range refs { + args[i] = ref + } + m.EXPECT().ShowRefs(args...).Return([]Ref{ + {Hash: "abc123", Name: "refs/heads/main"}, + }, nil) + }, + expectedRefs: []Ref{ + {Hash: "abc123", Name: "refs/heads/main"}, + }, + expectedError: nil, + }, + { + name: "refs not found", + refs: []string{"refs/heads/nonexistent"}, + setupMock: func(m *MockGitInterface, refs []string) { + args := make([]any, len(refs)) + for i, ref := range refs { + args[i] = ref + } + m.EXPECT().ShowRefs(args...).Return([]Ref{}, errors.New("fatal: bad ref")) + }, + expectedRefs: []Ref{}, + expectedError: errors.New("fatal: bad ref"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit, tt.refs) + + refs, err := mockGit.ShowRefs(tt.refs...) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.expectedRefs, refs) + }) + } +} + +func TestListRemotes(t *testing.T) { + tests := []struct { + name string + setupMock func(*MockGitInterface) + expectedRemotes []string + expectedError error + }{ + { + name: "successful remotes listing", + setupMock: func(m *MockGitInterface) { + m.EXPECT().ListRemotes().Return([]string{ + "origin\thttps://github.com/user/repo.git (fetch)", + "origin\thttps://github.com/user/repo.git (push)", + }, nil) + }, + expectedRemotes: []string{ + "origin\thttps://github.com/user/repo.git (fetch)", + "origin\thttps://github.com/user/repo.git (push)", + }, + expectedError: nil, + }, + { + name: "no remotes", + setupMock: func(m *MockGitInterface) { + m.EXPECT().ListRemotes().Return([]string{}, nil) + }, + expectedRemotes: []string{}, + expectedError: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit) + + remotes, err := mockGit.ListRemotes() + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.expectedRemotes, remotes) + }) + } +} + +func TestConfig(t *testing.T) { + tests := []struct { + name string + key string + setupMock func(*MockGitInterface, string) + expectedValue string + expectedError error + }{ + { + name: "successful config retrieval", + key: "user.name", + setupMock: func(m *MockGitInterface, key string) { + m.EXPECT().Config(key).Return("John Doe", nil) + }, + expectedValue: "John Doe", + expectedError: nil, + }, + { + name: "config key not found", + key: "invalid.key", + setupMock: func(m *MockGitInterface, key string) { + m.EXPECT().Config(key).Return("", errors.New("unknown config key: invalid.key")) + }, + expectedValue: "", + expectedError: errors.New("unknown config key: invalid.key"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit, tt.key) + + value, err := mockGit.Config(tt.key) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.expectedValue, value) + }) + } +} + +func TestSetUpstream(t *testing.T) { + tests := []struct { + name string + remote string + branch string + setupMock func(*MockGitInterface, string, string) + expectedError error + }{ + { + name: "successful upstream set", + remote: "origin", + branch: "main", + setupMock: func(m *MockGitInterface, remote, branch string) { + m.EXPECT().SetUpstream(remote, branch).Return(nil) + }, + expectedError: nil, + }, + { + name: "upstream set fails", + remote: "origin", + branch: "main", + setupMock: func(m *MockGitInterface, remote, branch string) { + m.EXPECT().SetUpstream(remote, branch).Return(errors.New("could not set upstream")) + }, + expectedError: errors.New("could not set upstream"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit, tt.remote, tt.branch) + + err := mockGit.SetUpstream(tt.remote, tt.branch) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestAddRemote(t *testing.T) { + tests := []struct { + name string + remoteName string + remoteURL string + setupMock func(*MockGitInterface, string, string) + expectedError error + }{ + { + name: "successful remote add", + remoteName: "upstream", + remoteURL: "https://github.com/upstream/repo.git", + setupMock: func(m *MockGitInterface, name, url string) { + m.EXPECT().AddRemote(name, url).Return(nil) + }, + expectedError: nil, + }, + { + name: "remote already exists", + remoteName: "origin", + remoteURL: "https://github.com/user/repo.git", + setupMock: func(m *MockGitInterface, name, url string) { + m.EXPECT().AddRemote(name, url).Return(errors.New("could not add remote")) + }, + expectedError: errors.New("could not add remote"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit, tt.remoteName, tt.remoteURL) + + err := mockGit.AddRemote(tt.remoteName, tt.remoteURL) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestSetRemoteResolution(t *testing.T) { + tests := []struct { + name string + remoteName string + resolution string + setupMock func(*MockGitInterface, string, string) + expectedError error + }{ + { + name: "successful remote resolution set", + remoteName: "origin", + resolution: "https://gitlab.com/user/repo", + setupMock: func(m *MockGitInterface, name, resolution string) { + m.EXPECT().SetRemoteResolution(name, resolution).Return(nil) + }, + expectedError: nil, + }, + { + name: "remote resolution set fails", + remoteName: "origin", + resolution: "https://gitlab.com/user/repo", + setupMock: func(m *MockGitInterface, name, resolution string) { + m.EXPECT().SetRemoteResolution(name, resolution).Return(errors.New("setting git config")) + }, + expectedError: errors.New("setting git config"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit, tt.remoteName, tt.resolution) + + err := mockGit.SetRemoteResolution(tt.remoteName, tt.resolution) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestSetRemoteConfig(t *testing.T) { + tests := []struct { + name string + remote string + key string + value string + setupMock func(*MockGitInterface, string, string, string) + expectedError error + }{ + { + name: "successful remote config set", + remote: "origin", + key: "pushurl", + value: "https://github.com/user/repo.git", + setupMock: func(m *MockGitInterface, remote, key, value string) { + m.EXPECT().SetRemoteConfig(remote, key, value).Return(nil) + }, + expectedError: nil, + }, + { + name: "remote config set fails", + remote: "origin", + key: "pushurl", + value: "https://github.com/user/repo.git", + setupMock: func(m *MockGitInterface, remote, key, value string) { + m.EXPECT().SetRemoteConfig(remote, key, value).Return(errors.New("setting git config")) + }, + expectedError: errors.New("setting git config"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit, tt.remote, tt.key, tt.value) + + err := mockGit.SetRemoteConfig(tt.remote, tt.key, tt.value) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestGetAllConfig(t *testing.T) { + tests := []struct { + name string + key string + setupMock func(*MockGitInterface, string) + expectedOutput []byte + expectedError error + }{ + { + name: "successful config retrieval", + key: "remote.origin.url", + setupMock: func(m *MockGitInterface, key string) { + m.EXPECT().GetAllConfig(key).Return([]byte("https://github.com/user/repo.git\n"), nil) + }, + expectedOutput: []byte("https://github.com/user/repo.git\n"), + expectedError: nil, + }, + { + name: "config key not found", + key: "nonexistent.key", + setupMock: func(m *MockGitInterface, key string) { + m.EXPECT().GetAllConfig(key).Return(nil, nil) + }, + expectedOutput: nil, + expectedError: nil, + }, + { + name: "invalid config key", + key: "invalid", + setupMock: func(m *MockGitInterface, key string) { + m.EXPECT().GetAllConfig(key).Return(nil, errors.New("incorrect Git configuration key.")) + }, + expectedOutput: nil, + expectedError: errors.New("incorrect Git configuration key."), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockGit := NewMockGitInterface(ctrl) + + tt.setupMock(mockGit, tt.key) + + output, err := mockGit.GetAllConfig(tt.key) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.expectedOutput, output) + }) + } +} + func TestCheckoutNewBranch(t *testing.T) { tests := []struct { name string -- GitLab From a84c23f7fba3b32954ab5807854cf54ecc3796cb Mon Sep 17 00:00:00 2001 From: Gary Holtz Date: Tue, 17 Jun 2025 21:39:52 -0500 Subject: [PATCH 3/3] wip --- pkg/git_mock/git_interface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/git_mock/git_interface.go b/pkg/git_mock/git_interface.go index 6b76424dd..220874fa1 100644 --- a/pkg/git_mock/git_interface.go +++ b/pkg/git_mock/git_interface.go @@ -147,7 +147,7 @@ func (g *StandardGitRunner) GetRemoteURL(remoteAlias string) (string, error) { // ShowRefs resolves fully-qualified refs to commit hashes func (g *StandardGitRunner) ShowRefs(ref ...string) ([]Ref, error) { args := append([]string{"show-ref", "--verify", "--"}, ref...) - stdout, _, err := g.runGitCommand(strings.Join(args, " ")) + stdout, _, err := g.runGitCommand(args...) var refs []Ref for _, line := range outputLines(stdout) { -- GitLab