1// Copyright 2015 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 gensupport 6 7import ( 8 "encoding/json" 9 "reflect" 10 "testing" 11 12 "google.golang.org/api/googleapi" 13) 14 15type schema struct { 16 // Basic types 17 B bool `json:"b,omitempty"` 18 F float64 `json:"f,omitempty"` 19 I int64 `json:"i,omitempty"` 20 Istr int64 `json:"istr,omitempty,string"` 21 Str string `json:"str,omitempty"` 22 23 // Pointers to basic types 24 PB *bool `json:"pb,omitempty"` 25 PF *float64 `json:"pf,omitempty"` 26 PI *int64 `json:"pi,omitempty"` 27 PIStr *int64 `json:"pistr,omitempty,string"` 28 PStr *string `json:"pstr,omitempty"` 29 30 // Other types 31 Int64s googleapi.Int64s `json:"i64s,omitempty"` 32 S []int `json:"s,omitempty"` 33 M map[string]string `json:"m,omitempty"` 34 Any interface{} `json:"any,omitempty"` 35 Child *child `json:"child,omitempty"` 36 MapToAnyArray map[string][]interface{} `json:"maptoanyarray,omitempty"` 37 38 ForceSendFields []string `json:"-"` 39 NullFields []string `json:"-"` 40} 41 42type child struct { 43 B bool `json:"childbool,omitempty"` 44} 45 46type testCase struct { 47 s schema 48 want string 49} 50 51func TestBasics(t *testing.T) { 52 for _, tc := range []testCase{ 53 { 54 s: schema{}, 55 want: `{}`, 56 }, 57 { 58 s: schema{ 59 ForceSendFields: []string{"B", "F", "I", "Istr", "Str", "PB", "PF", "PI", "PIStr", "PStr"}, 60 }, 61 want: `{"b":false,"f":0.0,"i":0,"istr":"0","str":""}`, 62 }, 63 { 64 s: schema{ 65 NullFields: []string{"B", "F", "I", "Istr", "Str", "PB", "PF", "PI", "PIStr", "PStr"}, 66 }, 67 want: `{"b":null,"f":null,"i":null,"istr":null,"str":null,"pb":null,"pf":null,"pi":null,"pistr":null,"pstr":null}`, 68 }, 69 { 70 s: schema{ 71 B: true, 72 F: 1.2, 73 I: 1, 74 Istr: 2, 75 Str: "a", 76 PB: googleapi.Bool(true), 77 PF: googleapi.Float64(1.2), 78 PI: googleapi.Int64(int64(1)), 79 PIStr: googleapi.Int64(int64(2)), 80 PStr: googleapi.String("a"), 81 }, 82 want: `{"b":true,"f":1.2,"i":1,"istr":"2","str":"a","pb":true,"pf":1.2,"pi":1,"pistr":"2","pstr":"a"}`, 83 }, 84 { 85 s: schema{ 86 B: false, 87 F: 0.0, 88 I: 0, 89 Istr: 0, 90 Str: "", 91 PB: googleapi.Bool(false), 92 PF: googleapi.Float64(0.0), 93 PI: googleapi.Int64(int64(0)), 94 PIStr: googleapi.Int64(int64(0)), 95 PStr: googleapi.String(""), 96 }, 97 want: `{"pb":false,"pf":0.0,"pi":0,"pistr":"0","pstr":""}`, 98 }, 99 { 100 s: schema{ 101 B: false, 102 F: 0.0, 103 I: 0, 104 Istr: 0, 105 Str: "", 106 PB: googleapi.Bool(false), 107 PF: googleapi.Float64(0.0), 108 PI: googleapi.Int64(int64(0)), 109 PIStr: googleapi.Int64(int64(0)), 110 PStr: googleapi.String(""), 111 ForceSendFields: []string{"B", "F", "I", "Istr", "Str", "PB", "PF", "PI", "PIStr", "PStr"}, 112 }, 113 want: `{"b":false,"f":0.0,"i":0,"istr":"0","str":"","pb":false,"pf":0.0,"pi":0,"pistr":"0","pstr":""}`, 114 }, 115 { 116 s: schema{ 117 B: false, 118 F: 0.0, 119 I: 0, 120 Istr: 0, 121 Str: "", 122 PB: googleapi.Bool(false), 123 PF: googleapi.Float64(0.0), 124 PI: googleapi.Int64(int64(0)), 125 PIStr: googleapi.Int64(int64(0)), 126 PStr: googleapi.String(""), 127 NullFields: []string{"B", "F", "I", "Istr", "Str"}, 128 }, 129 want: `{"b":null,"f":null,"i":null,"istr":null,"str":null,"pb":false,"pf":0.0,"pi":0,"pistr":"0","pstr":""}`, 130 }, 131 } { 132 checkMarshalJSON(t, tc) 133 } 134} 135 136func TestSliceFields(t *testing.T) { 137 for _, tc := range []testCase{ 138 { 139 s: schema{}, 140 want: `{}`, 141 }, 142 { 143 s: schema{S: []int{}, Int64s: googleapi.Int64s{}}, 144 want: `{}`, 145 }, 146 { 147 s: schema{S: []int{1}, Int64s: googleapi.Int64s{1}}, 148 want: `{"s":[1],"i64s":["1"]}`, 149 }, 150 { 151 s: schema{ 152 ForceSendFields: []string{"S", "Int64s"}, 153 }, 154 want: `{"s":[],"i64s":[]}`, 155 }, 156 { 157 s: schema{ 158 S: []int{}, 159 Int64s: googleapi.Int64s{}, 160 ForceSendFields: []string{"S", "Int64s"}, 161 }, 162 want: `{"s":[],"i64s":[]}`, 163 }, 164 { 165 s: schema{ 166 S: []int{1}, 167 Int64s: googleapi.Int64s{1}, 168 ForceSendFields: []string{"S", "Int64s"}, 169 }, 170 want: `{"s":[1],"i64s":["1"]}`, 171 }, 172 { 173 s: schema{ 174 NullFields: []string{"S", "Int64s"}, 175 }, 176 want: `{"s":null,"i64s":null}`, 177 }, 178 } { 179 checkMarshalJSON(t, tc) 180 } 181} 182 183func TestMapField(t *testing.T) { 184 for _, tc := range []testCase{ 185 { 186 s: schema{}, 187 want: `{}`, 188 }, 189 { 190 s: schema{M: make(map[string]string)}, 191 want: `{}`, 192 }, 193 { 194 s: schema{M: map[string]string{"a": "b"}}, 195 want: `{"m":{"a":"b"}}`, 196 }, 197 { 198 s: schema{ 199 ForceSendFields: []string{"M"}, 200 }, 201 want: `{"m":{}}`, 202 }, 203 { 204 s: schema{ 205 NullFields: []string{"M"}, 206 }, 207 want: `{"m":null}`, 208 }, 209 { 210 s: schema{ 211 M: make(map[string]string), 212 ForceSendFields: []string{"M"}, 213 }, 214 want: `{"m":{}}`, 215 }, 216 { 217 s: schema{ 218 M: make(map[string]string), 219 NullFields: []string{"M"}, 220 }, 221 want: `{"m":null}`, 222 }, 223 { 224 s: schema{ 225 M: map[string]string{"a": "b"}, 226 ForceSendFields: []string{"M"}, 227 }, 228 want: `{"m":{"a":"b"}}`, 229 }, 230 { 231 s: schema{ 232 M: map[string]string{"a": "b"}, 233 NullFields: []string{"M.a", "M."}, 234 }, 235 want: `{"m": {"a": null, "":null}}`, 236 }, 237 { 238 s: schema{ 239 M: map[string]string{"a": "b"}, 240 NullFields: []string{"M.c"}, 241 }, 242 want: `{"m": {"a": "b", "c": null}}`, 243 }, 244 { 245 s: schema{ 246 NullFields: []string{"M.a"}, 247 ForceSendFields: []string{"M"}, 248 }, 249 want: `{"m": {"a": null}}`, 250 }, 251 { 252 s: schema{ 253 NullFields: []string{"M.a"}, 254 }, 255 want: `{}`, 256 }, 257 } { 258 checkMarshalJSON(t, tc) 259 } 260} 261 262func TestMapToAnyArray(t *testing.T) { 263 for _, tc := range []testCase{ 264 { 265 s: schema{}, 266 want: `{}`, 267 }, 268 { 269 s: schema{MapToAnyArray: make(map[string][]interface{})}, 270 want: `{}`, 271 }, 272 { 273 s: schema{ 274 MapToAnyArray: map[string][]interface{}{ 275 "a": {2, "b"}, 276 }, 277 }, 278 want: `{"maptoanyarray":{"a":[2, "b"]}}`, 279 }, 280 { 281 s: schema{ 282 MapToAnyArray: map[string][]interface{}{ 283 "a": nil, 284 }, 285 }, 286 want: `{"maptoanyarray":{"a": null}}`, 287 }, 288 { 289 s: schema{ 290 MapToAnyArray: map[string][]interface{}{ 291 "a": {nil}, 292 }, 293 }, 294 want: `{"maptoanyarray":{"a":[null]}}`, 295 }, 296 { 297 s: schema{ 298 ForceSendFields: []string{"MapToAnyArray"}, 299 }, 300 want: `{"maptoanyarray":{}}`, 301 }, 302 { 303 s: schema{ 304 NullFields: []string{"MapToAnyArray"}, 305 }, 306 want: `{"maptoanyarray":null}`, 307 }, 308 { 309 s: schema{ 310 MapToAnyArray: make(map[string][]interface{}), 311 ForceSendFields: []string{"MapToAnyArray"}, 312 }, 313 want: `{"maptoanyarray":{}}`, 314 }, 315 { 316 s: schema{ 317 MapToAnyArray: map[string][]interface{}{ 318 "a": {2, "b"}, 319 }, 320 ForceSendFields: []string{"MapToAnyArray"}, 321 }, 322 want: `{"maptoanyarray":{"a":[2, "b"]}}`, 323 }, 324 } { 325 checkMarshalJSON(t, tc) 326 } 327} 328 329type anyType struct { 330 Field int 331} 332 333func (a anyType) MarshalJSON() ([]byte, error) { 334 return []byte(`"anyType value"`), nil 335} 336 337func TestAnyField(t *testing.T) { 338 // ForceSendFields has no effect on nil interfaces and interfaces that contain nil pointers. 339 var nilAny *anyType 340 for _, tc := range []testCase{ 341 { 342 s: schema{}, 343 want: `{}`, 344 }, 345 { 346 s: schema{Any: nilAny}, 347 want: `{"any": null}`, 348 }, 349 { 350 s: schema{Any: &anyType{}}, 351 want: `{"any":"anyType value"}`, 352 }, 353 { 354 s: schema{Any: anyType{}}, 355 want: `{"any":"anyType value"}`, 356 }, 357 { 358 s: schema{ 359 ForceSendFields: []string{"Any"}, 360 }, 361 want: `{}`, 362 }, 363 { 364 s: schema{ 365 NullFields: []string{"Any"}, 366 }, 367 want: `{"any":null}`, 368 }, 369 { 370 s: schema{ 371 Any: nilAny, 372 ForceSendFields: []string{"Any"}, 373 }, 374 want: `{"any": null}`, 375 }, 376 { 377 s: schema{ 378 Any: &anyType{}, 379 ForceSendFields: []string{"Any"}, 380 }, 381 want: `{"any":"anyType value"}`, 382 }, 383 { 384 s: schema{ 385 Any: anyType{}, 386 ForceSendFields: []string{"Any"}, 387 }, 388 want: `{"any":"anyType value"}`, 389 }, 390 } { 391 checkMarshalJSON(t, tc) 392 } 393} 394 395func TestSubschema(t *testing.T) { 396 // Subschemas are always stored as pointers, so ForceSendFields has no effect on them. 397 for _, tc := range []testCase{ 398 { 399 s: schema{}, 400 want: `{}`, 401 }, 402 { 403 s: schema{ 404 ForceSendFields: []string{"Child"}, 405 }, 406 want: `{}`, 407 }, 408 { 409 s: schema{ 410 NullFields: []string{"Child"}, 411 }, 412 want: `{"child":null}`, 413 }, 414 { 415 s: schema{Child: &child{}}, 416 want: `{"child":{}}`, 417 }, 418 { 419 s: schema{ 420 Child: &child{}, 421 ForceSendFields: []string{"Child"}, 422 }, 423 want: `{"child":{}}`, 424 }, 425 { 426 s: schema{Child: &child{B: true}}, 427 want: `{"child":{"childbool":true}}`, 428 }, 429 430 { 431 s: schema{ 432 Child: &child{B: true}, 433 ForceSendFields: []string{"Child"}, 434 }, 435 want: `{"child":{"childbool":true}}`, 436 }, 437 } { 438 checkMarshalJSON(t, tc) 439 } 440} 441 442// checkMarshalJSON verifies that calling schemaToMap on tc.s yields a result which is equivalent to tc.want. 443func checkMarshalJSON(t *testing.T, tc testCase) { 444 doCheckMarshalJSON(t, tc.s, tc.s.ForceSendFields, tc.s.NullFields, tc.want) 445 if len(tc.s.ForceSendFields) == 0 && len(tc.s.NullFields) == 0 { 446 // verify that the code path used when ForceSendFields and NullFields 447 // are non-empty produces the same output as the fast path that is used 448 // when they are empty. 449 doCheckMarshalJSON(t, tc.s, []string{"dummy"}, []string{"dummy"}, tc.want) 450 } 451} 452 453func doCheckMarshalJSON(t *testing.T, s schema, forceSendFields, nullFields []string, wantJSON string) { 454 encoded, err := MarshalJSON(s, forceSendFields, nullFields) 455 if err != nil { 456 t.Fatalf("encoding json:\n got err: %v", err) 457 } 458 459 // The expected and obtained JSON can differ in field ordering, so unmarshal before comparing. 460 var got interface{} 461 var want interface{} 462 err = json.Unmarshal(encoded, &got) 463 if err != nil { 464 t.Fatalf("decoding json:\n got err: %v", err) 465 } 466 err = json.Unmarshal([]byte(wantJSON), &want) 467 if err != nil { 468 t.Fatalf("decoding json:\n got err: %v", err) 469 } 470 if !reflect.DeepEqual(got, want) { 471 t.Errorf("schemaToMap:\ngot :%v\nwant: %v", got, want) 472 } 473} 474 475func TestParseJSONTag(t *testing.T) { 476 for _, tc := range []struct { 477 tag string 478 want jsonTag 479 }{ 480 { 481 tag: "-", 482 want: jsonTag{ignore: true}, 483 }, { 484 tag: "name,omitempty", 485 want: jsonTag{apiName: "name"}, 486 }, { 487 tag: "name,omitempty,string", 488 want: jsonTag{apiName: "name", stringFormat: true}, 489 }, 490 } { 491 got, err := parseJSONTag(tc.tag) 492 if err != nil { 493 t.Fatalf("parsing json:\n got err: %v\ntag: %q", err, tc.tag) 494 } 495 if !reflect.DeepEqual(got, tc.want) { 496 t.Errorf("parseJSONTage:\ngot :%v\nwant:%v", got, tc.want) 497 } 498 } 499} 500func TestParseMalformedJSONTag(t *testing.T) { 501 for _, tag := range []string{ 502 "", 503 "name", 504 "name,", 505 "name,blah", 506 "name,blah,string", 507 ",omitempty", 508 ",omitempty,string", 509 "name,omitempty,string,blah", 510 } { 511 _, err := parseJSONTag(tag) 512 if err == nil { 513 t.Fatalf("parsing json: expected err, got nil for tag: %v", tag) 514 } 515 } 516} 517