1package api
2
3import (
4	"io"
5	"net/http"
6	"strings"
7	"testing"
8
9	"github.com/cli/cli/v2/internal/ghrepo"
10	"github.com/cli/cli/v2/pkg/httpmock"
11)
12
13func TestGitHubRepo_notFound(t *testing.T) {
14	httpReg := &httpmock.Registry{}
15	defer httpReg.Verify(t)
16
17	httpReg.Register(
18		httpmock.GraphQL(`query RepositoryInfo\b`),
19		httpmock.StringResponse(`{ "data": { "repository": null } }`))
20
21	client := NewClient(ReplaceTripper(httpReg))
22	repo, err := GitHubRepo(client, ghrepo.New("OWNER", "REPO"))
23	if err == nil {
24		t.Fatal("GitHubRepo did not return an error")
25	}
26	if wants := "GraphQL: Could not resolve to a Repository with the name 'OWNER/REPO'."; err.Error() != wants {
27		t.Errorf("GitHubRepo error: want %q, got %q", wants, err.Error())
28	}
29	if repo != nil {
30		t.Errorf("GitHubRepo: expected nil repo, got %v", repo)
31	}
32}
33
34func Test_RepoMetadata(t *testing.T) {
35	http := &httpmock.Registry{}
36	client := NewClient(ReplaceTripper(http))
37
38	repo, _ := ghrepo.FromFullName("OWNER/REPO")
39	input := RepoMetadataInput{
40		Assignees:  true,
41		Reviewers:  true,
42		Labels:     true,
43		Projects:   true,
44		Milestones: true,
45	}
46
47	http.Register(
48		httpmock.GraphQL(`query RepositoryAssignableUsers\b`),
49		httpmock.StringResponse(`
50		{ "data": { "repository": { "assignableUsers": {
51			"nodes": [
52				{ "login": "hubot", "id": "HUBOTID" },
53				{ "login": "MonaLisa", "id": "MONAID" }
54			],
55			"pageInfo": { "hasNextPage": false }
56		} } } }
57		`))
58	http.Register(
59		httpmock.GraphQL(`query RepositoryLabelList\b`),
60		httpmock.StringResponse(`
61		{ "data": { "repository": { "labels": {
62			"nodes": [
63				{ "name": "feature", "id": "FEATUREID" },
64				{ "name": "TODO", "id": "TODOID" },
65				{ "name": "bug", "id": "BUGID" }
66			],
67			"pageInfo": { "hasNextPage": false }
68		} } } }
69		`))
70	http.Register(
71		httpmock.GraphQL(`query RepositoryMilestoneList\b`),
72		httpmock.StringResponse(`
73		{ "data": { "repository": { "milestones": {
74			"nodes": [
75				{ "title": "GA", "id": "GAID" },
76				{ "title": "Big One.oh", "id": "BIGONEID" }
77			],
78			"pageInfo": { "hasNextPage": false }
79		} } } }
80		`))
81	http.Register(
82		httpmock.GraphQL(`query RepositoryProjectList\b`),
83		httpmock.StringResponse(`
84		{ "data": { "repository": { "projects": {
85			"nodes": [
86				{ "name": "Cleanup", "id": "CLEANUPID" },
87				{ "name": "Roadmap", "id": "ROADMAPID" }
88			],
89			"pageInfo": { "hasNextPage": false }
90		} } } }
91		`))
92	http.Register(
93		httpmock.GraphQL(`query OrganizationProjectList\b`),
94		httpmock.StringResponse(`
95		{ "data": { "organization": { "projects": {
96			"nodes": [
97				{ "name": "Triage", "id": "TRIAGEID" }
98			],
99			"pageInfo": { "hasNextPage": false }
100		} } } }
101		`))
102	http.Register(
103		httpmock.GraphQL(`query OrganizationTeamList\b`),
104		httpmock.StringResponse(`
105		{ "data": { "organization": { "teams": {
106			"nodes": [
107				{ "slug": "owners", "id": "OWNERSID" },
108				{ "slug": "Core", "id": "COREID" }
109			],
110			"pageInfo": { "hasNextPage": false }
111		} } } }
112		`))
113
114	result, err := RepoMetadata(client, repo, input)
115	if err != nil {
116		t.Fatalf("unexpected error: %v", err)
117	}
118
119	expectedMemberIDs := []string{"MONAID", "HUBOTID"}
120	memberIDs, err := result.MembersToIDs([]string{"monalisa", "hubot"})
121	if err != nil {
122		t.Errorf("error resolving members: %v", err)
123	}
124	if !sliceEqual(memberIDs, expectedMemberIDs) {
125		t.Errorf("expected members %v, got %v", expectedMemberIDs, memberIDs)
126	}
127
128	expectedTeamIDs := []string{"COREID", "OWNERSID"}
129	teamIDs, err := result.TeamsToIDs([]string{"OWNER/core", "/owners"})
130	if err != nil {
131		t.Errorf("error resolving teams: %v", err)
132	}
133	if !sliceEqual(teamIDs, expectedTeamIDs) {
134		t.Errorf("expected teams %v, got %v", expectedTeamIDs, teamIDs)
135	}
136
137	expectedLabelIDs := []string{"BUGID", "TODOID"}
138	labelIDs, err := result.LabelsToIDs([]string{"bug", "todo"})
139	if err != nil {
140		t.Errorf("error resolving labels: %v", err)
141	}
142	if !sliceEqual(labelIDs, expectedLabelIDs) {
143		t.Errorf("expected labels %v, got %v", expectedLabelIDs, labelIDs)
144	}
145
146	expectedProjectIDs := []string{"TRIAGEID", "ROADMAPID"}
147	projectIDs, err := result.ProjectsToIDs([]string{"triage", "roadmap"})
148	if err != nil {
149		t.Errorf("error resolving projects: %v", err)
150	}
151	if !sliceEqual(projectIDs, expectedProjectIDs) {
152		t.Errorf("expected projects %v, got %v", expectedProjectIDs, projectIDs)
153	}
154
155	expectedMilestoneID := "BIGONEID"
156	milestoneID, err := result.MilestoneToID("big one.oh")
157	if err != nil {
158		t.Errorf("error resolving milestone: %v", err)
159	}
160	if milestoneID != expectedMilestoneID {
161		t.Errorf("expected milestone %v, got %v", expectedMilestoneID, milestoneID)
162	}
163}
164
165func Test_ProjectsToPaths(t *testing.T) {
166	expectedProjectPaths := []string{"OWNER/REPO/PROJECT_NUMBER", "ORG/PROJECT_NUMBER"}
167	projects := []RepoProject{
168		{ID: "id1", Name: "My Project", ResourcePath: "/OWNER/REPO/projects/PROJECT_NUMBER"},
169		{ID: "id2", Name: "Org Project", ResourcePath: "/orgs/ORG/projects/PROJECT_NUMBER"},
170		{ID: "id3", Name: "Project", ResourcePath: "/orgs/ORG/projects/PROJECT_NUMBER_2"},
171	}
172	projectNames := []string{"My Project", "Org Project"}
173
174	projectPaths, err := ProjectsToPaths(projects, projectNames)
175	if err != nil {
176		t.Errorf("error resolving projects: %v", err)
177	}
178	if !sliceEqual(projectPaths, expectedProjectPaths) {
179		t.Errorf("expected projects %v, got %v", expectedProjectPaths, projectPaths)
180	}
181}
182
183func Test_ProjectNamesToPaths(t *testing.T) {
184	http := &httpmock.Registry{}
185	client := NewClient(ReplaceTripper(http))
186
187	repo, _ := ghrepo.FromFullName("OWNER/REPO")
188
189	http.Register(
190		httpmock.GraphQL(`query RepositoryProjectList\b`),
191		httpmock.StringResponse(`
192		{ "data": { "repository": { "projects": {
193			"nodes": [
194				{ "name": "Cleanup", "id": "CLEANUPID", "resourcePath": "/OWNER/REPO/projects/1" },
195				{ "name": "Roadmap", "id": "ROADMAPID", "resourcePath": "/OWNER/REPO/projects/2" }
196			],
197			"pageInfo": { "hasNextPage": false }
198		} } } }
199		`))
200	http.Register(
201		httpmock.GraphQL(`query OrganizationProjectList\b`),
202		httpmock.StringResponse(`
203			{ "data": { "organization": { "projects": {
204				"nodes": [
205					{ "name": "Triage", "id": "TRIAGEID", "resourcePath": "/orgs/ORG/projects/1"  }
206				],
207				"pageInfo": { "hasNextPage": false }
208			} } } }
209			`))
210
211	projectPaths, err := ProjectNamesToPaths(client, repo, []string{"Triage", "Roadmap"})
212	if err != nil {
213		t.Fatalf("unexpected error: %v", err)
214	}
215
216	expectedProjectPaths := []string{"ORG/1", "OWNER/REPO/2"}
217	if !sliceEqual(projectPaths, expectedProjectPaths) {
218		t.Errorf("expected projects paths %v, got %v", expectedProjectPaths, projectPaths)
219	}
220}
221
222func Test_RepoResolveMetadataIDs(t *testing.T) {
223	http := &httpmock.Registry{}
224	client := NewClient(ReplaceTripper(http))
225
226	repo, _ := ghrepo.FromFullName("OWNER/REPO")
227	input := RepoResolveInput{
228		Assignees: []string{"monalisa", "hubot"},
229		Reviewers: []string{"monalisa", "octocat", "OWNER/core", "/robots"},
230		Labels:    []string{"bug", "help wanted"},
231	}
232
233	expectedQuery := `query RepositoryResolveMetadataIDs {
234u000: user(login:"monalisa"){id,login}
235u001: user(login:"hubot"){id,login}
236u002: user(login:"octocat"){id,login}
237repository(owner:"OWNER",name:"REPO"){
238l000: label(name:"bug"){id,name}
239l001: label(name:"help wanted"){id,name}
240}
241organization(login:"OWNER"){
242t000: team(slug:"core"){id,slug}
243t001: team(slug:"robots"){id,slug}
244}
245}
246`
247	responseJSON := `
248	{ "data": {
249		"u000": { "login": "MonaLisa", "id": "MONAID" },
250		"u001": { "login": "hubot", "id": "HUBOTID" },
251		"u002": { "login": "octocat", "id": "OCTOID" },
252		"repository": {
253			"l000": { "name": "bug", "id": "BUGID" },
254			"l001": { "name": "Help Wanted", "id": "HELPID" }
255		},
256		"organization": {
257			"t000": { "slug": "core", "id": "COREID" },
258			"t001": { "slug": "Robots", "id": "ROBOTID" }
259		}
260	} }
261	`
262
263	http.Register(
264		httpmock.GraphQL(`query RepositoryResolveMetadataIDs\b`),
265		httpmock.GraphQLQuery(responseJSON, func(q string, _ map[string]interface{}) {
266			if q != expectedQuery {
267				t.Errorf("expected query %q, got %q", expectedQuery, q)
268			}
269		}))
270
271	result, err := RepoResolveMetadataIDs(client, repo, input)
272	if err != nil {
273		t.Fatalf("unexpected error: %v", err)
274	}
275
276	expectedMemberIDs := []string{"MONAID", "HUBOTID", "OCTOID"}
277	memberIDs, err := result.MembersToIDs([]string{"monalisa", "hubot", "octocat"})
278	if err != nil {
279		t.Errorf("error resolving members: %v", err)
280	}
281	if !sliceEqual(memberIDs, expectedMemberIDs) {
282		t.Errorf("expected members %v, got %v", expectedMemberIDs, memberIDs)
283	}
284
285	expectedTeamIDs := []string{"COREID", "ROBOTID"}
286	teamIDs, err := result.TeamsToIDs([]string{"/core", "/robots"})
287	if err != nil {
288		t.Errorf("error resolving teams: %v", err)
289	}
290	if !sliceEqual(teamIDs, expectedTeamIDs) {
291		t.Errorf("expected members %v, got %v", expectedTeamIDs, teamIDs)
292	}
293
294	expectedLabelIDs := []string{"BUGID", "HELPID"}
295	labelIDs, err := result.LabelsToIDs([]string{"bug", "help wanted"})
296	if err != nil {
297		t.Errorf("error resolving labels: %v", err)
298	}
299	if !sliceEqual(labelIDs, expectedLabelIDs) {
300		t.Errorf("expected members %v, got %v", expectedLabelIDs, labelIDs)
301	}
302}
303
304func sliceEqual(a, b []string) bool {
305	if len(a) != len(b) {
306		return false
307	}
308
309	for i := range a {
310		if a[i] != b[i] {
311			return false
312		}
313	}
314
315	return true
316}
317
318func Test_RepoMilestones(t *testing.T) {
319	tests := []struct {
320		state   string
321		want    string
322		wantErr bool
323	}{
324		{
325			state: "open",
326			want:  `"states":["OPEN"]`,
327		},
328		{
329			state: "closed",
330			want:  `"states":["CLOSED"]`,
331		},
332		{
333			state: "all",
334			want:  `"states":["OPEN","CLOSED"]`,
335		},
336		{
337			state:   "invalid state",
338			wantErr: true,
339		},
340	}
341	for _, tt := range tests {
342		var query string
343		reg := &httpmock.Registry{}
344		reg.Register(httpmock.MatchAny, func(req *http.Request) (*http.Response, error) {
345			buf := new(strings.Builder)
346			_, err := io.Copy(buf, req.Body)
347			if err != nil {
348				return nil, err
349			}
350			query = buf.String()
351			return httpmock.StringResponse("{}")(req)
352		})
353		client := NewClient(ReplaceTripper(reg))
354
355		_, err := RepoMilestones(client, ghrepo.New("OWNER", "REPO"), tt.state)
356		if (err != nil) != tt.wantErr {
357			t.Errorf("RepoMilestones() error = %v, wantErr %v", err, tt.wantErr)
358			return
359		}
360		if !strings.Contains(query, tt.want) {
361			t.Errorf("query does not contain %v", tt.want)
362		}
363	}
364}
365