1// Copyright 2013 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	"bytes"
10	"context"
11	"encoding/json"
12	"fmt"
13	"io/ioutil"
14	"net/http"
15	"os"
16	"reflect"
17	"strings"
18	"testing"
19)
20
21func TestRepositoriesService_ListReleases(t *testing.T) {
22	client, mux, _, teardown := setup()
23	defer teardown()
24
25	mux.HandleFunc("/repos/o/r/releases", func(w http.ResponseWriter, r *http.Request) {
26		testMethod(t, r, "GET")
27		testFormValues(t, r, values{"page": "2"})
28		fmt.Fprint(w, `[{"id":1}]`)
29	})
30
31	opt := &ListOptions{Page: 2}
32	releases, _, err := client.Repositories.ListReleases(context.Background(), "o", "r", opt)
33	if err != nil {
34		t.Errorf("Repositories.ListReleases returned error: %v", err)
35	}
36	want := []*RepositoryRelease{{ID: Int64(1)}}
37	if !reflect.DeepEqual(releases, want) {
38		t.Errorf("Repositories.ListReleases returned %+v, want %+v", releases, want)
39	}
40}
41
42func TestRepositoriesService_GetRelease(t *testing.T) {
43	client, mux, _, teardown := setup()
44	defer teardown()
45
46	mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) {
47		testMethod(t, r, "GET")
48		fmt.Fprint(w, `{"id":1,"author":{"login":"l"}}`)
49	})
50
51	release, resp, err := client.Repositories.GetRelease(context.Background(), "o", "r", 1)
52	if err != nil {
53		t.Errorf("Repositories.GetRelease returned error: %v\n%v", err, resp.Body)
54	}
55
56	want := &RepositoryRelease{ID: Int64(1), Author: &User{Login: String("l")}}
57	if !reflect.DeepEqual(release, want) {
58		t.Errorf("Repositories.GetRelease returned %+v, want %+v", release, want)
59	}
60}
61
62func TestRepositoriesService_GetLatestRelease(t *testing.T) {
63	client, mux, _, teardown := setup()
64	defer teardown()
65
66	mux.HandleFunc("/repos/o/r/releases/latest", func(w http.ResponseWriter, r *http.Request) {
67		testMethod(t, r, "GET")
68		fmt.Fprint(w, `{"id":3}`)
69	})
70
71	release, resp, err := client.Repositories.GetLatestRelease(context.Background(), "o", "r")
72	if err != nil {
73		t.Errorf("Repositories.GetLatestRelease returned error: %v\n%v", err, resp.Body)
74	}
75
76	want := &RepositoryRelease{ID: Int64(3)}
77	if !reflect.DeepEqual(release, want) {
78		t.Errorf("Repositories.GetLatestRelease returned %+v, want %+v", release, want)
79	}
80}
81
82func TestRepositoriesService_GetReleaseByTag(t *testing.T) {
83	client, mux, _, teardown := setup()
84	defer teardown()
85
86	mux.HandleFunc("/repos/o/r/releases/tags/foo", func(w http.ResponseWriter, r *http.Request) {
87		testMethod(t, r, "GET")
88		fmt.Fprint(w, `{"id":13}`)
89	})
90
91	release, resp, err := client.Repositories.GetReleaseByTag(context.Background(), "o", "r", "foo")
92	if err != nil {
93		t.Errorf("Repositories.GetReleaseByTag returned error: %v\n%v", err, resp.Body)
94	}
95
96	want := &RepositoryRelease{ID: Int64(13)}
97	if !reflect.DeepEqual(release, want) {
98		t.Errorf("Repositories.GetReleaseByTag returned %+v, want %+v", release, want)
99	}
100}
101
102func TestRepositoriesService_CreateRelease(t *testing.T) {
103	client, mux, _, teardown := setup()
104	defer teardown()
105
106	input := &RepositoryRelease{
107		Name: String("v1.0"),
108		// Fields to be removed:
109		ID:          Int64(2),
110		CreatedAt:   &Timestamp{referenceTime},
111		PublishedAt: &Timestamp{referenceTime},
112		URL:         String("http://url/"),
113		HTMLURL:     String("http://htmlurl/"),
114		AssetsURL:   String("http://assetsurl/"),
115		Assets:      []ReleaseAsset{{ID: Int64(5)}},
116		UploadURL:   String("http://uploadurl/"),
117		ZipballURL:  String("http://zipballurl/"),
118		TarballURL:  String("http://tarballurl/"),
119		Author:      &User{Name: String("octocat")},
120		NodeID:      String("nodeid"),
121	}
122
123	mux.HandleFunc("/repos/o/r/releases", func(w http.ResponseWriter, r *http.Request) {
124		v := new(repositoryReleaseRequest)
125		json.NewDecoder(r.Body).Decode(v)
126
127		testMethod(t, r, "POST")
128		want := &repositoryReleaseRequest{Name: String("v1.0")}
129		if !reflect.DeepEqual(v, want) {
130			t.Errorf("Request body = %+v, want %+v", v, want)
131		}
132		fmt.Fprint(w, `{"id":1}`)
133	})
134
135	release, _, err := client.Repositories.CreateRelease(context.Background(), "o", "r", input)
136	if err != nil {
137		t.Errorf("Repositories.CreateRelease returned error: %v", err)
138	}
139
140	want := &RepositoryRelease{ID: Int64(1)}
141	if !reflect.DeepEqual(release, want) {
142		t.Errorf("Repositories.CreateRelease returned %+v, want %+v", release, want)
143	}
144}
145
146func TestRepositoriesService_EditRelease(t *testing.T) {
147	client, mux, _, teardown := setup()
148	defer teardown()
149
150	input := &RepositoryRelease{
151		Name: String("n"),
152		// Fields to be removed:
153		ID:          Int64(2),
154		CreatedAt:   &Timestamp{referenceTime},
155		PublishedAt: &Timestamp{referenceTime},
156		URL:         String("http://url/"),
157		HTMLURL:     String("http://htmlurl/"),
158		AssetsURL:   String("http://assetsurl/"),
159		Assets:      []ReleaseAsset{{ID: Int64(5)}},
160		UploadURL:   String("http://uploadurl/"),
161		ZipballURL:  String("http://zipballurl/"),
162		TarballURL:  String("http://tarballurl/"),
163		Author:      &User{Name: String("octocat")},
164		NodeID:      String("nodeid"),
165	}
166
167	mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) {
168		v := new(repositoryReleaseRequest)
169		json.NewDecoder(r.Body).Decode(v)
170
171		testMethod(t, r, "PATCH")
172		want := &repositoryReleaseRequest{Name: String("n")}
173		if !reflect.DeepEqual(v, want) {
174			t.Errorf("Request body = %+v, want %+v", v, want)
175		}
176		fmt.Fprint(w, `{"id":1}`)
177	})
178
179	release, _, err := client.Repositories.EditRelease(context.Background(), "o", "r", 1, input)
180	if err != nil {
181		t.Errorf("Repositories.EditRelease returned error: %v", err)
182	}
183	want := &RepositoryRelease{ID: Int64(1)}
184	if !reflect.DeepEqual(release, want) {
185		t.Errorf("Repositories.EditRelease returned = %+v, want %+v", release, want)
186	}
187}
188
189func TestRepositoriesService_DeleteRelease(t *testing.T) {
190	client, mux, _, teardown := setup()
191	defer teardown()
192
193	mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) {
194		testMethod(t, r, "DELETE")
195	})
196
197	_, err := client.Repositories.DeleteRelease(context.Background(), "o", "r", 1)
198	if err != nil {
199		t.Errorf("Repositories.DeleteRelease returned error: %v", err)
200	}
201}
202
203func TestRepositoriesService_ListReleaseAssets(t *testing.T) {
204	client, mux, _, teardown := setup()
205	defer teardown()
206
207	mux.HandleFunc("/repos/o/r/releases/1/assets", func(w http.ResponseWriter, r *http.Request) {
208		testMethod(t, r, "GET")
209		testFormValues(t, r, values{"page": "2"})
210		fmt.Fprint(w, `[{"id":1}]`)
211	})
212
213	opt := &ListOptions{Page: 2}
214	assets, _, err := client.Repositories.ListReleaseAssets(context.Background(), "o", "r", 1, opt)
215	if err != nil {
216		t.Errorf("Repositories.ListReleaseAssets returned error: %v", err)
217	}
218	want := []*ReleaseAsset{{ID: Int64(1)}}
219	if !reflect.DeepEqual(assets, want) {
220		t.Errorf("Repositories.ListReleaseAssets returned %+v, want %+v", assets, want)
221	}
222}
223
224func TestRepositoriesService_GetReleaseAsset(t *testing.T) {
225	client, mux, _, teardown := setup()
226	defer teardown()
227
228	mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
229		testMethod(t, r, "GET")
230		fmt.Fprint(w, `{"id":1}`)
231	})
232
233	asset, _, err := client.Repositories.GetReleaseAsset(context.Background(), "o", "r", 1)
234	if err != nil {
235		t.Errorf("Repositories.GetReleaseAsset returned error: %v", err)
236	}
237	want := &ReleaseAsset{ID: Int64(1)}
238	if !reflect.DeepEqual(asset, want) {
239		t.Errorf("Repositories.GetReleaseAsset returned %+v, want %+v", asset, want)
240	}
241}
242
243func TestRepositoriesService_DownloadReleaseAsset_Stream(t *testing.T) {
244	client, mux, _, teardown := setup()
245	defer teardown()
246
247	mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
248		testMethod(t, r, "GET")
249		testHeader(t, r, "Accept", defaultMediaType)
250		w.Header().Set("Content-Type", "application/octet-stream")
251		w.Header().Set("Content-Disposition", "attachment; filename=hello-world.txt")
252		fmt.Fprint(w, "Hello World")
253	})
254
255	reader, _, err := client.Repositories.DownloadReleaseAsset(context.Background(), "o", "r", 1)
256	if err != nil {
257		t.Errorf("Repositories.DownloadReleaseAsset returned error: %v", err)
258	}
259	want := []byte("Hello World")
260	content, err := ioutil.ReadAll(reader)
261	if err != nil {
262		t.Errorf("Repositories.DownloadReleaseAsset returned bad reader: %v", err)
263	}
264	if !bytes.Equal(want, content) {
265		t.Errorf("Repositories.DownloadReleaseAsset returned %+v, want %+v", content, want)
266	}
267}
268
269func TestRepositoriesService_DownloadReleaseAsset_Redirect(t *testing.T) {
270	client, mux, _, teardown := setup()
271	defer teardown()
272
273	mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
274		testMethod(t, r, "GET")
275		testHeader(t, r, "Accept", defaultMediaType)
276		http.Redirect(w, r, "/yo", http.StatusFound)
277	})
278
279	_, got, err := client.Repositories.DownloadReleaseAsset(context.Background(), "o", "r", 1)
280	if err != nil {
281		t.Errorf("Repositories.DownloadReleaseAsset returned error: %v", err)
282	}
283	want := "/yo"
284	if !strings.HasSuffix(got, want) {
285		t.Errorf("Repositories.DownloadReleaseAsset returned %+v, want %+v", got, want)
286	}
287}
288
289func TestRepositoriesService_DownloadReleaseAsset_APIError(t *testing.T) {
290	client, mux, _, teardown := setup()
291	defer teardown()
292
293	mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
294		testMethod(t, r, "GET")
295		testHeader(t, r, "Accept", defaultMediaType)
296		w.WriteHeader(http.StatusNotFound)
297		fmt.Fprint(w, `{"message":"Not Found","documentation_url":"https://developer.github.com/v3"}`)
298	})
299
300	resp, loc, err := client.Repositories.DownloadReleaseAsset(context.Background(), "o", "r", 1)
301	if err == nil {
302		t.Error("Repositories.DownloadReleaseAsset did not return an error")
303	}
304
305	if resp != nil {
306		resp.Close()
307		t.Error("Repositories.DownloadReleaseAsset returned stream, want nil")
308	}
309
310	if loc != "" {
311		t.Errorf(`Repositories.DownloadReleaseAsset returned "%s", want empty ""`, loc)
312	}
313}
314
315func TestRepositoriesService_EditReleaseAsset(t *testing.T) {
316	client, mux, _, teardown := setup()
317	defer teardown()
318
319	input := &ReleaseAsset{Name: String("n")}
320
321	mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
322		v := new(ReleaseAsset)
323		json.NewDecoder(r.Body).Decode(v)
324
325		testMethod(t, r, "PATCH")
326		if !reflect.DeepEqual(v, input) {
327			t.Errorf("Request body = %+v, want %+v", v, input)
328		}
329		fmt.Fprint(w, `{"id":1}`)
330	})
331
332	asset, _, err := client.Repositories.EditReleaseAsset(context.Background(), "o", "r", 1, input)
333	if err != nil {
334		t.Errorf("Repositories.EditReleaseAsset returned error: %v", err)
335	}
336	want := &ReleaseAsset{ID: Int64(1)}
337	if !reflect.DeepEqual(asset, want) {
338		t.Errorf("Repositories.EditReleaseAsset returned = %+v, want %+v", asset, want)
339	}
340}
341
342func TestRepositoriesService_DeleteReleaseAsset(t *testing.T) {
343	client, mux, _, teardown := setup()
344	defer teardown()
345
346	mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
347		testMethod(t, r, "DELETE")
348	})
349
350	_, err := client.Repositories.DeleteReleaseAsset(context.Background(), "o", "r", 1)
351	if err != nil {
352		t.Errorf("Repositories.DeleteReleaseAsset returned error: %v", err)
353	}
354}
355
356func TestRepositoriesService_UploadReleaseAsset(t *testing.T) {
357	var (
358		defaultUploadOptions     = &UploadOptions{Name: "n"}
359		defaultExpectedFormValue = values{"name": "n"}
360		mediaTypeTextPlain       = "text/plain; charset=utf-8"
361	)
362	uploadTests := []struct {
363		uploadOpts         *UploadOptions
364		fileName           string
365		expectedFormValues values
366		expectedMediaType  string
367	}{
368		// No file extension and no explicit media type.
369		{
370			defaultUploadOptions,
371			"upload",
372			defaultExpectedFormValue,
373			defaultMediaType,
374		},
375		// File extension and no explicit media type.
376		{
377			defaultUploadOptions,
378			"upload.txt",
379			defaultExpectedFormValue,
380			mediaTypeTextPlain,
381		},
382		// No file extension and explicit media type.
383		{
384			&UploadOptions{Name: "n", MediaType: "image/png"},
385			"upload",
386			defaultExpectedFormValue,
387			"image/png",
388		},
389		// File extension and explicit media type.
390		{
391			&UploadOptions{Name: "n", MediaType: "image/png"},
392			"upload.png",
393			defaultExpectedFormValue,
394			"image/png",
395		},
396		// Label provided.
397		{
398			&UploadOptions{Name: "n", Label: "l"},
399			"upload.txt",
400			values{"name": "n", "label": "l"},
401			mediaTypeTextPlain,
402		},
403		// No label provided.
404		{
405			defaultUploadOptions,
406			"upload.txt",
407			defaultExpectedFormValue,
408			mediaTypeTextPlain,
409		},
410	}
411
412	client, mux, _, teardown := setup()
413	defer teardown()
414
415	for key, test := range uploadTests {
416		releaseEndpoint := fmt.Sprintf("/repos/o/r/releases/%d/assets", key)
417		mux.HandleFunc(releaseEndpoint, func(w http.ResponseWriter, r *http.Request) {
418			testMethod(t, r, "POST")
419			testHeader(t, r, "Content-Type", test.expectedMediaType)
420			testHeader(t, r, "Content-Length", "12")
421			testFormValues(t, r, test.expectedFormValues)
422			testBody(t, r, "Upload me !\n")
423
424			fmt.Fprintf(w, `{"id":1}`)
425		})
426
427		file, dir, err := openTestFile(test.fileName, "Upload me !\n")
428		if err != nil {
429			t.Fatalf("Unable to create temp file: %v", err)
430		}
431		defer os.RemoveAll(dir)
432
433		asset, _, err := client.Repositories.UploadReleaseAsset(context.Background(), "o", "r", int64(key), test.uploadOpts, file)
434		if err != nil {
435			t.Errorf("Repositories.UploadReleaseAssert returned error: %v", err)
436		}
437		want := &ReleaseAsset{ID: Int64(1)}
438		if !reflect.DeepEqual(asset, want) {
439			t.Errorf("Repositories.UploadReleaseAssert returned %+v, want %+v", asset, want)
440		}
441	}
442}
443