1// Copyright 2017 Google LLC.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package main
6
7import (
8	"context"
9	"encoding/json"
10	"fmt"
11	"io/ioutil"
12	"math"
13	"net/http"
14	"net/http/httptest"
15	"net/url"
16	"reflect"
17	"strings"
18	"testing"
19	"testing/iotest"
20
21	// If you add a client, add a matching go:generate line below.
22	mon "google.golang.org/api/monitoring/v3"
23	storage "google.golang.org/api/storage/v1"
24)
25
26//go:generate -command api go run gen.go docurls.go replacements.go -install -api
27
28//go:generate api monitoring:v3
29//go:generate api storage:v1
30
31type myHandler struct {
32	location string
33	r        *http.Request
34	body     []byte
35	reqURIs  []string
36	err      error
37}
38
39func (h *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
40	h.r = r
41	v, err := url.ParseRequestURI(r.URL.RequestURI())
42	if err != nil {
43		h.err = err
44		return
45	}
46	h.reqURIs = append(h.reqURIs, v.String())
47	if h.location != "" {
48		w.Header().Set("Location", h.location)
49	}
50	h.body, h.err = ioutil.ReadAll(r.Body)
51	fmt.Fprintf(w, "{}")
52}
53
54func TestMedia(t *testing.T) {
55	handler := &myHandler{}
56	server := httptest.NewServer(handler)
57	defer server.Close()
58
59	client := &http.Client{}
60	s, err := storage.New(client)
61	if err != nil {
62		t.Fatalf("unable to create service: %v", err)
63	}
64	s.BasePath = fmt.Sprintf("%s%s", server.URL, "/storage/v1/")
65
66	const body = "fake media data"
67	f := strings.NewReader(body)
68	o := &storage.Object{
69		Bucket:          "mybucket",
70		Name:            "filename",
71		ContentType:     "plain/text",
72		ContentEncoding: "utf-8",
73		ContentLanguage: "en",
74	}
75	_, err = s.Objects.Insert("mybucket", o).Media(f).Do()
76	if err != nil {
77		t.Fatalf("unable to insert object: %v", err)
78	}
79	g := handler.r
80	if w := "POST"; g.Method != w {
81		t.Errorf("Method = %q; want %q", g.Method, w)
82	}
83	if w := "HTTP/1.1"; g.Proto != w {
84		t.Errorf("Proto = %q; want %q", g.Proto, w)
85	}
86	if w := 1; g.ProtoMajor != w {
87		t.Errorf("ProtoMajor = %v; want %v", g.ProtoMajor, w)
88	}
89	if w := 1; g.ProtoMinor != w {
90		t.Errorf("ProtoMinor = %v; want %v", g.ProtoMinor, w)
91	}
92	if w, k := "google-api-go-client/0.5", "User-Agent"; len(g.Header[k]) != 1 || g.Header[k][0] != w {
93		t.Errorf("header %q = %#v; want %q", k, g.Header[k], w)
94	}
95	if w, k := "multipart/related; boundary=", "Content-Type"; len(g.Header[k]) != 1 || !strings.HasPrefix(g.Header[k][0], w) {
96		t.Errorf("header %q = %#v; want %q", k, g.Header[k], w)
97	}
98	if w, k := "gzip", "Accept-Encoding"; len(g.Header[k]) != 1 || g.Header[k][0] != w {
99		t.Errorf("header %q = %#v; want %q", k, g.Header[k], w)
100	}
101	if w := int64(-1); g.ContentLength != w {
102		t.Errorf("ContentLength = %v; want %v", g.ContentLength, w)
103	}
104	if w := "chunked"; len(g.TransferEncoding) != 1 || g.TransferEncoding[0] != w {
105		t.Errorf("TransferEncoding = %#v; want %q", g.TransferEncoding, w)
106	}
107	if w := server.Listener.Addr().String(); g.Host != w {
108		t.Errorf("Host = %q; want %q", g.Host, w)
109	}
110	if g.Form != nil {
111		t.Errorf("Form = %#v; want nil", g.Form)
112	}
113	if g.PostForm != nil {
114		t.Errorf("PostForm = %#v; want nil", g.PostForm)
115	}
116	if g.MultipartForm != nil {
117		t.Errorf("MultipartForm = %#v; want nil", g.MultipartForm)
118	}
119	if w := "/upload/storage/v1/b/mybucket/o?alt=json&prettyPrint=false&uploadType=multipart"; g.RequestURI != w {
120		t.Errorf("RequestURI = %q; want %q", g.RequestURI, w)
121	}
122	if w := "\r\n\r\n" + body + "\r\n"; !strings.Contains(string(handler.body), w) {
123		t.Errorf("Body = %q, want substring %q", handler.body, w)
124	}
125	if handler.err != nil {
126		t.Errorf("handler err = %v, want nil", handler.err)
127	}
128}
129
130func TestResumableMedia(t *testing.T) {
131	handler := &myHandler{}
132	server := httptest.NewServer(handler)
133	defer server.Close()
134
135	handler.location = server.URL
136	client := &http.Client{}
137	s, err := storage.New(client)
138	if err != nil {
139		t.Fatalf("unable to create service: %v", err)
140	}
141	s.BasePath = server.URL
142
143	const data = "fake resumable media data"
144	mediaSize := len(data)
145	f := strings.NewReader(data)
146	o := &storage.Object{
147		Bucket:          "mybucket",
148		Name:            "filename",
149		ContentType:     "plain/text",
150		ContentEncoding: "utf-8",
151		ContentLanguage: "en",
152	}
153	_, err = s.Objects.Insert("mybucket", o).Name("filename").ResumableMedia(context.Background(), f, int64(len(data)), "text/plain").Do()
154	if err != nil {
155		t.Fatalf("unable to insert object: %v", err)
156	}
157	g := handler.r
158	if w := "POST"; g.Method != w {
159		t.Errorf("Method = %q; want %q", g.Method, w)
160	}
161	if w := "HTTP/1.1"; g.Proto != w {
162		t.Errorf("Proto = %q; want %q", g.Proto, w)
163	}
164	if w := 1; g.ProtoMajor != w {
165		t.Errorf("ProtoMajor = %v; want %v", g.ProtoMajor, w)
166	}
167	if w := 1; g.ProtoMinor != w {
168		t.Errorf("ProtoMinor = %v; want %v", g.ProtoMinor, w)
169	}
170	if w, k := "google-api-go-client/0.5", "User-Agent"; len(g.Header[k]) != 1 || g.Header[k][0] != w {
171		t.Errorf("header %q = %#v; want %q", k, g.Header[k], w)
172	}
173	if want, got := []string{"text/plain"}, g.Header["Content-Type"]; !reflect.DeepEqual(got, want) {
174		t.Errorf("header Content-Type got: %#v; want: %#v", got, want)
175	}
176	if w, k := "gzip", "Accept-Encoding"; len(g.Header[k]) != 1 || g.Header[k][0] != w {
177		t.Errorf("header %q = %#v; want %q", k, g.Header[k], w)
178	}
179	if w := int64(mediaSize); g.ContentLength != w {
180		t.Errorf("ContentLength = %v; want %v", g.ContentLength, w)
181	}
182	if len(g.TransferEncoding) != 0 {
183		t.Errorf("TransferEncoding = %#v; want nil", g.TransferEncoding)
184	}
185	if g.Form != nil {
186		t.Errorf("Form = %#v; want nil", g.Form)
187	}
188	if g.PostForm != nil {
189		t.Errorf("PostForm = %#v; want nil", g.PostForm)
190	}
191	if g.MultipartForm != nil {
192		t.Errorf("MultipartForm = %#v; want nil", g.MultipartForm)
193	}
194	if handler.err != nil {
195		t.Errorf("handler err = %v, want nil", handler.err)
196	}
197}
198
199func TestNoMedia(t *testing.T) {
200	handler := &myHandler{}
201	server := httptest.NewServer(handler)
202	defer server.Close()
203
204	client := &http.Client{}
205	s, err := storage.New(client)
206	if err != nil {
207		t.Fatalf("unable to create service: %v", err)
208	}
209	s.BasePath = fmt.Sprintf("%s%s", server.URL, "/storage/v1/")
210
211	o := &storage.Object{
212		Bucket:          "mybucket",
213		Name:            "filename",
214		ContentType:     "plain/text",
215		ContentEncoding: "utf-8",
216		ContentLanguage: "en",
217	}
218	_, err = s.Objects.Insert("mybucket", o).Do()
219	if err != nil {
220		t.Fatalf("unable to insert object: %v", err)
221	}
222	g := handler.r
223	if w := "POST"; g.Method != w {
224		t.Errorf("Method = %q; want %q", g.Method, w)
225	}
226	if w := "HTTP/1.1"; g.Proto != w {
227		t.Errorf("Proto = %q; want %q", g.Proto, w)
228	}
229	if w := 1; g.ProtoMajor != w {
230		t.Errorf("ProtoMajor = %v; want %v", g.ProtoMajor, w)
231	}
232	if w := 1; g.ProtoMinor != w {
233		t.Errorf("ProtoMinor = %v; want %v", g.ProtoMinor, w)
234	}
235	if w, k := "google-api-go-client/0.5", "User-Agent"; len(g.Header[k]) != 1 || g.Header[k][0] != w {
236		t.Errorf("header %q = %#v; want %q", k, g.Header[k], w)
237	}
238	if w, k := "application/json", "Content-Type"; len(g.Header[k]) != 1 || g.Header[k][0] != w {
239		t.Errorf("header %q = %#v; want %q", k, g.Header[k], w)
240	}
241	if w, k := "gzip", "Accept-Encoding"; len(g.Header[k]) != 1 || g.Header[k][0] != w {
242		t.Errorf("header %q = %#v; want %q", k, g.Header[k], w)
243	}
244	if w := int64(116); g.ContentLength != w {
245		t.Errorf("ContentLength = %v; want %v", g.ContentLength, w)
246	}
247	if len(g.TransferEncoding) != 0 {
248		t.Errorf("TransferEncoding = %#v; want []string{}", g.TransferEncoding)
249	}
250	if w := server.Listener.Addr().String(); g.Host != w {
251		t.Errorf("Host = %q; want %q", g.Host, w)
252	}
253	if g.Form != nil {
254		t.Errorf("Form = %#v; want nil", g.Form)
255	}
256	if g.PostForm != nil {
257		t.Errorf("PostForm = %#v; want nil", g.PostForm)
258	}
259	if g.MultipartForm != nil {
260		t.Errorf("MultipartForm = %#v; want nil", g.MultipartForm)
261	}
262	if w := "/storage/v1/b/mybucket/o?alt=json&prettyPrint=false"; g.RequestURI != w {
263		t.Errorf("RequestURI = %q; want %q", g.RequestURI, w)
264	}
265	if w := `{"bucket":"mybucket","contentEncoding":"utf-8","contentLanguage":"en","contentType":"plain/text","name":"filename"}` + "\n"; string(handler.body) != w {
266		t.Errorf("Body = %q, want %q", handler.body, w)
267	}
268	if handler.err != nil {
269		t.Errorf("handler err = %v, want nil", handler.err)
270	}
271}
272
273func TestMediaErrHandling(t *testing.T) {
274	handler := &myHandler{}
275	server := httptest.NewServer(handler)
276	defer server.Close()
277
278	client := &http.Client{}
279	s, err := storage.New(client)
280	if err != nil {
281		t.Fatalf("unable to create service: %v", err)
282	}
283	s.BasePath = fmt.Sprintf("%s%s", server.URL, "/storage/v1/")
284
285	const body = "fake media data"
286	f := strings.NewReader(body)
287	// The combination of TimeoutReader and OneByteReader causes the first byte to
288	// be successfully delivered, but then a timeout error is reported.
289	r := iotest.TimeoutReader(iotest.OneByteReader(f))
290	o := &storage.Object{
291		Bucket:          "mybucket",
292		Name:            "filename",
293		ContentType:     "plain/text",
294		ContentEncoding: "utf-8",
295		ContentLanguage: "en",
296	}
297	_, err = s.Objects.Insert("mybucket", o).Media(r).Do()
298	if err == nil || !strings.Contains(err.Error(), "timeout") {
299		t.Errorf("expected timeout error, got %v", err)
300	}
301	if handler.err != nil {
302		t.Errorf("handler err = %v, want nil", handler.err)
303	}
304}
305
306func TestUserAgent(t *testing.T) {
307	handler := &myHandler{}
308	server := httptest.NewServer(handler)
309	defer server.Close()
310
311	client := &http.Client{}
312	s, err := storage.New(client)
313	if err != nil {
314		t.Fatalf("unable to create service: %v", err)
315	}
316	s.BasePath = server.URL
317	s.UserAgent = "myagent/1.0"
318
319	f := strings.NewReader("fake media data")
320	o := &storage.Object{
321		Bucket:          "mybucket",
322		Name:            "filename",
323		ContentType:     "plain/text",
324		ContentEncoding: "utf-8",
325		ContentLanguage: "en",
326	}
327	_, err = s.Objects.Insert("mybucket", o).Media(f).Do()
328	if err != nil {
329		t.Fatalf("unable to insert object: %v", err)
330	}
331	g := handler.r
332	if w, k := "google-api-go-client/0.5 myagent/1.0", "User-Agent"; len(g.Header[k]) != 1 || g.Header[k][0] != w {
333		t.Errorf("header %q = %#v; want %q", k, g.Header[k], w)
334	}
335}
336
337func myProgressUpdater(current, total int64) {}
338
339func TestParams(t *testing.T) {
340	handler := &myHandler{}
341	server := httptest.NewServer(handler)
342	defer server.Close()
343
344	handler.location = server.URL + "/uploadURL"
345	client := &http.Client{}
346	s, err := storage.New(client)
347	if err != nil {
348		t.Fatalf("unable to create service: %v", err)
349	}
350	s.BasePath = server.URL
351	s.UserAgent = "myagent/1.0"
352
353	const data = "fake media data"
354	f := strings.NewReader(data)
355	o := &storage.Object{
356		Bucket:          "mybucket",
357		Name:            "filename",
358		ContentType:     "plain/text",
359		ContentEncoding: "utf-8",
360		ContentLanguage: "en",
361	}
362	_, err = s.Objects.Insert("mybucket", o).Name(o.Name).IfGenerationMatch(42).ResumableMedia(context.Background(), f, int64(len(data)), "plain/text").ProgressUpdater(myProgressUpdater).Projection("full").Do()
363	if err != nil {
364		t.Fatalf("unable to insert object: %v", err)
365	}
366	if g, w := len(handler.reqURIs), 2; g != w {
367		t.Fatalf("len(reqURIs) = %v, want %v", g, w)
368	}
369	want := []string{
370		"/upload/storage/v1/b/mybucket/o?alt=json&ifGenerationMatch=42&name=filename&prettyPrint=false&projection=full&uploadType=resumable",
371		"/uploadURL",
372	}
373	if !reflect.DeepEqual(handler.reqURIs, want) {
374		t.Errorf("reqURIs = %#v, want = %#v", handler.reqURIs, want)
375	}
376}
377
378// This test verifies that the unmarshal code generated for float64s
379// (in this case, the one inside mon.TypedValue) compiles and
380// behaves correctly.
381func TestUnmarshalSpecialFloats(t *testing.T) {
382	for _, test := range []struct {
383		in   string
384		want float64
385	}{
386		{`{"doubleValue": 3}`, 3},
387		{`{"doubleValue": "Infinity"}`, math.Inf(1)},
388		{`{"doubleValue": "-Infinity"}`, math.Inf(-1)},
389		{`{"doubleValue": "NaN"}`, math.NaN()},
390	} {
391		var got mon.TypedValue
392		if err := json.Unmarshal([]byte(test.in), &got); err != nil {
393			t.Fatal(err)
394		}
395		if !fleq(*got.DoubleValue, test.want) {
396			t.Errorf("got\n%+v\nwant\n%+v", *got.DoubleValue, test.want)
397		}
398	}
399}
400
401func fleq(f1, f2 float64) bool {
402	return f1 == f2 || (math.IsNaN(f1) && math.IsNaN(f2))
403}
404