1// Copyright 2016 The go-github AUTHORS. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file.
5
6package github
7
8import (
9	"context"
10	"encoding/json"
11	"fmt"
12	"net/http"
13	"reflect"
14	"strings"
15	"testing"
16)
17
18func TestProject_marshall(t *testing.T) {
19	testJSONMarshal(t, &Project{}, "{}")
20
21	u := &Project{
22		ID:         Int64(1),
23		URL:        String("u"),
24		HTMLURL:    String("h"),
25		ColumnsURL: String("c"),
26		OwnerURL:   String("o"),
27		Name:       String("n"),
28		Body:       String("b"),
29		Number:     Int(1),
30		State:      String("s"),
31		CreatedAt:  &Timestamp{referenceTime},
32		UpdatedAt:  &Timestamp{referenceTime},
33		NodeID:     String("n"),
34		Creator: &User{
35			Login:       String("l"),
36			ID:          Int64(1),
37			AvatarURL:   String("a"),
38			GravatarID:  String("g"),
39			Name:        String("n"),
40			Company:     String("c"),
41			Blog:        String("b"),
42			Location:    String("l"),
43			Email:       String("e"),
44			Hireable:    Bool(true),
45			PublicRepos: Int(1),
46			Followers:   Int(1),
47			Following:   Int(1),
48			CreatedAt:   &Timestamp{referenceTime},
49			URL:         String("u"),
50		},
51	}
52	want := `{
53		"id": 1,
54		"url": "u",
55		"html_url": "h",
56		"columns_url": "c",
57		"owner_url": "o",
58		"name": "n",
59		"body": "b",
60		"number": 1,
61		"state": "s",
62		"created_at": ` + referenceTimeStr + `,
63		"updated_at": ` + referenceTimeStr + `,
64		"node_id": "n",
65		"creator": {
66			"login": "l",
67			"id": 1,
68			"avatar_url": "a",
69			"gravatar_id": "g",
70			"name": "n",
71			"company": "c",
72			"blog": "b",
73			"location": "l",
74			"email": "e",
75			"hireable": true,
76			"public_repos": 1,
77			"followers": 1,
78			"following": 1,
79			"created_at": ` + referenceTimeStr + `,
80			"url": "u"
81		}
82	}`
83	testJSONMarshal(t, u, want)
84}
85func TestProjectsService_UpdateProject(t *testing.T) {
86	client, mux, _, teardown := setup()
87	defer teardown()
88
89	input := &ProjectOptions{
90		Name:   String("Project Name"),
91		Body:   String("Project body."),
92		State:  String("open"),
93		Public: Bool(true),
94
95		OrganizationPermission: String("read"),
96	}
97
98	mux.HandleFunc("/projects/1", func(w http.ResponseWriter, r *http.Request) {
99		testMethod(t, r, "PATCH")
100		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
101
102		v := &ProjectOptions{}
103		json.NewDecoder(r.Body).Decode(v)
104		if !reflect.DeepEqual(v, input) {
105			t.Errorf("Request body = %+v, want %+v", v, input)
106		}
107
108		fmt.Fprint(w, `{"id":1}`)
109	})
110
111	project, _, err := client.Projects.UpdateProject(context.Background(), 1, input)
112	if err != nil {
113		t.Errorf("Projects.UpdateProject returned error: %v", err)
114	}
115
116	want := &Project{ID: Int64(1)}
117	if !reflect.DeepEqual(project, want) {
118		t.Errorf("Projects.UpdateProject returned %+v, want %+v", project, want)
119	}
120}
121
122func TestProjectsService_GetProject(t *testing.T) {
123	client, mux, _, teardown := setup()
124	defer teardown()
125
126	mux.HandleFunc("/projects/1", func(w http.ResponseWriter, r *http.Request) {
127		testMethod(t, r, "GET")
128		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
129		fmt.Fprint(w, `{"id":1}`)
130	})
131
132	project, _, err := client.Projects.GetProject(context.Background(), 1)
133	if err != nil {
134		t.Errorf("Projects.GetProject returned error: %v", err)
135	}
136
137	want := &Project{ID: Int64(1)}
138	if !reflect.DeepEqual(project, want) {
139		t.Errorf("Projects.GetProject returned %+v, want %+v", project, want)
140	}
141}
142
143func TestProjectsService_DeleteProject(t *testing.T) {
144	client, mux, _, teardown := setup()
145	defer teardown()
146
147	mux.HandleFunc("/projects/1", func(w http.ResponseWriter, r *http.Request) {
148		testMethod(t, r, "DELETE")
149		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
150	})
151
152	_, err := client.Projects.DeleteProject(context.Background(), 1)
153	if err != nil {
154		t.Errorf("Projects.DeleteProject returned error: %v", err)
155	}
156}
157
158func TestProjectsService_ListProjectColumns(t *testing.T) {
159	client, mux, _, teardown := setup()
160	defer teardown()
161
162	wantAcceptHeaders := []string{mediaTypeProjectsPreview}
163	mux.HandleFunc("/projects/1/columns", func(w http.ResponseWriter, r *http.Request) {
164		testMethod(t, r, "GET")
165		testHeader(t, r, "Accept", strings.Join(wantAcceptHeaders, ", "))
166		testFormValues(t, r, values{"page": "2"})
167		fmt.Fprint(w, `[{"id":1}]`)
168	})
169
170	opt := &ListOptions{Page: 2}
171	columns, _, err := client.Projects.ListProjectColumns(context.Background(), 1, opt)
172	if err != nil {
173		t.Errorf("Projects.ListProjectColumns returned error: %v", err)
174	}
175
176	want := []*ProjectColumn{{ID: Int64(1)}}
177	if !reflect.DeepEqual(columns, want) {
178		t.Errorf("Projects.ListProjectColumns returned %+v, want %+v", columns, want)
179	}
180}
181
182func TestProjectsService_GetProjectColumn(t *testing.T) {
183	client, mux, _, teardown := setup()
184	defer teardown()
185
186	mux.HandleFunc("/projects/columns/1", func(w http.ResponseWriter, r *http.Request) {
187		testMethod(t, r, "GET")
188		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
189		fmt.Fprint(w, `{"id":1}`)
190	})
191
192	column, _, err := client.Projects.GetProjectColumn(context.Background(), 1)
193	if err != nil {
194		t.Errorf("Projects.GetProjectColumn returned error: %v", err)
195	}
196
197	want := &ProjectColumn{ID: Int64(1)}
198	if !reflect.DeepEqual(column, want) {
199		t.Errorf("Projects.GetProjectColumn returned %+v, want %+v", column, want)
200	}
201}
202
203func TestProjectsService_CreateProjectColumn(t *testing.T) {
204	client, mux, _, teardown := setup()
205	defer teardown()
206
207	input := &ProjectColumnOptions{Name: "Column Name"}
208
209	mux.HandleFunc("/projects/1/columns", func(w http.ResponseWriter, r *http.Request) {
210		testMethod(t, r, "POST")
211		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
212
213		v := &ProjectColumnOptions{}
214		json.NewDecoder(r.Body).Decode(v)
215		if !reflect.DeepEqual(v, input) {
216			t.Errorf("Request body = %+v, want %+v", v, input)
217		}
218
219		fmt.Fprint(w, `{"id":1}`)
220	})
221
222	column, _, err := client.Projects.CreateProjectColumn(context.Background(), 1, input)
223	if err != nil {
224		t.Errorf("Projects.CreateProjectColumn returned error: %v", err)
225	}
226
227	want := &ProjectColumn{ID: Int64(1)}
228	if !reflect.DeepEqual(column, want) {
229		t.Errorf("Projects.CreateProjectColumn returned %+v, want %+v", column, want)
230	}
231}
232
233func TestProjectsService_UpdateProjectColumn(t *testing.T) {
234	client, mux, _, teardown := setup()
235	defer teardown()
236
237	input := &ProjectColumnOptions{Name: "Column Name"}
238
239	mux.HandleFunc("/projects/columns/1", func(w http.ResponseWriter, r *http.Request) {
240		testMethod(t, r, "PATCH")
241		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
242
243		v := &ProjectColumnOptions{}
244		json.NewDecoder(r.Body).Decode(v)
245		if !reflect.DeepEqual(v, input) {
246			t.Errorf("Request body = %+v, want %+v", v, input)
247		}
248
249		fmt.Fprint(w, `{"id":1}`)
250	})
251
252	column, _, err := client.Projects.UpdateProjectColumn(context.Background(), 1, input)
253	if err != nil {
254		t.Errorf("Projects.UpdateProjectColumn returned error: %v", err)
255	}
256
257	want := &ProjectColumn{ID: Int64(1)}
258	if !reflect.DeepEqual(column, want) {
259		t.Errorf("Projects.UpdateProjectColumn returned %+v, want %+v", column, want)
260	}
261}
262
263func TestProjectsService_DeleteProjectColumn(t *testing.T) {
264	client, mux, _, teardown := setup()
265	defer teardown()
266
267	mux.HandleFunc("/projects/columns/1", func(w http.ResponseWriter, r *http.Request) {
268		testMethod(t, r, "DELETE")
269		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
270	})
271
272	_, err := client.Projects.DeleteProjectColumn(context.Background(), 1)
273	if err != nil {
274		t.Errorf("Projects.DeleteProjectColumn returned error: %v", err)
275	}
276}
277
278func TestProjectsService_MoveProjectColumn(t *testing.T) {
279	client, mux, _, teardown := setup()
280	defer teardown()
281
282	input := &ProjectColumnMoveOptions{Position: "after:12345"}
283
284	mux.HandleFunc("/projects/columns/1/moves", func(w http.ResponseWriter, r *http.Request) {
285		testMethod(t, r, "POST")
286		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
287
288		v := &ProjectColumnMoveOptions{}
289		json.NewDecoder(r.Body).Decode(v)
290		if !reflect.DeepEqual(v, input) {
291			t.Errorf("Request body = %+v, want %+v", v, input)
292		}
293	})
294
295	_, err := client.Projects.MoveProjectColumn(context.Background(), 1, input)
296	if err != nil {
297		t.Errorf("Projects.MoveProjectColumn returned error: %v", err)
298	}
299}
300
301func TestProjectsService_ListProjectCards(t *testing.T) {
302	client, mux, _, teardown := setup()
303	defer teardown()
304
305	mux.HandleFunc("/projects/columns/1/cards", func(w http.ResponseWriter, r *http.Request) {
306		testMethod(t, r, "GET")
307		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
308		testFormValues(t, r, values{
309			"archived_state": "all",
310			"page":           "2"})
311		fmt.Fprint(w, `[{"id":1}]`)
312	})
313
314	opt := &ProjectCardListOptions{
315		ArchivedState: String("all"),
316		ListOptions:   ListOptions{Page: 2}}
317	cards, _, err := client.Projects.ListProjectCards(context.Background(), 1, opt)
318	if err != nil {
319		t.Errorf("Projects.ListProjectCards returned error: %v", err)
320	}
321
322	want := []*ProjectCard{{ID: Int64(1)}}
323	if !reflect.DeepEqual(cards, want) {
324		t.Errorf("Projects.ListProjectCards returned %+v, want %+v", cards, want)
325	}
326}
327
328func TestProjectsService_GetProjectCard(t *testing.T) {
329	client, mux, _, teardown := setup()
330	defer teardown()
331
332	mux.HandleFunc("/projects/columns/cards/1", func(w http.ResponseWriter, r *http.Request) {
333		testMethod(t, r, "GET")
334		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
335		fmt.Fprint(w, `{"id":1}`)
336	})
337
338	card, _, err := client.Projects.GetProjectCard(context.Background(), 1)
339	if err != nil {
340		t.Errorf("Projects.GetProjectCard returned error: %v", err)
341	}
342
343	want := &ProjectCard{ID: Int64(1)}
344	if !reflect.DeepEqual(card, want) {
345		t.Errorf("Projects.GetProjectCard returned %+v, want %+v", card, want)
346	}
347}
348
349func TestProjectsService_CreateProjectCard(t *testing.T) {
350	client, mux, _, teardown := setup()
351	defer teardown()
352
353	input := &ProjectCardOptions{
354		ContentID:   12345,
355		ContentType: "Issue",
356	}
357
358	mux.HandleFunc("/projects/columns/1/cards", func(w http.ResponseWriter, r *http.Request) {
359		testMethod(t, r, "POST")
360		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
361
362		v := &ProjectCardOptions{}
363		json.NewDecoder(r.Body).Decode(v)
364		if !reflect.DeepEqual(v, input) {
365			t.Errorf("Request body = %+v, want %+v", v, input)
366		}
367
368		fmt.Fprint(w, `{"id":1}`)
369	})
370
371	card, _, err := client.Projects.CreateProjectCard(context.Background(), 1, input)
372	if err != nil {
373		t.Errorf("Projects.CreateProjectCard returned error: %v", err)
374	}
375
376	want := &ProjectCard{ID: Int64(1)}
377	if !reflect.DeepEqual(card, want) {
378		t.Errorf("Projects.CreateProjectCard returned %+v, want %+v", card, want)
379	}
380}
381
382func TestProjectsService_UpdateProjectCard(t *testing.T) {
383	client, mux, _, teardown := setup()
384	defer teardown()
385
386	input := &ProjectCardOptions{
387		ContentID:   12345,
388		ContentType: "Issue",
389	}
390
391	mux.HandleFunc("/projects/columns/cards/1", func(w http.ResponseWriter, r *http.Request) {
392		testMethod(t, r, "PATCH")
393		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
394
395		v := &ProjectCardOptions{}
396		json.NewDecoder(r.Body).Decode(v)
397		if !reflect.DeepEqual(v, input) {
398			t.Errorf("Request body = %+v, want %+v", v, input)
399		}
400
401		fmt.Fprint(w, `{"id":1, "archived":false}`)
402	})
403
404	card, _, err := client.Projects.UpdateProjectCard(context.Background(), 1, input)
405	if err != nil {
406		t.Errorf("Projects.UpdateProjectCard returned error: %v", err)
407	}
408
409	want := &ProjectCard{ID: Int64(1), Archived: Bool(false)}
410	if !reflect.DeepEqual(card, want) {
411		t.Errorf("Projects.UpdateProjectCard returned %+v, want %+v", card, want)
412	}
413}
414
415func TestProjectsService_DeleteProjectCard(t *testing.T) {
416	client, mux, _, teardown := setup()
417	defer teardown()
418
419	mux.HandleFunc("/projects/columns/cards/1", func(w http.ResponseWriter, r *http.Request) {
420		testMethod(t, r, "DELETE")
421		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
422	})
423
424	_, err := client.Projects.DeleteProjectCard(context.Background(), 1)
425	if err != nil {
426		t.Errorf("Projects.DeleteProjectCard returned error: %v", err)
427	}
428}
429
430func TestProjectsService_MoveProjectCard(t *testing.T) {
431	client, mux, _, teardown := setup()
432	defer teardown()
433
434	input := &ProjectCardMoveOptions{Position: "after:12345"}
435
436	mux.HandleFunc("/projects/columns/cards/1/moves", func(w http.ResponseWriter, r *http.Request) {
437		testMethod(t, r, "POST")
438		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
439
440		v := &ProjectCardMoveOptions{}
441		json.NewDecoder(r.Body).Decode(v)
442		if !reflect.DeepEqual(v, input) {
443			t.Errorf("Request body = %+v, want %+v", v, input)
444		}
445	})
446
447	_, err := client.Projects.MoveProjectCard(context.Background(), 1, input)
448	if err != nil {
449		t.Errorf("Projects.MoveProjectCard returned error: %v", err)
450	}
451}
452
453func TestProjectsService_AddProjectCollaborator(t *testing.T) {
454	client, mux, _, teardown := setup()
455	defer teardown()
456
457	opt := &ProjectCollaboratorOptions{
458		Permission: String("admin"),
459	}
460
461	mux.HandleFunc("/projects/1/collaborators/u", func(w http.ResponseWriter, r *http.Request) {
462		testMethod(t, r, "PUT")
463		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
464
465		v := &ProjectCollaboratorOptions{}
466		json.NewDecoder(r.Body).Decode(v)
467		if !reflect.DeepEqual(v, opt) {
468			t.Errorf("Request body = %+v, want %+v", v, opt)
469		}
470
471		w.WriteHeader(http.StatusNoContent)
472	})
473
474	_, err := client.Projects.AddProjectCollaborator(context.Background(), 1, "u", opt)
475	if err != nil {
476		t.Errorf("Projects.AddProjectCollaborator returned error: %v", err)
477	}
478}
479
480func TestProjectsService_AddCollaborator_invalidUser(t *testing.T) {
481	client, _, _, teardown := setup()
482	defer teardown()
483
484	_, err := client.Projects.AddProjectCollaborator(context.Background(), 1, "%", nil)
485	testURLParseError(t, err)
486}
487
488func TestProjectsService_RemoveCollaborator(t *testing.T) {
489	client, mux, _, teardown := setup()
490	defer teardown()
491
492	mux.HandleFunc("/projects/1/collaborators/u", func(w http.ResponseWriter, r *http.Request) {
493		testMethod(t, r, "DELETE")
494		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
495		w.WriteHeader(http.StatusNoContent)
496	})
497
498	_, err := client.Projects.RemoveProjectCollaborator(context.Background(), 1, "u")
499	if err != nil {
500		t.Errorf("Projects.RemoveProjectCollaborator returned error: %v", err)
501	}
502}
503
504func TestProjectsService_RemoveCollaborator_invalidUser(t *testing.T) {
505	client, _, _, teardown := setup()
506	defer teardown()
507
508	_, err := client.Projects.RemoveProjectCollaborator(context.Background(), 1, "%")
509	testURLParseError(t, err)
510}
511
512func TestProjectsService_ListCollaborators(t *testing.T) {
513	client, mux, _, teardown := setup()
514	defer teardown()
515
516	mux.HandleFunc("/projects/1/collaborators", func(w http.ResponseWriter, r *http.Request) {
517		testMethod(t, r, "GET")
518		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
519		testFormValues(t, r, values{"page": "2"})
520		fmt.Fprintf(w, `[{"id":1}, {"id":2}]`)
521	})
522
523	opt := &ListCollaboratorOptions{
524		ListOptions: ListOptions{Page: 2},
525	}
526	users, _, err := client.Projects.ListProjectCollaborators(context.Background(), 1, opt)
527	if err != nil {
528		t.Errorf("Projects.ListProjectCollaborators returned error: %v", err)
529	}
530
531	want := []*User{{ID: Int64(1)}, {ID: Int64(2)}}
532	if !reflect.DeepEqual(users, want) {
533		t.Errorf("Projects.ListProjectCollaborators returned %+v, want %+v", users, want)
534	}
535}
536
537func TestProjectsService_ListCollaborators_withAffiliation(t *testing.T) {
538	client, mux, _, teardown := setup()
539	defer teardown()
540
541	mux.HandleFunc("/projects/1/collaborators", func(w http.ResponseWriter, r *http.Request) {
542		testMethod(t, r, "GET")
543		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
544		testFormValues(t, r, values{"affiliation": "all", "page": "2"})
545		fmt.Fprintf(w, `[{"id":1}, {"id":2}]`)
546	})
547
548	opt := &ListCollaboratorOptions{
549		ListOptions: ListOptions{Page: 2},
550		Affiliation: String("all"),
551	}
552	users, _, err := client.Projects.ListProjectCollaborators(context.Background(), 1, opt)
553	if err != nil {
554		t.Errorf("Projects.ListProjectCollaborators returned error: %v", err)
555	}
556
557	want := []*User{{ID: Int64(1)}, {ID: Int64(2)}}
558	if !reflect.DeepEqual(users, want) {
559		t.Errorf("Projects.ListProjectCollaborators returned %+v, want %+v", users, want)
560	}
561}
562
563func TestProjectsService_GetPermissionLevel(t *testing.T) {
564	client, mux, _, teardown := setup()
565	defer teardown()
566
567	mux.HandleFunc("/projects/1/collaborators/u/permission", func(w http.ResponseWriter, r *http.Request) {
568		testMethod(t, r, "GET")
569		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
570		fmt.Fprintf(w, `{"permission":"admin","user":{"login":"u"}}`)
571	})
572
573	ppl, _, err := client.Projects.ReviewProjectCollaboratorPermission(context.Background(), 1, "u")
574	if err != nil {
575		t.Errorf("Projects.ReviewProjectCollaboratorPermission returned error: %v", err)
576	}
577
578	want := &ProjectPermissionLevel{
579		Permission: String("admin"),
580		User: &User{
581			Login: String("u"),
582		},
583	}
584
585	if !reflect.DeepEqual(ppl, want) {
586		t.Errorf("Projects.ReviewProjectCollaboratorPermission returned %+v, want %+v", ppl, want)
587	}
588}
589