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