Files
Sameen Karim d4967d8d04 submit command
2026-04-03 04:02:59 -04:00

211 lines
5.2 KiB
Go

package cmd
import (
"fmt"
"io"
"testing"
"github.com/github/gh-stack/internal/config"
"github.com/github/gh-stack/internal/git"
"github.com/github/gh-stack/internal/github"
"github.com/github/gh-stack/internal/stack"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// newPushMock creates a MockOps pre-configured for push tests.
func newPushMock(tmpDir string, currentBranch string) *git.MockOps {
return &git.MockOps{
GitDirFn: func() (string, error) { return tmpDir, nil },
CurrentBranchFn: func() (string, error) { return currentBranch, nil },
ResolveRemoteFn: func(string) (string, error) { return "origin", nil },
PushFn: func(string, []string, bool, bool) error { return nil },
}
}
func TestPush_PushesAllBranches(t *testing.T) {
s := stack.Stack{
Trunk: stack.BranchRef{Branch: "main"},
Branches: []stack.BranchRef{
{Branch: "b1"},
{Branch: "b2"},
},
}
tmpDir := t.TempDir()
writeStackFile(t, tmpDir, s)
var pushCalls []pushCall
mock := newPushMock(tmpDir, "b1")
mock.PushFn = func(remote string, branches []string, force, atomic bool) error {
pushCalls = append(pushCalls, pushCall{remote, branches, force, atomic})
return nil
}
restore := git.SetOps(mock)
defer restore()
cfg, _, errR := config.NewTestConfig()
cfg.GitHubClientOverride = &github.MockClient{}
cmd := PushCmd(cfg)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
err := cmd.Execute()
cfg.Err.Close()
errOut, _ := io.ReadAll(errR)
output := string(errOut)
assert.NoError(t, err)
require.Len(t, pushCalls, 1)
assert.Equal(t, "origin", pushCalls[0].remote)
assert.Equal(t, []string{"b1", "b2"}, pushCalls[0].branches)
assert.True(t, pushCalls[0].force)
assert.True(t, pushCalls[0].atomic)
assert.Contains(t, output, "Pushed 2 branches")
assert.Contains(t, output, "gh stack submit", "should hint about submit when branches have no PRs")
}
func TestPush_NoSubmitHintWhenPRsExist(t *testing.T) {
s := stack.Stack{
Trunk: stack.BranchRef{Branch: "main"},
Branches: []stack.BranchRef{
{Branch: "b1", PullRequest: &stack.PullRequestRef{Number: 10}},
{Branch: "b2", PullRequest: &stack.PullRequestRef{Number: 11}},
},
}
tmpDir := t.TempDir()
writeStackFile(t, tmpDir, s)
mock := newPushMock(tmpDir, "b1")
restore := git.SetOps(mock)
defer restore()
cfg, _, errR := config.NewTestConfig()
cfg.GitHubClientOverride = &github.MockClient{}
cmd := PushCmd(cfg)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
err := cmd.Execute()
cfg.Err.Close()
errOut, _ := io.ReadAll(errR)
output := string(errOut)
assert.NoError(t, err)
assert.Contains(t, output, "Pushed 2 branches")
assert.NotContains(t, output, "gh stack submit", "should not hint about submit when all branches have PRs")
}
func TestPush_SkipsMergedBranches(t *testing.T) {
s := stack.Stack{
Trunk: stack.BranchRef{Branch: "main"},
Branches: []stack.BranchRef{
{Branch: "b1", PullRequest: &stack.PullRequestRef{Number: 1, Merged: true}},
{Branch: "b2"},
{Branch: "b3", PullRequest: &stack.PullRequestRef{Number: 3, Merged: true}},
},
}
tmpDir := t.TempDir()
writeStackFile(t, tmpDir, s)
var pushCalls []pushCall
mock := newPushMock(tmpDir, "b2")
mock.PushFn = func(remote string, branches []string, force, atomic bool) error {
pushCalls = append(pushCalls, pushCall{remote, branches, force, atomic})
return nil
}
restore := git.SetOps(mock)
defer restore()
cfg, _, errR := config.NewTestConfig()
cfg.GitHubClientOverride = &github.MockClient{}
cmd := PushCmd(cfg)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
err := cmd.Execute()
cfg.Err.Close()
_, _ = io.ReadAll(errR)
assert.NoError(t, err)
require.Len(t, pushCalls, 1)
assert.Equal(t, []string{"b2"}, pushCalls[0].branches)
}
func TestPush_PushFailure(t *testing.T) {
s := stack.Stack{
Trunk: stack.BranchRef{Branch: "main"},
Branches: []stack.BranchRef{
{Branch: "b1"},
},
}
tmpDir := t.TempDir()
writeStackFile(t, tmpDir, s)
mock := newPushMock(tmpDir, "b1")
mock.PushFn = func(string, []string, bool, bool) error {
return fmt.Errorf("remote rejected")
}
restore := git.SetOps(mock)
defer restore()
cfg, _, errR := config.NewTestConfig()
cfg.GitHubClientOverride = &github.MockClient{}
cmd := PushCmd(cfg)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
err := cmd.Execute()
cfg.Err.Close()
errOut, _ := io.ReadAll(errR)
output := string(errOut)
assert.ErrorIs(t, err, ErrSilent)
assert.Contains(t, output, "failed to push")
}
func TestPush_DoesNotCreatePRs(t *testing.T) {
s := stack.Stack{
Trunk: stack.BranchRef{Branch: "main"},
Branches: []stack.BranchRef{
{Branch: "b1"},
{Branch: "b2"},
},
}
tmpDir := t.TempDir()
writeStackFile(t, tmpDir, s)
mock := newPushMock(tmpDir, "b1")
restore := git.SetOps(mock)
defer restore()
createPRCalled := false
cfg, _, errR := config.NewTestConfig()
cfg.GitHubClientOverride = &github.MockClient{
CreatePRFn: func(string, string, string, string, bool) (*github.PullRequest, error) {
createPRCalled = true
return nil, nil
},
}
cmd := PushCmd(cfg)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
err := cmd.Execute()
cfg.Err.Close()
_, _ = io.ReadAll(errR)
assert.NoError(t, err)
assert.False(t, createPRCalled, "push should not create PRs")
}