package create import ( "bytes" "encoding/json" "io/ioutil" "net/http" "testing" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/internal/run" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" "github.com/cli/cli/v2/pkg/prompt" "github.com/cli/cli/v2/test" "github.com/google/shlex" "github.com/stretchr/testify/assert" ) func runCommand(httpClient *http.Client, cli string, isTTY bool) (*test.CmdOut, error) { io, _, stdout, stderr := iostreams.Test() io.SetStdoutTTY(isTTY) io.SetStdinTTY(isTTY) fac := &cmdutil.Factory{ IOStreams: io, HttpClient: func() (*http.Client, error) { return httpClient, nil }, Config: func() (config.Config, error) { return config.NewBlankConfig(), nil }, } cmd := NewCmdCreate(fac, nil) // TODO STUPID HACK // cobra aggressively adds help to all commands. since we're not running through the root command // (which manages help when running for real) and since create has a '-h' flag (for homepage), // cobra blows up when it tried to add a help flag and -h is already in use. This hack adds a // dummy help flag with a random shorthand to get around this. cmd.Flags().BoolP("help", "x", false, "") argv, err := shlex.Split(cli) cmd.SetArgs(argv) cmd.SetIn(&bytes.Buffer{}) cmd.SetOut(&bytes.Buffer{}) cmd.SetErr(&bytes.Buffer{}) if err != nil { panic(err) } _, err = cmd.ExecuteC() if err != nil { return nil, err } return &test.CmdOut{ OutBuf: stdout, ErrBuf: stderr}, nil } func TestRepoCreate(t *testing.T) { reg := &httpmock.Registry{} reg.Register( httpmock.GraphQL(`mutation RepositoryCreate\b`), httpmock.StringResponse(` { "data": { "createRepository": { "repository": { "id": "REPOID", "url": "https://github.com/OWNER/REPO", "name": "REPO", "owner": { "login": "OWNER" } } } } }`)) httpClient := &http.Client{Transport: reg} cs, cmdTeardown := run.Stub() defer cmdTeardown(t) cs.Register(`git remote add -f origin https://github\.com/OWNER/REPO\.git`, 0, "") cs.Register(`git rev-parse --show-toplevel`, 0, "") as, surveyTearDown := prompt.InitAskStubber() defer surveyTearDown() as.Stub([]*prompt.QuestionStub{ { Name: "repoVisibility", Value: "PRIVATE", }, }) as.Stub([]*prompt.QuestionStub{ { Name: "addGitIgnore", Value: false, }, }) as.Stub([]*prompt.QuestionStub{ { Name: "addLicense", Value: false, }, }) as.Stub([]*prompt.QuestionStub{ { Name: "confirmSubmit", Value: true, }, }) output, err := runCommand(httpClient, "REPO", true) if err != nil { t.Errorf("error running command `repo create`: %v", err) } assert.Equal(t, "", output.String()) assert.Equal(t, "✓ Created repository OWNER/REPO on GitHub\n✓ Added remote https://github.com/OWNER/REPO.git\n", output.Stderr()) var reqBody struct { Query string Variables struct { Input map[string]interface{} } } if len(reg.Requests) != 1 { t.Fatalf("expected 1 HTTP request, got %d", len(reg.Requests)) } bodyBytes, _ := ioutil.ReadAll(reg.Requests[0].Body) _ = json.Unmarshal(bodyBytes, &reqBody) if repoName := reqBody.Variables.Input["name"].(string); repoName != "REPO" { t.Errorf("expected %q, got %q", "REPO", repoName) } if repoVisibility := reqBody.Variables.Input["visibility"].(string); repoVisibility != "PRIVATE" { t.Errorf("expected %q, got %q", "PRIVATE", repoVisibility) } if _, ownerSet := reqBody.Variables.Input["ownerId"]; ownerSet { t.Error("expected ownerId not to be set") } } func TestRepoCreate_outsideGitWorkDir(t *testing.T) { reg := &httpmock.Registry{} reg.Register( httpmock.GraphQL(`mutation RepositoryCreate\b`), httpmock.StringResponse(` { "data": { "createRepository": { "repository": { "id": "REPOID", "url": "https://github.com/OWNER/REPO", "name": "REPO", "owner": { "login": "OWNER" } } } } }`)) httpClient := &http.Client{Transport: reg} cs, cmdTeardown := run.Stub() defer cmdTeardown(t) cs.Register(`git rev-parse --show-toplevel`, 1, "") cs.Register(`git init REPO`, 0, "") cs.Register(`git -C REPO remote add origin https://github\.com/OWNER/REPO\.git`, 0, "") output, err := runCommand(httpClient, "REPO --private --confirm", false) if err != nil { t.Errorf("error running command `repo create`: %v", err) } assert.Equal(t, "https://github.com/OWNER/REPO\n", output.String()) assert.Equal(t, "", output.Stderr()) var reqBody struct { Query string Variables struct { Input map[string]interface{} } } if len(reg.Requests) != 1 { t.Fatalf("expected 1 HTTP request, got %d", len(reg.Requests)) } bodyBytes, _ := ioutil.ReadAll(reg.Requests[0].Body) _ = json.Unmarshal(bodyBytes, &reqBody) if repoName := reqBody.Variables.Input["name"].(string); repoName != "REPO" { t.Errorf("expected %q, got %q", "REPO", repoName) } if repoVisibility := reqBody.Variables.Input["visibility"].(string); repoVisibility != "PRIVATE" { t.Errorf("expected %q, got %q", "PRIVATE", repoVisibility) } if _, ownerSet := reqBody.Variables.Input["ownerId"]; ownerSet { t.Error("expected ownerId not to be set") } } func TestRepoCreate_org(t *testing.T) { reg := &httpmock.Registry{} reg.Register( httpmock.REST("GET", "users/ORG"), httpmock.StringResponse(` { "node_id": "ORGID" }`)) reg.Register( httpmock.GraphQL(`mutation RepositoryCreate\b`), httpmock.StringResponse(` { "data": { "createRepository": { "repository": { "id": "REPOID", "url": "https://github.com/ORG/REPO", "name": "REPO", "owner": { "login": "ORG" } } } } }`)) httpClient := &http.Client{Transport: reg} cs, cmdTeardown := run.Stub() defer cmdTeardown(t) cs.Register(`git remote add -f origin https://github\.com/ORG/REPO\.git`, 0, "") cs.Register(`git rev-parse --show-toplevel`, 0, "") as, surveyTearDown := prompt.InitAskStubber() defer surveyTearDown() as.Stub([]*prompt.QuestionStub{ { Name: "repoVisibility", Value: "PRIVATE", }, }) as.Stub([]*prompt.QuestionStub{ { Name: "addGitIgnore", Value: false, }, }) as.Stub([]*prompt.QuestionStub{ { Name: "addLicense", Value: false, }, }) as.Stub([]*prompt.QuestionStub{ { Name: "confirmSubmit", Value: true, }, }) output, err := runCommand(httpClient, "ORG/REPO", true) if err != nil { t.Errorf("error running command `repo create`: %v", err) } assert.Equal(t, "", output.String()) assert.Equal(t, "✓ Created repository ORG/REPO on GitHub\n✓ Added remote https://github.com/ORG/REPO.git\n", output.Stderr()) var reqBody struct { Query string Variables struct { Input map[string]interface{} } } if len(reg.Requests) != 2 { t.Fatalf("expected 2 HTTP requests, got %d", len(reg.Requests)) } assert.Equal(t, "/users/ORG", reg.Requests[0].URL.Path) bodyBytes, _ := ioutil.ReadAll(reg.Requests[1].Body) _ = json.Unmarshal(bodyBytes, &reqBody) if orgID := reqBody.Variables.Input["ownerId"].(string); orgID != "ORGID" { t.Errorf("expected %q, got %q", "ORGID", orgID) } if _, teamSet := reqBody.Variables.Input["teamId"]; teamSet { t.Error("expected teamId not to be set") } } func TestRepoCreate_orgWithTeam(t *testing.T) { reg := &httpmock.Registry{} reg.Register( httpmock.REST("GET", "orgs/ORG/teams/monkeys"), httpmock.StringResponse(` { "node_id": "TEAMID", "organization": { "node_id": "ORGID" } }`)) reg.Register( httpmock.GraphQL(`mutation RepositoryCreate\b`), httpmock.StringResponse(` { "data": { "createRepository": { "repository": { "id": "REPOID", "url": "https://github.com/ORG/REPO", "name": "REPO", "owner": { "login": "ORG" } } } } }`)) httpClient := &http.Client{Transport: reg} cs, cmdTeardown := run.Stub() defer cmdTeardown(t) cs.Register(`git remote add -f origin https://github\.com/ORG/REPO\.git`, 0, "") cs.Register(`git rev-parse --show-toplevel`, 0, "") as, surveyTearDown := prompt.InitAskStubber() defer surveyTearDown() as.Stub([]*prompt.QuestionStub{ { Name: "repoVisibility", Value: "PRIVATE", }, }) as.Stub([]*prompt.QuestionStub{ { Name: "addGitIgnore", Value: false, }, }) as.Stub([]*prompt.QuestionStub{ { Name: "addLicense", Value: false, }, }) as.Stub([]*prompt.QuestionStub{ { Name: "confirmSubmit", Value: true, }, }) output, err := runCommand(httpClient, "ORG/REPO --team monkeys", true) if err != nil { t.Errorf("error running command `repo create`: %v", err) } assert.Equal(t, "", output.String()) assert.Equal(t, "✓ Created repository ORG/REPO on GitHub\n✓ Added remote https://github.com/ORG/REPO.git\n", output.Stderr()) var reqBody struct { Query string Variables struct { Input map[string]interface{} } } if len(reg.Requests) != 2 { t.Fatalf("expected 2 HTTP requests, got %d", len(reg.Requests)) } assert.Equal(t, "/orgs/ORG/teams/monkeys", reg.Requests[0].URL.Path) bodyBytes, _ := ioutil.ReadAll(reg.Requests[1].Body) _ = json.Unmarshal(bodyBytes, &reqBody) if orgID := reqBody.Variables.Input["ownerId"].(string); orgID != "ORGID" { t.Errorf("expected %q, got %q", "ORGID", orgID) } if teamID := reqBody.Variables.Input["teamId"].(string); teamID != "TEAMID" { t.Errorf("expected %q, got %q", "TEAMID", teamID) } } func TestRepoCreate_template(t *testing.T) { reg := &httpmock.Registry{} defer reg.Verify(t) reg.Register( httpmock.GraphQL(`mutation CloneTemplateRepository\b`), httpmock.StringResponse(` { "data": { "cloneTemplateRepository": { "repository": { "id": "REPOID", "name": "REPO", "owner": { "login": "OWNER" }, "url": "https://github.com/OWNER/REPO" } } } }`)) reg.StubRepoInfoResponse("OWNER", "REPO", "main") reg.Register( httpmock.GraphQL(`query UserCurrent\b`), httpmock.StringResponse(`{"data":{"viewer":{"ID":"OWNERID"}}}`)) httpClient := &http.Client{Transport: reg} cs, cmdTeardown := run.Stub() defer cmdTeardown(t) cs.Register(`git rev-parse --show-toplevel`, 1, "") cs.Register(`git init REPO`, 0, "") cs.Register(`git -C REPO remote add`, 0, "") cs.Register(`git -C REPO fetch origin \+refs/heads/main:refs/remotes/origin/main`, 0, "") cs.Register(`git -C REPO checkout main`, 0, "") _, surveyTearDown := prompt.InitAskStubber() defer surveyTearDown() output, err := runCommand(httpClient, "REPO -y --private --template='OWNER/REPO'", true) if err != nil { t.Errorf("error running command `repo create`: %v", err) return } assert.Equal(t, "", output.String()) assert.Equal(t, heredoc.Doc(` ✓ Created repository OWNER/REPO on GitHub ✓ Initialized repository in "REPO" `), output.Stderr()) var reqBody struct { Query string Variables struct { Input map[string]interface{} } } bodyBytes, _ := ioutil.ReadAll(reg.Requests[2].Body) _ = json.Unmarshal(bodyBytes, &reqBody) if repoName := reqBody.Variables.Input["name"].(string); repoName != "REPO" { t.Errorf("expected %q, got %q", "REPO", repoName) } if repoVisibility := reqBody.Variables.Input["visibility"].(string); repoVisibility != "PRIVATE" { t.Errorf("expected %q, got %q", "PRIVATE", repoVisibility) } if ownerId := reqBody.Variables.Input["ownerId"].(string); ownerId != "OWNERID" { t.Errorf("expected %q, got %q", "OWNERID", ownerId) } } func TestRepoCreate_withoutNameArg(t *testing.T) { reg := &httpmock.Registry{} reg.Register( httpmock.REST("GET", "users/OWNER"), httpmock.StringResponse(`{ "node_id": "OWNERID" }`)) reg.Register( httpmock.GraphQL(`mutation RepositoryCreate\b`), httpmock.StringResponse(` { "data": { "createRepository": { "repository": { "id": "REPOID", "url": "https://github.com/OWNER/REPO", "name": "REPO", "owner": { "login": "OWNER" } } } } }`)) httpClient := &http.Client{Transport: reg} cs, cmdTeardown := run.Stub() defer cmdTeardown(t) cs.Register(`git remote add -f origin https://github\.com/OWNER/REPO\.git`, 0, "") cs.Register(`git rev-parse --show-toplevel`, 0, "") as, surveyTearDown := prompt.InitAskStubber() defer surveyTearDown() as.Stub([]*prompt.QuestionStub{ { Name: "repoName", Value: "OWNER/REPO", }, { Name: "repoDescription", Value: "DESCRIPTION", }, { Name: "repoVisibility", Value: "PRIVATE", }, }) as.Stub([]*prompt.QuestionStub{ { Name: "confirmSubmit", Value: true, }, }) output, err := runCommand(httpClient, "", true) if err != nil { t.Errorf("error running command `repo create`: %v", err) } assert.Equal(t, "", output.String()) assert.Equal(t, "✓ Created repository OWNER/REPO on GitHub\n✓ Added remote https://github.com/OWNER/REPO.git\n", output.Stderr()) var reqBody struct { Query string Variables struct { Input map[string]interface{} } } if len(reg.Requests) != 2 { t.Fatalf("expected 2 HTTP request, got %d", len(reg.Requests)) } bodyBytes, _ := ioutil.ReadAll(reg.Requests[1].Body) _ = json.Unmarshal(bodyBytes, &reqBody) if repoName := reqBody.Variables.Input["name"].(string); repoName != "REPO" { t.Errorf("expected %q, got %q", "REPO", repoName) } if repoVisibility := reqBody.Variables.Input["visibility"].(string); repoVisibility != "PRIVATE" { t.Errorf("expected %q, got %q", "PRIVATE", repoVisibility) } if ownerId := reqBody.Variables.Input["ownerId"].(string); ownerId != "OWNERID" { t.Errorf("expected %q, got %q", "OWNERID", ownerId) } } func TestRepoCreate_WithGitIgnore(t *testing.T) { cs, cmdTeardown := run.Stub() defer cmdTeardown(t) cs.Register(`git remote add -f origin https://github\.com/OWNER/REPO\.git`, 0, "") cs.Register(`git rev-parse --show-toplevel`, 0, "") as, surveyTearDown := prompt.InitAskStubber() defer surveyTearDown() as.Stub([]*prompt.QuestionStub{ { Name: "repoVisibility", Value: "PRIVATE", }, }) as.Stub([]*prompt.QuestionStub{ { Name: "addGitIgnore", Value: true, }, }) as.Stub([]*prompt.QuestionStub{ { Name: "chooseGitIgnore", Value: "Go", }, }) as.Stub([]*prompt.QuestionStub{ { Name: "addLicense", Value: false, }, }) as.Stub([]*prompt.QuestionStub{ { Name: "confirmSubmit", Value: true, }, }) reg := &httpmock.Registry{} reg.Register( httpmock.REST("GET", "users/OWNER"), httpmock.StringResponse(`{ "node_id": "OWNERID" }`)) reg.Register( httpmock.REST("GET", "gitignore/templates"), httpmock.StringResponse(`["Actionscript","Android","AppceleratorTitanium","Autotools","Bancha","C","C++","Go"]`)) reg.Register( httpmock.REST("POST", "user/repos"), httpmock.StringResponse(`{"name":"REPO", "owner":{"login": "OWNER"}, "html_url":"https://github.com/OWNER/REPO"}`)) httpClient := &http.Client{Transport: reg} output, err := runCommand(httpClient, "OWNER/REPO", true) if err != nil { t.Errorf("error running command `repo create`: %v", err) } assert.Equal(t, "", output.String()) assert.Equal(t, "✓ Created repository OWNER/REPO on GitHub\n✓ Added remote https://github.com/OWNER/REPO.git\n", output.Stderr()) if len(reg.Requests) != 3 { t.Fatalf("expected 3 HTTP request, got %d", len(reg.Requests)) } reqBody := make(map[string]interface{}) dec := json.NewDecoder(reg.Requests[2].Body) assert.NoError(t, dec.Decode(&reqBody)) if gitignore := reqBody["gitignore_template"]; gitignore != "Go" { t.Errorf("expected %q, got %q", "Go", gitignore) } if license := reqBody["license_template"]; license != nil { t.Errorf("expected %v, got %v", nil, license) } } func TestRepoCreate_WithBothGitIgnoreLicense(t *testing.T) { cs, cmdTeardown := run.Stub() defer cmdTeardown(t) cs.Register(`git remote add -f origin https://github\.com/OWNER/REPO\.git`, 0, "") cs.Register(`git rev-parse --show-toplevel`, 0, "") as, surveyTearDown := prompt.InitAskStubber() defer surveyTearDown() as.Stub([]*prompt.QuestionStub{ { Name: "repoVisibility", Value: "PRIVATE", }, }) as.Stub([]*prompt.QuestionStub{ { Name: "addGitIgnore", Value: true, }, }) as.Stub([]*prompt.QuestionStub{ { Name: "chooseGitIgnore", Value: "Go", }, }) as.Stub([]*prompt.QuestionStub{ { Name: "addLicense", Value: true, }, }) as.Stub([]*prompt.QuestionStub{ { Name: "chooseLicense", Value: "GNU Lesser General Public License v3.0", }, }) as.Stub([]*prompt.QuestionStub{ { Name: "confirmSubmit", Value: true, }, }) reg := &httpmock.Registry{} reg.Register( httpmock.REST("GET", "users/OWNER"), httpmock.StringResponse(`{ "node_id": "OWNERID" }`)) reg.Register( httpmock.REST("GET", "gitignore/templates"), httpmock.StringResponse(`["Actionscript","Android","AppceleratorTitanium","Autotools","Bancha","C","C++","Go"]`)) reg.Register( httpmock.REST("GET", "licenses"), httpmock.StringResponse(`[{"key": "mit","name": "MIT License"},{"key": "lgpl-3.0","name": "GNU Lesser General Public License v3.0"}]`)) reg.Register( httpmock.REST("POST", "user/repos"), httpmock.StringResponse(`{"name":"REPO", "owner":{"login": "OWNER"}, "html_url":"https://github.com/OWNER/REPO"}`)) httpClient := &http.Client{Transport: reg} output, err := runCommand(httpClient, "OWNER/REPO", true) if err != nil { t.Errorf("error running command `repo create`: %v", err) } assert.Equal(t, "", output.String()) assert.Equal(t, "✓ Created repository OWNER/REPO on GitHub\n✓ Added remote https://github.com/OWNER/REPO.git\n", output.Stderr()) if len(reg.Requests) != 4 { t.Fatalf("expected 4 HTTP request, got %d", len(reg.Requests)) } reqBody := make(map[string]interface{}) dec := json.NewDecoder(reg.Requests[3].Body) assert.NoError(t, dec.Decode(&reqBody)) if gitignore := reqBody["gitignore_template"]; gitignore != "Go" { t.Errorf("expected %q, got %q", "Go", gitignore) } if license := reqBody["license_template"]; license != "lgpl-3.0" { t.Errorf("expected %q, got %q", "lgpl-3.0", license) } } func TestRepoCreate_WithConfirmFlag(t *testing.T) { cs, cmdTeardown := run.Stub() defer cmdTeardown(t) cs.Register(`git remote add -f origin https://github\.com/OWNER/REPO\.git`, 0, "") cs.Register(`git rev-parse --show-toplevel`, 0, "") reg := &httpmock.Registry{} reg.Register( httpmock.GraphQL(`mutation RepositoryCreate\b`), httpmock.StringResponse(` { "data": { "createRepository": { "repository": { "id": "REPOID", "url": "https://github.com/OWNER/REPO", "name": "REPO", "owner": { "login": "OWNER" } } } } }`), ) reg.Register( httpmock.REST("GET", "users/OWNER"), httpmock.StringResponse(`{ "node_id": "OWNERID" }`), ) httpClient := &http.Client{Transport: reg} in := "OWNER/REPO --confirm --private" output, err := runCommand(httpClient, in, true) if err != nil { t.Errorf("error running command `repo create %v`: %v", in, err) } assert.Equal(t, "", output.String()) assert.Equal(t, "✓ Created repository OWNER/REPO on GitHub\n✓ Added remote https://github.com/OWNER/REPO.git\n", output.Stderr()) var reqBody struct { Query string Variables struct { Input map[string]interface{} } } if len(reg.Requests) != 2 { t.Fatalf("expected 2 HTTP request, got %d", len(reg.Requests)) } bodyBytes, _ := ioutil.ReadAll(reg.Requests[1].Body) _ = json.Unmarshal(bodyBytes, &reqBody) if repoName := reqBody.Variables.Input["name"].(string); repoName != "REPO" { t.Errorf("expected %q, got %q", "REPO", repoName) } if repoVisibility := reqBody.Variables.Input["visibility"].(string); repoVisibility != "PRIVATE" { t.Errorf("expected %q, got %q", "PRIVATE", repoVisibility) } }