package shared import ( "net/http" "strings" "testing" "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/pkg/httpmock" "github.com/stretchr/testify/assert" ) func TestIssueFromArgWithFields(t *testing.T) { type args struct { baseRepoFn func() (ghrepo.Interface, error) selector string } tests := []struct { name string args args httpStub func(*httpmock.Registry) wantIssue int wantRepo string wantProjects string wantErr bool }{ { name: "number argument", args: args{ selector: "13", baseRepoFn: func() (ghrepo.Interface, error) { return ghrepo.FromFullName("OWNER/REPO") }, }, httpStub: func(r *httpmock.Registry) { r.Register( httpmock.GraphQL(`query IssueByNumber\b`), httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled": true, "issue":{"number":13} }}}`)) }, wantIssue: 13, wantRepo: "https://github.com/OWNER/REPO", }, { name: "number with hash argument", args: args{ selector: "#13", baseRepoFn: func() (ghrepo.Interface, error) { return ghrepo.FromFullName("OWNER/REPO") }, }, httpStub: func(r *httpmock.Registry) { r.Register( httpmock.GraphQL(`query IssueByNumber\b`), httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled": true, "issue":{"number":13} }}}`)) }, wantIssue: 13, wantRepo: "https://github.com/OWNER/REPO", }, { name: "URL argument", args: args{ selector: "https://example.org/OWNER/REPO/issues/13#comment-123", baseRepoFn: nil, }, httpStub: func(r *httpmock.Registry) { r.Register( httpmock.GraphQL(`query IssueByNumber\b`), httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled": true, "issue":{"number":13} }}}`)) }, wantIssue: 13, wantRepo: "https://example.org/OWNER/REPO", }, { name: "project cards permission issue", args: args{ selector: "https://example.org/OWNER/REPO/issues/13", baseRepoFn: nil, }, httpStub: func(r *httpmock.Registry) { r.Register( httpmock.GraphQL(`query IssueByNumber\b`), httpmock.StringResponse(` { "data": { "repository": { "hasIssuesEnabled": true, "issue": { "number": 13, "projectCards": { "nodes": [ null, { "project": {"name": "myproject"}, "column": {"name": "To Do"} }, null, { "project": {"name": "other project"}, "column": null } ] } } } }, "errors": [ { "type": "FORBIDDEN", "message": "Resource not accessible by integration", "path": ["repository", "issue", "projectCards", "nodes", 0] }, { "type": "FORBIDDEN", "message": "Resource not accessible by integration", "path": ["repository", "issue", "projectCards", "nodes", 2] } ] }`)) }, wantErr: true, wantIssue: 13, wantProjects: "myproject, other project", wantRepo: "https://example.org/OWNER/REPO", }, { name: "projects permission issue", args: args{ selector: "https://example.org/OWNER/REPO/issues/13", baseRepoFn: nil, }, httpStub: func(r *httpmock.Registry) { r.Register( httpmock.GraphQL(`query IssueByNumber\b`), httpmock.StringResponse(` { "data": { "repository": { "hasIssuesEnabled": true, "issue": { "number": 13, "projectCards": { "nodes": null, "totalCount": 0 } } } }, "errors": [ { "type": "FORBIDDEN", "message": "Resource not accessible by integration", "path": ["repository", "issue", "projectCards", "nodes"] } ] }`)) }, wantErr: true, wantIssue: 13, wantProjects: "", wantRepo: "https://example.org/OWNER/REPO", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reg := &httpmock.Registry{} if tt.httpStub != nil { tt.httpStub(reg) } httpClient := &http.Client{Transport: reg} issue, repo, err := IssueFromArgWithFields(httpClient, tt.args.baseRepoFn, tt.args.selector, []string{"number"}) if (err != nil) != tt.wantErr { t.Errorf("IssueFromArgWithFields() error = %v, wantErr %v", err, tt.wantErr) if issue == nil { return } } if issue.Number != tt.wantIssue { t.Errorf("want issue #%d, got #%d", tt.wantIssue, issue.Number) } if gotProjects := strings.Join(issue.ProjectCards.ProjectNames(), ", "); gotProjects != tt.wantProjects { t.Errorf("want projects %q, got %q", tt.wantProjects, gotProjects) } repoURL := ghrepo.GenerateRepoURL(repo, "") if repoURL != tt.wantRepo { t.Errorf("want repo %s, got %s", tt.wantRepo, repoURL) } }) } } func TestIssuesFromArgsWithFields(t *testing.T) { type args struct { baseRepoFn func() (ghrepo.Interface, error) selectors []string } tests := []struct { name string args args httpStub func(*httpmock.Registry) wantIssues []int wantRepo string wantErr bool wantErrMsg string }{ { name: "multiple repos", args: args{ selectors: []string{"1", "https://github.com/OWNER/OTHERREPO/issues/2"}, baseRepoFn: func() (ghrepo.Interface, error) { return ghrepo.New("OWNER", "REPO"), nil }, }, httpStub: func(r *httpmock.Registry) { r.Register( httpmock.GraphQL(`query IssueByNumber\b`), httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled": true, "issue":{"number":1} }}}`)) r.Register( httpmock.GraphQL(`query IssueByNumber\b`), httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled": true, "issue":{"number":2} }}}`)) }, wantErr: true, wantErrMsg: "multiple issues must be in same repo", }, { name: "multiple issues", args: args{ selectors: []string{"1", "2"}, baseRepoFn: func() (ghrepo.Interface, error) { return ghrepo.New("OWNER", "REPO"), nil }, }, httpStub: func(r *httpmock.Registry) { r.Register( httpmock.GraphQL(`query IssueByNumber\b`), httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled": true, "issue":{"number":1} }}}`)) r.Register( httpmock.GraphQL(`query IssueByNumber\b`), httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled": true, "issue":{"number":2} }}}`)) }, wantIssues: []int{1, 2}, wantRepo: "https://github.com/OWNER/REPO", }, } for _, tt := range tests { if !tt.wantErr && len(tt.args.selectors) != len(tt.wantIssues) { t.Fatal("number of selectors and issues not equal") } t.Run(tt.name, func(t *testing.T) { reg := &httpmock.Registry{} if tt.httpStub != nil { tt.httpStub(reg) } httpClient := &http.Client{Transport: reg} issues, repo, err := IssuesFromArgsWithFields(httpClient, tt.args.baseRepoFn, tt.args.selectors, []string{"number"}) if (err != nil) != tt.wantErr { t.Errorf("IssuesFromArgsWithFields() error = %v, wantErr %v", err, tt.wantErr) if issues == nil { return } } if tt.wantErr { assert.Error(t, err) assert.ErrorContains(t, err, tt.wantErrMsg) return } assert.NoError(t, err) for i := range issues { assert.Contains(t, tt.wantIssues, issues[i].Number) } if repo != nil { repoURL := ghrepo.GenerateRepoURL(repo, "") if repoURL != tt.wantRepo { t.Errorf("want repo %s, got %s", tt.wantRepo, repoURL) } } else if tt.wantRepo != "" { t.Errorf("want repo %sw, got nil", tt.wantRepo) } }) } }