769 lines
26 KiB
Go
769 lines
26 KiB
Go
package develop
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/MakeNowJust/heredoc"
|
|
"github.com/cli/cli/v2/context"
|
|
"github.com/cli/cli/v2/git"
|
|
"github.com/cli/cli/v2/internal/ghrepo"
|
|
"github.com/cli/cli/v2/internal/run"
|
|
"github.com/cli/cli/v2/pkg/cmd/issue/argparsetest"
|
|
"github.com/cli/cli/v2/pkg/cmdutil"
|
|
"github.com/cli/cli/v2/pkg/httpmock"
|
|
"github.com/cli/cli/v2/pkg/iostreams"
|
|
"github.com/google/shlex"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNewCmdDevelop(t *testing.T) {
|
|
// Test shared parsing of issue number / URL.
|
|
argparsetest.TestArgParsing(t, NewCmdDevelop)
|
|
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
output DevelopOptions
|
|
expectedBaseRepo ghrepo.Interface
|
|
wantStdout string
|
|
wantStderr string
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "branch-repo flag",
|
|
input: "1 --branch-repo owner/repo",
|
|
output: DevelopOptions{
|
|
IssueNumber: 1,
|
|
BranchRepo: "owner/repo",
|
|
},
|
|
},
|
|
{
|
|
name: "base flag",
|
|
input: "1 --base feature",
|
|
output: DevelopOptions{
|
|
IssueNumber: 1,
|
|
BaseBranch: "feature",
|
|
},
|
|
},
|
|
{
|
|
name: "checkout flag",
|
|
input: "1 --checkout",
|
|
output: DevelopOptions{
|
|
IssueNumber: 1,
|
|
Checkout: true,
|
|
},
|
|
},
|
|
{
|
|
name: "list flag",
|
|
input: "1 --list",
|
|
output: DevelopOptions{
|
|
IssueNumber: 1,
|
|
List: true,
|
|
},
|
|
},
|
|
{
|
|
name: "name flag",
|
|
input: "1 --name feature",
|
|
output: DevelopOptions{
|
|
IssueNumber: 1,
|
|
Name: "feature",
|
|
},
|
|
},
|
|
{
|
|
name: "issue-repo flag",
|
|
input: "1 --issue-repo cli/cli",
|
|
output: DevelopOptions{
|
|
IssueNumber: 1,
|
|
},
|
|
wantStdout: "Flag --issue-repo has been deprecated, use `--repo` instead\n",
|
|
},
|
|
{
|
|
name: "list and branch repo flags",
|
|
input: "1 --list --branch-repo owner/repo",
|
|
wantErr: true,
|
|
errMsg: "specify only one of `--list` or `--branch-repo`",
|
|
},
|
|
{
|
|
name: "list and base flags",
|
|
input: "1 --list --base feature",
|
|
wantErr: true,
|
|
errMsg: "specify only one of `--list` or `--base`",
|
|
},
|
|
{
|
|
name: "list and checkout flags",
|
|
input: "1 --list --checkout",
|
|
wantErr: true,
|
|
errMsg: "specify only one of `--list` or `--checkout`",
|
|
},
|
|
{
|
|
name: "list and name flags",
|
|
input: "1 --list --name my-branch",
|
|
wantErr: true,
|
|
errMsg: "specify only one of `--list` or `--name`",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ios, _, stdOut, stdErr := iostreams.Test()
|
|
f := &cmdutil.Factory{
|
|
IOStreams: ios,
|
|
}
|
|
argv, err := shlex.Split(tt.input)
|
|
assert.NoError(t, err)
|
|
var gotOpts *DevelopOptions
|
|
cmd := NewCmdDevelop(f, func(opts *DevelopOptions) error {
|
|
gotOpts = opts
|
|
return nil
|
|
})
|
|
cmd.SetArgs(argv)
|
|
cmd.SetIn(&bytes.Buffer{})
|
|
cmd.SetOut(stdOut)
|
|
cmd.SetErr(stdErr)
|
|
|
|
_, err = cmd.ExecuteC()
|
|
if tt.wantErr {
|
|
require.EqualError(t, err, tt.errMsg)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.output.IssueNumber, gotOpts.IssueNumber)
|
|
assert.Equal(t, tt.output.Name, gotOpts.Name)
|
|
assert.Equal(t, tt.output.BaseBranch, gotOpts.BaseBranch)
|
|
assert.Equal(t, tt.output.Checkout, gotOpts.Checkout)
|
|
assert.Equal(t, tt.output.List, gotOpts.List)
|
|
assert.Equal(t, tt.wantStdout, stdOut.String())
|
|
assert.Equal(t, tt.wantStderr, stdErr.String())
|
|
if tt.expectedBaseRepo != nil {
|
|
baseRepo, err := gotOpts.BaseRepo()
|
|
require.NoError(t, err)
|
|
require.True(
|
|
t,
|
|
ghrepo.IsSame(tt.expectedBaseRepo, baseRepo),
|
|
"expected base repo %+v, got %+v", tt.expectedBaseRepo, baseRepo,
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDevelopRun(t *testing.T) {
|
|
featureEnabledPayload := `{"data":{"LinkedBranch":{"fields":[{"name":"id"},{"name":"ref"}]}}}`
|
|
featureDisabledPayload := `{"data":{"LinkedBranch":null}}`
|
|
|
|
tests := []struct {
|
|
name string
|
|
opts *DevelopOptions
|
|
cmdStubs func(*run.CommandStubber)
|
|
runStubs func(*run.CommandStubber)
|
|
remotes map[string]string
|
|
httpStubs func(*httpmock.Registry, *testing.T)
|
|
expectedOut string
|
|
expectedErrOut string
|
|
wantErr string
|
|
tty bool
|
|
}{
|
|
{
|
|
name: "returns an error when the feature is not supported by the API",
|
|
opts: &DevelopOptions{
|
|
IssueNumber: 42,
|
|
List: true,
|
|
},
|
|
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"hasIssuesEnabled":true,"issue":{"id":"SOMEID","number":42}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
|
httpmock.StringResponse(featureDisabledPayload),
|
|
)
|
|
},
|
|
wantErr: "the `gh issue develop` command is not currently available",
|
|
},
|
|
{
|
|
name: "list branches for an issue",
|
|
opts: &DevelopOptions{
|
|
IssueNumber: 42,
|
|
List: true,
|
|
},
|
|
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"hasIssuesEnabled":true,"issue":{"id":"SOMEID","number":42}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
|
httpmock.StringResponse(featureEnabledPayload),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query ListLinkedBranches\b`),
|
|
httpmock.GraphQLQuery(`
|
|
{"data":{"repository":{"issue":{"linkedBranches":{"nodes":[{"ref":{"name":"foo","repository":{"url":"https://github.com/OWNER/REPO"}}},{"ref":{"name":"bar","repository":{"url":"https://github.com/OWNER/REPO"}}}]}}}}}
|
|
`, func(query string, inputs map[string]interface{}) {
|
|
assert.Equal(t, float64(42), inputs["number"])
|
|
assert.Equal(t, "OWNER", inputs["owner"])
|
|
assert.Equal(t, "REPO", inputs["name"])
|
|
}))
|
|
},
|
|
expectedOut: "foo\thttps://github.com/OWNER/REPO/tree/foo\nbar\thttps://github.com/OWNER/REPO/tree/bar\n",
|
|
},
|
|
{
|
|
name: "list branches for an issue in tty",
|
|
opts: &DevelopOptions{
|
|
IssueNumber: 42,
|
|
List: true,
|
|
},
|
|
tty: true,
|
|
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"hasIssuesEnabled":true,"issue":{"id":"SOMEID","number":42}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
|
httpmock.StringResponse(featureEnabledPayload),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query ListLinkedBranches\b`),
|
|
httpmock.GraphQLQuery(`
|
|
{"data":{"repository":{"issue":{"linkedBranches":{"nodes":[{"ref":{"name":"foo","repository":{"url":"https://github.com/OWNER/REPO"}}},{"ref":{"name":"bar","repository":{"url":"https://github.com/OWNER/OTHER-REPO"}}}]}}}}}
|
|
`, func(query string, inputs map[string]interface{}) {
|
|
assert.Equal(t, float64(42), inputs["number"])
|
|
assert.Equal(t, "OWNER", inputs["owner"])
|
|
assert.Equal(t, "REPO", inputs["name"])
|
|
}))
|
|
},
|
|
expectedOut: heredoc.Doc(`
|
|
|
|
Showing linked branches for OWNER/REPO#42
|
|
|
|
BRANCH URL
|
|
foo https://github.com/OWNER/REPO/tree/foo
|
|
bar https://github.com/OWNER/OTHER-REPO/tree/bar
|
|
`),
|
|
},
|
|
{
|
|
name: "develop new branch",
|
|
opts: &DevelopOptions{
|
|
IssueNumber: 123,
|
|
},
|
|
remotes: map[string]string{
|
|
"origin": "OWNER/REPO",
|
|
},
|
|
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
|
httpmock.StringResponse(featureEnabledPayload),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"hasIssuesEnabled":true,"issue":{"id": "SOMEID","number":123,"title":"my issue"}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","defaultBranchRef":{"target":{"oid":"DEFAULTOID"}},"ref":{"target":{"oid":""}}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation CreateLinkedBranch\b`),
|
|
httpmock.GraphQLMutation(`{"data":{"createLinkedBranch":{"linkedBranch":{"id":"2","ref":{"name":"my-issue-1"}}}}}`,
|
|
func(inputs map[string]interface{}) {
|
|
assert.Equal(t, "REPOID", inputs["repositoryId"])
|
|
assert.Equal(t, "SOMEID", inputs["issueId"])
|
|
assert.Equal(t, "DEFAULTOID", inputs["oid"])
|
|
}),
|
|
)
|
|
},
|
|
runStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git fetch origin \+refs/heads/my-issue-1:refs/remotes/origin/my-issue-1`, 0, "")
|
|
},
|
|
expectedOut: "github.com/OWNER/REPO/tree/my-issue-1\n",
|
|
},
|
|
{
|
|
name: "develop new branch in different repo than issue",
|
|
opts: &DevelopOptions{
|
|
IssueNumber: 123,
|
|
BranchRepo: "OWNER2/REPO",
|
|
},
|
|
remotes: map[string]string{
|
|
"origin": "OWNER2/REPO",
|
|
},
|
|
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
|
httpmock.StringResponse(featureEnabledPayload),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.GraphQLQuery(`{"data":{"repository":{"hasIssuesEnabled":true,"issue":{"id": "SOMEID","number":123,"title":"my issue"}}}}`,
|
|
func(_ string, inputs map[string]interface{}) {
|
|
assert.Equal(t, "OWNER", inputs["owner"])
|
|
assert.Equal(t, "REPO", inputs["repo"])
|
|
assert.Equal(t, float64(123), inputs["number"])
|
|
}),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
|
httpmock.GraphQLQuery(`{"data":{"repository":{"id":"REPOID","defaultBranchRef":{"target":{"oid":"DEFAULTOID"}},"ref":{"target":{"oid":""}}}}}`,
|
|
func(_ string, inputs map[string]interface{}) {
|
|
assert.Equal(t, "OWNER2", inputs["owner"])
|
|
assert.Equal(t, "REPO", inputs["name"])
|
|
}),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation CreateLinkedBranch\b`),
|
|
httpmock.GraphQLMutation(`{"data":{"createLinkedBranch":{"linkedBranch":{"id":"2","ref":{"name":"my-issue-1"}}}}}`,
|
|
func(inputs map[string]interface{}) {
|
|
assert.Equal(t, "REPOID", inputs["repositoryId"])
|
|
assert.Equal(t, "SOMEID", inputs["issueId"])
|
|
assert.Equal(t, "DEFAULTOID", inputs["oid"])
|
|
}),
|
|
)
|
|
},
|
|
runStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git fetch origin \+refs/heads/my-issue-1:refs/remotes/origin/my-issue-1`, 0, "")
|
|
},
|
|
expectedOut: "github.com/OWNER2/REPO/tree/my-issue-1\n",
|
|
},
|
|
{
|
|
name: "develop new branch with name and base specified",
|
|
opts: &DevelopOptions{
|
|
Name: "my-branch",
|
|
BaseBranch: "main",
|
|
IssueNumber: 123,
|
|
},
|
|
remotes: map[string]string{
|
|
"origin": "OWNER/REPO",
|
|
},
|
|
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
|
httpmock.StringResponse(featureEnabledPayload),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled":true,"issue":{"id":"SOMEID","number":123,"title":"my issue"}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","ref":{"target":{"oid":"OID"}}}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`query ListLinkedBranches\b`),
|
|
httpmock.GraphQLQuery(`
|
|
{"data":{"repository":{"issue":{"linkedBranches":{"nodes":[]}}}}}
|
|
`, func(query string, inputs map[string]interface{}) {
|
|
assert.Equal(t, float64(123), inputs["number"])
|
|
assert.Equal(t, "OWNER", inputs["owner"])
|
|
assert.Equal(t, "REPO", inputs["name"])
|
|
}),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation CreateLinkedBranch\b`),
|
|
httpmock.GraphQLMutation(`{"data":{"createLinkedBranch":{"linkedBranch":{"id":"2","ref":{"name":"my-branch"}}}}}`,
|
|
func(inputs map[string]interface{}) {
|
|
assert.Equal(t, "REPOID", inputs["repositoryId"])
|
|
assert.Equal(t, "SOMEID", inputs["issueId"])
|
|
assert.Equal(t, "OID", inputs["oid"])
|
|
assert.Equal(t, "my-branch", inputs["name"])
|
|
}),
|
|
)
|
|
},
|
|
runStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git fetch origin \+refs/heads/my-branch:refs/remotes/origin/my-branch`, 0, "")
|
|
cs.Register(`git config branch\.my-branch\.gh-merge-base main`, 0, "")
|
|
},
|
|
expectedOut: "github.com/OWNER/REPO/tree/my-branch\n",
|
|
},
|
|
{
|
|
name: "develop existing linked branch with name and checkout",
|
|
opts: &DevelopOptions{
|
|
Name: "my-branch",
|
|
BaseBranch: "main",
|
|
IssueNumber: 123,
|
|
Checkout: true,
|
|
},
|
|
remotes: map[string]string{
|
|
"origin": "OWNER/REPO",
|
|
},
|
|
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
|
httpmock.StringResponse(featureEnabledPayload),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled":true,"issue":{"id":"SOMEID","number":123,"title":"my issue"}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query ListLinkedBranches\b`),
|
|
httpmock.GraphQLQuery(`
|
|
{"data":{"repository":{"issue":{"linkedBranches":{"nodes":[{"ref":{"name":"my-branch","repository":{"url":"https://github.com/OWNER/REPO"}}}]}}}}}
|
|
`, func(query string, inputs map[string]interface{}) {
|
|
assert.Equal(t, float64(123), inputs["number"])
|
|
assert.Equal(t, "OWNER", inputs["owner"])
|
|
assert.Equal(t, "REPO", inputs["name"])
|
|
}),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","ref":{"target":{"oid":"OID"}}}}}`))
|
|
},
|
|
runStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git config branch\.my-branch\.gh-merge-base main`, 0, "")
|
|
cs.Register(`git fetch origin \+refs/heads/my-branch:refs/remotes/origin/my-branch`, 0, "")
|
|
cs.Register(`git rev-parse --verify refs/heads/my-branch`, 0, "")
|
|
cs.Register(`git checkout my-branch`, 0, "")
|
|
cs.Register(`git pull --ff-only origin my-branch`, 0, "")
|
|
},
|
|
expectedOut: "github.com/OWNER/REPO/tree/my-branch\n",
|
|
},
|
|
{
|
|
name: "develop existing linked branch with name in tty shows reuse message",
|
|
opts: &DevelopOptions{
|
|
Name: "my-branch",
|
|
BaseBranch: "main",
|
|
IssueNumber: 123,
|
|
},
|
|
tty: true,
|
|
remotes: map[string]string{
|
|
"origin": "OWNER/REPO",
|
|
},
|
|
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
|
httpmock.StringResponse(featureEnabledPayload),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled":true,"issue":{"id":"SOMEID","number":123,"title":"my issue"}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query ListLinkedBranches\b`),
|
|
httpmock.GraphQLQuery(`
|
|
{"data":{"repository":{"issue":{"linkedBranches":{"nodes":[{"ref":{"name":"my-branch","repository":{"url":"https://github.com/OWNER/REPO"}}}]}}}}}
|
|
`, func(query string, inputs map[string]interface{}) {
|
|
assert.Equal(t, float64(123), inputs["number"])
|
|
assert.Equal(t, "OWNER", inputs["owner"])
|
|
assert.Equal(t, "REPO", inputs["name"])
|
|
}),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","ref":{"target":{"oid":"OID"}}}}}`))
|
|
},
|
|
runStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git config branch\.my-branch\.gh-merge-base main`, 0, "")
|
|
cs.Register(`git fetch origin \+refs/heads/my-branch:refs/remotes/origin/my-branch`, 0, "")
|
|
},
|
|
expectedOut: "github.com/OWNER/REPO/tree/my-branch\n",
|
|
expectedErrOut: "Using existing linked branch \"my-branch\"\n",
|
|
},
|
|
{
|
|
name: "develop existing linked branch with invalid base branch returns an error",
|
|
opts: &DevelopOptions{
|
|
Name: "my-branch",
|
|
BaseBranch: "does-not-exist-branch",
|
|
IssueNumber: 123,
|
|
},
|
|
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
|
httpmock.StringResponse(featureEnabledPayload),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled":true,"issue":{"id":"SOMEID","number":123,"title":"my issue"}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query ListLinkedBranches\b`),
|
|
httpmock.GraphQLQuery(`
|
|
{"data":{"repository":{"issue":{"linkedBranches":{"nodes":[{"ref":{"name":"my-branch","repository":{"url":"https://github.com/OWNER/REPO"}}}]}}}}}
|
|
`, func(query string, inputs map[string]interface{}) {
|
|
assert.Equal(t, float64(123), inputs["number"])
|
|
assert.Equal(t, "OWNER", inputs["owner"])
|
|
assert.Equal(t, "REPO", inputs["name"])
|
|
}),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","defaultBranchRef":{"target":{"oid":"DEFAULTOID"}},"ref":null}}}`),
|
|
)
|
|
},
|
|
wantErr: `could not find branch "does-not-exist-branch" in OWNER/REPO`,
|
|
},
|
|
{
|
|
name: "develop with empty linked branch name response returns an error",
|
|
opts: &DevelopOptions{
|
|
Name: "my-branch",
|
|
BaseBranch: "main",
|
|
IssueNumber: 123,
|
|
},
|
|
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
|
httpmock.StringResponse(featureEnabledPayload),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled":true,"issue":{"id":"SOMEID","number":123,"title":"my issue"}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query ListLinkedBranches\b`),
|
|
httpmock.GraphQLQuery(`
|
|
{"data":{"repository":{"issue":{"linkedBranches":{"nodes":[]}}}}}
|
|
`, func(query string, inputs map[string]interface{}) {
|
|
assert.Equal(t, float64(123), inputs["number"])
|
|
assert.Equal(t, "OWNER", inputs["owner"])
|
|
assert.Equal(t, "REPO", inputs["name"])
|
|
}),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","ref":{"target":{"oid":"OID"}}}}}`))
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation CreateLinkedBranch\b`),
|
|
httpmock.GraphQLMutation(`{"data":{"createLinkedBranch":{"linkedBranch":{"id":"2","ref":{"name":""}}}}}`,
|
|
func(inputs map[string]interface{}) {
|
|
assert.Equal(t, "REPOID", inputs["repositoryId"])
|
|
assert.Equal(t, "SOMEID", inputs["issueId"])
|
|
assert.Equal(t, "OID", inputs["oid"])
|
|
assert.Equal(t, "my-branch", inputs["name"])
|
|
}),
|
|
)
|
|
},
|
|
wantErr: "failed to create linked branch: API returned empty branch name",
|
|
},
|
|
{
|
|
name: "develop new branch outside of local git repo",
|
|
opts: &DevelopOptions{
|
|
IssueNumber: 123,
|
|
BaseRepo: func() (ghrepo.Interface, error) {
|
|
return ghrepo.New("cli", "cli"), nil
|
|
},
|
|
},
|
|
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
|
httpmock.StringResponse(featureEnabledPayload),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"hasIssuesEnabled":true,"issue":{"id": "SOMEID","number":123,"title":"my issue"}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","defaultBranchRef":{"target":{"oid":"DEFAULTOID"}},"ref":{"target":{"oid":""}}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation CreateLinkedBranch\b`),
|
|
httpmock.GraphQLMutation(`{"data":{"createLinkedBranch":{"linkedBranch":{"id":"2","ref":{"name":"my-issue-1"}}}}}`,
|
|
func(inputs map[string]interface{}) {
|
|
assert.Equal(t, "REPOID", inputs["repositoryId"])
|
|
assert.Equal(t, "SOMEID", inputs["issueId"])
|
|
assert.Equal(t, "DEFAULTOID", inputs["oid"])
|
|
}),
|
|
)
|
|
},
|
|
expectedOut: "github.com/cli/cli/tree/my-issue-1\n",
|
|
},
|
|
{
|
|
name: "develop new branch with checkout when local branch exists",
|
|
opts: &DevelopOptions{
|
|
Name: "my-branch",
|
|
IssueNumber: 123,
|
|
Checkout: true,
|
|
},
|
|
remotes: map[string]string{
|
|
"origin": "OWNER/REPO",
|
|
},
|
|
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
|
httpmock.StringResponse(featureEnabledPayload),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"hasIssuesEnabled":true,"issue":{"id": "SOMEID","number":123,"title":"my issue"}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","ref":{"target":{"oid":"OID"}}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query ListLinkedBranches\b`),
|
|
httpmock.GraphQLQuery(`
|
|
{"data":{"repository":{"issue":{"linkedBranches":{"nodes":[]}}}}}
|
|
`, func(query string, inputs map[string]interface{}) {
|
|
assert.Equal(t, float64(123), inputs["number"])
|
|
assert.Equal(t, "OWNER", inputs["owner"])
|
|
assert.Equal(t, "REPO", inputs["name"])
|
|
}),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation CreateLinkedBranch\b`),
|
|
httpmock.GraphQLMutation(`{"data":{"createLinkedBranch":{"linkedBranch":{"id":"2","ref":{"name":"my-branch"}}}}}`,
|
|
func(inputs map[string]interface{}) {
|
|
assert.Equal(t, "REPOID", inputs["repositoryId"])
|
|
assert.Equal(t, "SOMEID", inputs["issueId"])
|
|
assert.Equal(t, "OID", inputs["oid"])
|
|
assert.Equal(t, "my-branch", inputs["name"])
|
|
}),
|
|
)
|
|
},
|
|
runStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git fetch origin \+refs/heads/my-branch:refs/remotes/origin/my-branch`, 0, "")
|
|
cs.Register(`git rev-parse --verify refs/heads/my-branch`, 0, "")
|
|
cs.Register(`git checkout my-branch`, 0, "")
|
|
cs.Register(`git pull --ff-only origin my-branch`, 0, "")
|
|
},
|
|
expectedOut: "github.com/OWNER/REPO/tree/my-branch\n",
|
|
},
|
|
{
|
|
name: "develop new branch with checkout when local branch does not exist",
|
|
opts: &DevelopOptions{
|
|
Name: "my-branch",
|
|
IssueNumber: 123,
|
|
Checkout: true,
|
|
},
|
|
remotes: map[string]string{
|
|
"origin": "OWNER/REPO",
|
|
},
|
|
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
|
httpmock.StringResponse(featureEnabledPayload),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"hasIssuesEnabled":true,"issue":{"id": "SOMEID","number":123,"title":"my issue"}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","ref":{"target":{"oid":"OID"}}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query ListLinkedBranches\b`),
|
|
httpmock.GraphQLQuery(`
|
|
{"data":{"repository":{"issue":{"linkedBranches":{"nodes":[]}}}}}
|
|
`, func(query string, inputs map[string]interface{}) {
|
|
assert.Equal(t, float64(123), inputs["number"])
|
|
assert.Equal(t, "OWNER", inputs["owner"])
|
|
assert.Equal(t, "REPO", inputs["name"])
|
|
}),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`mutation CreateLinkedBranch\b`),
|
|
httpmock.GraphQLMutation(`{"data":{"createLinkedBranch":{"linkedBranch":{"id":"2","ref":{"name":"my-branch"}}}}}`,
|
|
func(inputs map[string]interface{}) {
|
|
assert.Equal(t, "REPOID", inputs["repositoryId"])
|
|
assert.Equal(t, "SOMEID", inputs["issueId"])
|
|
assert.Equal(t, "OID", inputs["oid"])
|
|
assert.Equal(t, "my-branch", inputs["name"])
|
|
}),
|
|
)
|
|
},
|
|
runStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git fetch origin \+refs/heads/my-branch:refs/remotes/origin/my-branch`, 0, "")
|
|
cs.Register(`git rev-parse --verify refs/heads/my-branch`, 1, "")
|
|
cs.Register(`git checkout -b my-branch --track origin/my-branch`, 0, "")
|
|
},
|
|
expectedOut: "github.com/OWNER/REPO/tree/my-branch\n",
|
|
},
|
|
{
|
|
name: "develop with base branch which does not exist",
|
|
opts: &DevelopOptions{
|
|
IssueNumber: 123,
|
|
BaseBranch: "does-not-exist-branch",
|
|
},
|
|
remotes: map[string]string{
|
|
"origin": "OWNER/REPO",
|
|
},
|
|
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
|
reg.Register(
|
|
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
|
httpmock.StringResponse(featureEnabledPayload),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query IssueByNumber\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"hasIssuesEnabled":true,"issue":{"id": "SOMEID","number":123,"title":"my issue"}}}}`),
|
|
)
|
|
reg.Register(
|
|
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
|
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","defaultBranchRef":{"target":{"oid":"DEFAULTOID"}},"ref":null}}}`),
|
|
)
|
|
},
|
|
wantErr: "could not find branch \"does-not-exist-branch\" in OWNER/REPO",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
opts := tt.opts
|
|
|
|
reg := &httpmock.Registry{}
|
|
defer reg.Verify(t)
|
|
if tt.httpStubs != nil {
|
|
tt.httpStubs(reg, t)
|
|
}
|
|
opts.HttpClient = func() (*http.Client, error) {
|
|
return &http.Client{Transport: reg}, nil
|
|
}
|
|
|
|
ios, _, stdout, stderr := iostreams.Test()
|
|
ios.SetStdoutTTY(tt.tty)
|
|
ios.SetStdinTTY(tt.tty)
|
|
ios.SetStderrTTY(tt.tty)
|
|
opts.IO = ios
|
|
|
|
if opts.BaseRepo == nil {
|
|
opts.BaseRepo = func() (ghrepo.Interface, error) {
|
|
return ghrepo.New("OWNER", "REPO"), nil
|
|
}
|
|
}
|
|
|
|
opts.Remotes = func() (context.Remotes, error) {
|
|
if len(tt.remotes) == 0 {
|
|
return nil, errors.New("no remotes")
|
|
}
|
|
var remotes context.Remotes
|
|
for name, repo := range tt.remotes {
|
|
r, err := ghrepo.FromFullName(repo)
|
|
if err != nil {
|
|
return remotes, err
|
|
}
|
|
remotes = append(remotes, &context.Remote{
|
|
Remote: &git.Remote{Name: name},
|
|
Repo: r,
|
|
})
|
|
}
|
|
return remotes, nil
|
|
}
|
|
|
|
opts.GitClient = &git.Client{
|
|
GhPath: "some/path/gh",
|
|
GitPath: "some/path/git",
|
|
}
|
|
|
|
cmdStubs, cmdTeardown := run.Stub()
|
|
defer cmdTeardown(t)
|
|
if tt.runStubs != nil {
|
|
tt.runStubs(cmdStubs)
|
|
}
|
|
|
|
err := developRun(opts)
|
|
if tt.wantErr != "" {
|
|
assert.EqualError(t, err, tt.wantErr)
|
|
return
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.expectedOut, stdout.String())
|
|
assert.Equal(t, tt.expectedErrOut, stderr.String())
|
|
}
|
|
})
|
|
}
|
|
}
|