1// Copyright 2011 Google LLC. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package googleapi
6
7import (
8	"encoding/json"
9	"io/ioutil"
10	"net/http"
11	"net/url"
12	"reflect"
13	"strings"
14	"testing"
15)
16
17type ExpandTest struct {
18	in         string
19	expansions map[string]string
20	want       string
21}
22
23var expandTests = []ExpandTest{
24	// no expansions
25	{
26		"http://www.golang.org/",
27		map[string]string{},
28		"http://www.golang.org/",
29	},
30	// one expansion, no escaping
31	{
32		"http://www.golang.org/{bucket}/delete",
33		map[string]string{
34			"bucket": "red",
35		},
36		"http://www.golang.org/red/delete",
37	},
38	// one expansion, with hex escapes
39	{
40		"http://www.golang.org/{bucket}/delete",
41		map[string]string{
42			"bucket": "red/blue",
43		},
44		"http://www.golang.org/red%2Fblue/delete",
45	},
46	// one expansion, with space
47	{
48		"http://www.golang.org/{bucket}/delete",
49		map[string]string{
50			"bucket": "red or blue",
51		},
52		"http://www.golang.org/red%20or%20blue/delete",
53	},
54	// expansion not found
55	{
56		"http://www.golang.org/{object}/delete",
57		map[string]string{
58			"bucket": "red or blue",
59		},
60		"http://www.golang.org//delete",
61	},
62	// multiple expansions
63	{
64		"http://www.golang.org/{one}/{two}/{three}/get",
65		map[string]string{
66			"one":   "ONE",
67			"two":   "TWO",
68			"three": "THREE",
69		},
70		"http://www.golang.org/ONE/TWO/THREE/get",
71	},
72	// utf-8 characters
73	{
74		"http://www.golang.org/{bucket}/get",
75		map[string]string{
76			"bucket": "£100",
77		},
78		"http://www.golang.org/%C2%A3100/get",
79	},
80	// punctuations
81	{
82		"http://www.golang.org/{bucket}/get",
83		map[string]string{
84			"bucket": `/\@:,.`,
85		},
86		"http://www.golang.org/%2F%5C%40%3A%2C./get",
87	},
88	// mis-matched brackets
89	{
90		"http://www.golang.org/{bucket/get",
91		map[string]string{
92			"bucket": "red",
93		},
94		"http://www.golang.org/%7Bbucket/get",
95	},
96	// "+" prefix for suppressing escape
97	// See also: http://tools.ietf.org/html/rfc6570#section-3.2.3
98	{
99		"http://www.golang.org/{+topic}",
100		map[string]string{
101			"topic": "/topics/myproject/mytopic",
102		},
103		// The double slashes here look weird, but it's intentional
104		"http://www.golang.org//topics/myproject/mytopic",
105	},
106}
107
108func TestExpand(t *testing.T) {
109	for i, test := range expandTests {
110		u := url.URL{
111			Path: test.in,
112		}
113		Expand(&u, test.expansions)
114		got := u.EscapedPath()
115		if got != test.want {
116			t.Errorf("got %q expected %q in test %d", got, test.want, i+1)
117		}
118	}
119}
120
121func TestResolveRelative(t *testing.T) {
122	resolveRelativeTests := []struct {
123		basestr   string
124		relstr    string
125		want      string
126		wantPanic bool
127	}{
128		{
129			basestr: "http://www.golang.org/",
130			relstr:  "topics/myproject/mytopic",
131			want:    "http://www.golang.org/topics/myproject/mytopic",
132		},
133		{
134			basestr: "http://www.golang.org/",
135			relstr:  "topics/{+myproject}/{release}:build:test:deploy",
136			want:    "http://www.golang.org/topics/{+myproject}/{release}:build:test:deploy",
137		},
138		{
139			basestr: "https://www.googleapis.com/admin/reports/v1/",
140			relstr:  "/admin/reports_v1/channels/stop",
141			want:    "https://www.googleapis.com/admin/reports_v1/channels/stop",
142		},
143		{
144			basestr: "https://www.googleapis.com/admin/directory/v1/",
145			relstr:  "customer/{customerId}/orgunits{/orgUnitPath*}",
146			want:    "https://www.googleapis.com/admin/directory/v1/customer/{customerId}/orgunits{/orgUnitPath*}",
147		},
148		{
149			basestr: "https://www.googleapis.com/tagmanager/v2/",
150			relstr:  "accounts",
151			want:    "https://www.googleapis.com/tagmanager/v2/accounts",
152		},
153		{
154			basestr: "https://www.googleapis.com/tagmanager/v2/",
155			relstr:  "{+parent}/workspaces",
156			want:    "https://www.googleapis.com/tagmanager/v2/{+parent}/workspaces",
157		},
158		{
159			basestr: "https://www.googleapis.com/tagmanager/v2/",
160			relstr:  "{+path}:create_version",
161			want:    "https://www.googleapis.com/tagmanager/v2/{+path}:create_version",
162		},
163		{
164			basestr:   "http://localhost",
165			relstr:    ":8080foo",
166			wantPanic: true,
167		},
168		{
169			basestr: "https://www.googleapis.com/exampleapi/v2/somemethod",
170			relstr:  "/upload/exampleapi/v2/somemethod",
171			want:    "https://www.googleapis.com/upload/exampleapi/v2/somemethod",
172		},
173		{
174			basestr: "https://otherhost.googleapis.com/exampleapi/v2/somemethod",
175			relstr:  "/upload/exampleapi/v2/alternatemethod",
176			want:    "https://otherhost.googleapis.com/upload/exampleapi/v2/alternatemethod",
177		},
178	}
179
180	for _, test := range resolveRelativeTests {
181		t.Run(test.basestr+test.relstr, func(t *testing.T) {
182			if test.wantPanic {
183				defer func() {
184					if r := recover(); r == nil {
185						t.Errorf("expected panic, but did not see one")
186					}
187				}()
188			}
189
190			got := ResolveRelative(test.basestr, test.relstr)
191			if got != test.want {
192				t.Errorf("got %q expected %q", got, test.want)
193			}
194		})
195	}
196}
197
198type CheckResponseTest struct {
199	in       *http.Response
200	bodyText string
201	want     error
202	errText  string
203}
204
205var checkResponseTests = []CheckResponseTest{
206	{
207		&http.Response{
208			StatusCode: http.StatusOK,
209		},
210		"",
211		nil,
212		"",
213	},
214	{
215		&http.Response{
216			StatusCode: http.StatusInternalServerError,
217		},
218		`{"error":{}}`,
219		&Error{
220			Code: http.StatusInternalServerError,
221			Body: `{"error":{}}`,
222		},
223		`googleapi: got HTTP response code 500 with body: {"error":{}}`,
224	},
225	{
226		&http.Response{
227			StatusCode: http.StatusNotFound,
228		},
229		`{"error":{"message":"Error message for StatusNotFound."}}`,
230		&Error{
231			Code:    http.StatusNotFound,
232			Message: "Error message for StatusNotFound.",
233			Body:    `{"error":{"message":"Error message for StatusNotFound."}}`,
234		},
235		"googleapi: Error 404: Error message for StatusNotFound.",
236	},
237	{
238		&http.Response{
239			StatusCode: http.StatusBadRequest,
240		},
241		`{"error":"invalid_token","error_description":"Invalid Value"}`,
242		&Error{
243			Code: http.StatusBadRequest,
244			Body: `{"error":"invalid_token","error_description":"Invalid Value"}`,
245		},
246		`googleapi: got HTTP response code 400 with body: {"error":"invalid_token","error_description":"Invalid Value"}`,
247	},
248	{
249		&http.Response{
250			StatusCode: http.StatusBadRequest,
251		},
252		`{"error":{"errors":[{"domain":"usageLimits","reason":"keyInvalid","message":"Bad Request"}],"code":400,"message":"Bad Request"}}`,
253		&Error{
254			Code: http.StatusBadRequest,
255			Errors: []ErrorItem{
256				{
257					Reason:  "keyInvalid",
258					Message: "Bad Request",
259				},
260			},
261			Body:    `{"error":{"errors":[{"domain":"usageLimits","reason":"keyInvalid","message":"Bad Request"}],"code":400,"message":"Bad Request"}}`,
262			Message: "Bad Request",
263		},
264		"googleapi: Error 400: Bad Request, keyInvalid",
265	},
266}
267
268func TestCheckResponse(t *testing.T) {
269	for _, test := range checkResponseTests {
270		res := test.in
271		if test.bodyText != "" {
272			res.Body = ioutil.NopCloser(strings.NewReader(test.bodyText))
273		}
274		g := CheckResponse(res)
275		if !reflect.DeepEqual(g, test.want) {
276			t.Errorf("CheckResponse: got %v, want %v", g, test.want)
277			gotJSON, err := json.Marshal(g)
278			if err != nil {
279				t.Error(err)
280			}
281			wantJSON, err := json.Marshal(test.want)
282			if err != nil {
283				t.Error(err)
284			}
285			t.Errorf("json(got):  %q\njson(want): %q", string(gotJSON), string(wantJSON))
286		}
287		if g != nil && g.Error() != test.errText {
288			t.Errorf("CheckResponse: unexpected error message.\nGot:  %q\nwant: %q", g, test.errText)
289		}
290	}
291}
292
293type VariantPoint struct {
294	Type        string
295	Coordinates []float64
296}
297
298type VariantTest struct {
299	in     map[string]interface{}
300	result bool
301	want   VariantPoint
302}
303
304var coords = []interface{}{1.0, 2.0}
305
306var variantTests = []VariantTest{
307	{
308		in: map[string]interface{}{
309			"type":        "Point",
310			"coordinates": coords,
311		},
312		result: true,
313		want: VariantPoint{
314			Type:        "Point",
315			Coordinates: []float64{1.0, 2.0},
316		},
317	},
318	{
319		in: map[string]interface{}{
320			"type":  "Point",
321			"bogus": coords,
322		},
323		result: true,
324		want: VariantPoint{
325			Type: "Point",
326		},
327	},
328}
329
330func TestVariantType(t *testing.T) {
331	for _, test := range variantTests {
332		if g := VariantType(test.in); g != test.want.Type {
333			t.Errorf("VariantType(%v): got %v, want %v", test.in, g, test.want.Type)
334		}
335	}
336}
337
338func TestConvertVariant(t *testing.T) {
339	for _, test := range variantTests {
340		g := VariantPoint{}
341		r := ConvertVariant(test.in, &g)
342		if r != test.result {
343			t.Errorf("ConvertVariant(%v): got %v, want %v", test.in, r, test.result)
344		}
345		if !reflect.DeepEqual(g, test.want) {
346			t.Errorf("ConvertVariant(%v): got %v, want %v", test.in, g, test.want)
347		}
348	}
349}
350
351func TestRoundChunkSize(t *testing.T) {
352	type testCase struct {
353		in   int
354		want int
355	}
356	for _, tc := range []testCase{
357		{0, 0},
358		{256*1024 - 1, 256 * 1024},
359		{256 * 1024, 256 * 1024},
360		{256*1024 + 1, 2 * 256 * 1024},
361	} {
362		mo := &MediaOptions{}
363		ChunkSize(tc.in).setOptions(mo)
364		if got := mo.ChunkSize; got != tc.want {
365			t.Errorf("rounding chunk size: got: %v; want %v", got, tc.want)
366		}
367	}
368}
369