github.com/stripe/stripe-go/v76@v76.25.0/form/form_test.go (about) 1 package form 2 3 import ( 4 "fmt" 5 "net/url" 6 "sync" 7 "testing" 8 9 assert "github.com/stretchr/testify/require" 10 ) 11 12 type benchStruct struct { 13 Bool bool `form:"bool"` 14 Ignored string `form:"-"` 15 Int int64 `form:"int64"` 16 String string `form:"string"` 17 SubSubStruct *testSubSubStruct `form:"subsubstruct"` 18 } 19 20 type testStruct struct { 21 // Note that only a pointer can implement the Appender interface, so only 22 // the pointer of testAppender is checked. 23 Appender *testAppender `form:"appender"` 24 25 Array [3]string `form:"array"` 26 ArrayPtr *[3]string `form:"array_ptr"` 27 28 Bool bool `form:"bool"` 29 BoolPtr *bool `form:"bool_ptr"` 30 31 Emptied bool `form:"emptied,empty"` 32 33 Float32 float32 `form:"float32"` 34 Float32Ptr *float32 `form:"float32_ptr"` 35 36 Float32Precise float32 `form:"float32_precise,high_precision"` 37 Float32PrecisePtr *float32 `form:"float32_precise_ptr,high_precision"` 38 39 Float64 float64 `form:"float64"` 40 Float64Ptr *float64 `form:"float64_ptr"` 41 42 Float64Precise float64 `form:"float64_precise,high_precision"` 43 Float64PrecisePtr *float64 `form:"float64_precise_ptr,high_precision"` 44 45 Ignored string `form:"-"` 46 47 Int int `form:"int"` 48 IntPtr *int `form:"int_ptr"` 49 Int8 int8 `form:"int8"` 50 Int8Ptr *int8 `form:"int8_ptr"` 51 Int16 int16 `form:"int16"` 52 Int16Ptr *int16 `form:"int16_ptr"` 53 Int32 int32 `form:"int32"` 54 Int32Ptr *int32 `form:"int32_ptr"` 55 Int64 int64 `form:"int64"` 56 Int64Ptr *int64 `form:"int64_ptr"` 57 58 Map map[string]interface{} `form:"map"` 59 60 Slice []string `form:"slice"` 61 SlicePtr *[]string `form:"slice_ptr"` 62 63 String string `form:"string"` 64 StringPtr *string `form:"string_ptr"` 65 66 SubStruct testSubStruct `form:"substruct"` 67 SubStructPtr *testSubStruct `form:"substruct_ptr"` 68 69 SubStructFlat testSubStruct `form:"*"` 70 SubStructFlatPtr *testSubStruct `form:"*"` 71 72 Uuint uint `form:"uint"` 73 UuintPtr *uint `form:"uint_ptr"` 74 Uuint8 uint8 `form:"uint8"` 75 Uuint8Ptr *uint8 `form:"uint8_ptr"` 76 Uuint16 uint16 `form:"uint16"` 77 Uuint16Ptr *uint16 `form:"uint16_ptr"` 78 Uuint32 uint32 `form:"uint32"` 79 Uuint32Ptr *uint32 `form:"uint32_ptr"` 80 Uuint64 uint64 `form:"uint64"` 81 Uuint64Ptr *uint64 `form:"uint64_ptr"` 82 } 83 84 type testAppender struct { 85 String string `form:"-"` // Value added manually 86 } 87 88 func (a *testAppender) AppendTo(values *Values, keyParts []string) { 89 values.Add(FormatKey(keyParts), a.String) 90 } 91 92 type testSubStruct struct { 93 SubSubStruct testSubSubStruct `form:"subsubstruct"` 94 } 95 96 type testSubSubStruct struct { 97 String string `form:"string"` 98 } 99 100 func init() { 101 Strict = true 102 } 103 104 func BenchmarkAppendTo(b *testing.B) { 105 // Disable strict mode for the duration of the benchmark (most real 106 // installations should not have it turned on) 107 Strict = false 108 defer func() { 109 Strict = true 110 }() 111 112 data := &benchStruct{ 113 Bool: true, 114 Ignored: "123", 115 Int: 123, 116 String: "123", 117 SubSubStruct: &testSubSubStruct{String: "123"}, 118 } 119 120 for i := 0; i < b.N; i++ { 121 form := &Values{} 122 AppendTo(form, data) 123 } 124 } 125 126 func TestAppendTo(t *testing.T) { 127 var arrayVal = [3]string{"1", "2", "3"} 128 var arrayVal0 = [3]string{} 129 130 var boolValT = true 131 var boolValF = false 132 133 var float32Val float32 = 1.2345 134 var float32Val0 float32 135 136 var float32PreciseVal float32 = 0.123456789012 137 138 var float64Val = 1.2345 139 var float64Val0 = 0.0 140 141 var float64PreciseVal = 0.123456789012345678901234 142 143 var intVal = 123 144 var intVal0 = 0 145 var int8Val int8 = 123 146 var int8Val0 int8 147 var int16Val int16 = 123 148 var int16Val0 int16 149 var int32Val int32 = 123 150 var int32Val0 int32 151 var int64Val int64 = 123 152 var int64Val0 int64 153 var sliceVal = []string{"1", "2", "3"} 154 var sliceVal0 = []string{} 155 156 var stringVal = "123" 157 var stringVal0 = "" 158 159 var subStructVal = testSubStruct{ 160 SubSubStruct: testSubSubStruct{ 161 String: "123", 162 }, 163 } 164 165 var uintVal uint = 123 166 var uintVal0 uint 167 var uint8Val uint8 = 123 168 var uint8Val0 uint8 169 var uint16Val uint16 = 123 170 var uint16Val0 uint16 171 var uint32Val uint32 = 123 172 var uint32Val0 uint32 173 var uint64Val uint64 = 123 174 var uint64Val0 uint64 175 176 testCases := []struct { 177 field string 178 data *testStruct 179 180 // Set to a pointer to the desired value or nil if the value shouldn't 181 // be present. 182 want *string 183 }{ 184 {"appender", &testStruct{Appender: &testAppender{String: "123"}}, stringPtr("123")}, 185 186 {"array[2]", &testStruct{Array: arrayVal}, stringPtr("3")}, 187 {"array", &testStruct{Array: arrayVal0}, nil}, 188 189 {"array_ptr[2]", &testStruct{ArrayPtr: &arrayVal}, stringPtr("3")}, 190 {"array_ptr", &testStruct{ArrayPtr: &arrayVal0}, nil}, 191 192 {"bool", &testStruct{Bool: boolValT}, stringPtr("true")}, 193 {"bool_ptr", &testStruct{}, nil}, 194 {"bool_ptr", &testStruct{BoolPtr: &boolValT}, stringPtr("true")}, 195 {"bool_ptr", &testStruct{BoolPtr: &boolValF}, stringPtr("false")}, 196 197 {"emptied", &testStruct{Emptied: true}, stringPtr("")}, 198 199 {"float32", &testStruct{Float32: float32Val}, stringPtr("1.2345")}, 200 {"float32_ptr", &testStruct{Float32Ptr: &float32Val}, stringPtr("1.2345")}, 201 {"float32_ptr", &testStruct{Float32Ptr: &float32Val0}, stringPtr("0.0000")}, 202 {"float32_ptr", &testStruct{}, nil}, 203 204 // Tests float32 with high precision 205 {"float32_precise", &testStruct{Float32Precise: float32PreciseVal}, stringPtr("0.12345679")}, 206 {"float32_precise_ptr", &testStruct{Float32PrecisePtr: &float32PreciseVal}, stringPtr("0.12345679")}, 207 {"float32_precise_ptr", &testStruct{Float32PrecisePtr: &float32Val0}, stringPtr("0")}, 208 {"float32_precise_ptr", &testStruct{}, nil}, 209 210 // The 32-bit test value we're using it already beyond the length of 211 // 32-bit precision. The 64-bit value (which starts with the same 212 // decimals) is well beyond it, and therefore encodes to the same 213 // value. 214 {"float32_precise", &testStruct{Float32Precise: float32(float64PreciseVal)}, stringPtr("0.12345679")}, 215 216 {"float64", &testStruct{Float64: float64Val}, stringPtr("1.2345")}, 217 {"float64_ptr", &testStruct{Float64Ptr: &float64Val}, stringPtr("1.2345")}, 218 {"float64_ptr", &testStruct{Float64Ptr: &float64Val0}, stringPtr("0.0000")}, 219 {"float64_ptr", &testStruct{}, nil}, 220 221 // Tests float64 with high precision 222 {"float64_precise", &testStruct{Float64Precise: float64PreciseVal}, stringPtr("0.12345678901234568")}, 223 {"float64_precise_ptr", &testStruct{Float64PrecisePtr: &float64PreciseVal}, stringPtr("0.12345678901234568")}, 224 {"float64_precise_ptr", &testStruct{Float64PrecisePtr: &float64Val0}, stringPtr("0")}, 225 {"float64_precise_ptr", &testStruct{}, nil}, 226 227 {"int", &testStruct{Int: intVal}, stringPtr("123")}, 228 {"int_ptr", &testStruct{IntPtr: &intVal}, stringPtr("123")}, 229 {"int_ptr", &testStruct{IntPtr: &intVal0}, stringPtr("0")}, 230 {"int_ptr", &testStruct{}, nil}, 231 {"int8", &testStruct{Int8: int8Val}, stringPtr("123")}, 232 {"int8_ptr", &testStruct{Int8Ptr: &int8Val}, stringPtr("123")}, 233 {"int8_ptr", &testStruct{Int8Ptr: &int8Val0}, stringPtr("0")}, 234 {"int8_ptr", &testStruct{}, nil}, 235 {"int16", &testStruct{Int16: int16Val}, stringPtr("123")}, 236 {"int16_ptr", &testStruct{Int16Ptr: &int16Val}, stringPtr("123")}, 237 {"int16_ptr", &testStruct{Int16Ptr: &int16Val0}, stringPtr("0")}, 238 {"int16_ptr", &testStruct{}, nil}, 239 {"int32", &testStruct{Int32: int32Val}, stringPtr("123")}, 240 {"int32_ptr", &testStruct{Int32Ptr: &int32Val}, stringPtr("123")}, 241 {"int32_ptr", &testStruct{Int32Ptr: &int32Val0}, stringPtr("0")}, 242 {"int32_ptr", &testStruct{}, nil}, 243 {"int64", &testStruct{Int64: int64Val}, stringPtr("123")}, 244 {"int64_ptr", &testStruct{Int64Ptr: &int64Val}, stringPtr("123")}, 245 {"int64_ptr", &testStruct{Int64Ptr: &int64Val0}, stringPtr("0")}, 246 {"int64_ptr", &testStruct{}, nil}, 247 248 // Tests map 249 { 250 "map[foo]", 251 &testStruct{Map: map[string]interface{}{ 252 "foo": "bar", 253 }}, 254 stringPtr("bar"), 255 }, 256 257 // Tests map with an empty value 258 { 259 "map[empty]", 260 &testStruct{Map: map[string]interface{}{ 261 // Note that we use an empty integer instead of an empty string 262 // here because `Value`''s `Get` implementation will return an 263 // empty string for an unset value which means that we can't 264 // differentiate between a missing and empty value. The empty 265 // value for int64 is 0, so we can. 266 "empty": int64(0), 267 }}, 268 stringPtr("0"), 269 }, 270 271 // Tests map nested inside of another map 272 { 273 "map[foo][bar]", 274 &testStruct{Map: map[string]interface{}{ 275 "foo": map[string]interface{}{"bar": "baz"}, 276 }}, 277 stringPtr("baz"), 278 }, 279 280 {"slice[2]", &testStruct{Slice: sliceVal}, stringPtr("3")}, 281 {"slice", &testStruct{Slice: []string{}}, stringPtr("")}, 282 {"slice", &testStruct{Slice: nil}, nil}, 283 284 {"slice_ptr[2]", &testStruct{SlicePtr: &sliceVal}, stringPtr("3")}, 285 286 // A slice pointer given an explicit but empty slice should encode to 287 // an empty string (this tells the Stripe API to "zero" the array). 288 {"slice_ptr", &testStruct{SlicePtr: &sliceVal0}, stringPtr("")}, 289 290 {"slice_ptr", &testStruct{SlicePtr: nil}, nil}, 291 292 {"string", &testStruct{String: stringVal}, &stringVal}, 293 {"string_ptr", &testStruct{StringPtr: &stringVal}, &stringVal}, 294 {"string_ptr", &testStruct{StringPtr: &stringVal0}, &stringVal0}, 295 {"string_ptr", &testStruct{}, nil}, 296 297 {"substruct[subsubstruct][string]", &testStruct{SubStruct: subStructVal}, stringPtr("123")}, 298 {"substruct_ptr[subsubstruct][string]", &testStruct{SubStructPtr: &subStructVal}, stringPtr("123")}, 299 300 {"subsubstruct[string]", &testStruct{SubStructFlat: subStructVal}, stringPtr("123")}, 301 {"subsubstruct[string]", &testStruct{SubStructFlatPtr: &subStructVal}, stringPtr("123")}, 302 303 {"uint", &testStruct{Uuint: uintVal}, stringPtr("123")}, 304 {"uint_ptr", &testStruct{UuintPtr: &uintVal}, stringPtr("123")}, 305 {"uint_ptr", &testStruct{UuintPtr: &uintVal0}, stringPtr("0")}, 306 {"uint_ptr", &testStruct{}, nil}, 307 {"uint8", &testStruct{Uuint8: uint8Val}, stringPtr("123")}, 308 {"uint8_ptr", &testStruct{Uuint8Ptr: &uint8Val}, stringPtr("123")}, 309 {"uint8_ptr", &testStruct{Uuint8Ptr: &uint8Val0}, stringPtr("0")}, 310 {"uint8_ptr", &testStruct{}, nil}, 311 {"uint16", &testStruct{Uuint16: uint16Val}, stringPtr("123")}, 312 {"uint16_ptr", &testStruct{Uuint16Ptr: &uint16Val}, stringPtr("123")}, 313 {"uint16_ptr", &testStruct{Uuint16Ptr: &uint16Val0}, stringPtr("0")}, 314 {"uint16_ptr", &testStruct{}, nil}, 315 {"uint32", &testStruct{Uuint32: uint32Val}, stringPtr("123")}, 316 {"uint32_ptr", &testStruct{Uuint32Ptr: &uint32Val}, stringPtr("123")}, 317 {"uint32_ptr", &testStruct{Uuint32Ptr: &uint32Val0}, stringPtr("0")}, 318 {"uint32_ptr", &testStruct{}, nil}, 319 {"uint64", &testStruct{Uuint64: uint64Val}, stringPtr("123")}, 320 {"uint64_ptr", &testStruct{Uuint64Ptr: &uint64Val}, stringPtr("123")}, 321 {"uint64_ptr", &testStruct{Uuint64Ptr: &uint64Val0}, stringPtr("0")}, 322 {"uint64_ptr", &testStruct{}, nil}, 323 } 324 for _, tc := range testCases { 325 t.Run(tc.field, func(t *testing.T) { 326 form := &Values{} 327 AppendTo(form, tc.data) 328 values := form.ToValues() 329 t.Logf("values: %+v encoded: \"%+v\"", values, form.Encode()) 330 331 actuals, ok := values[tc.field] 332 if ok { 333 if tc.want == nil { 334 assert.Fail(t, fmt.Sprintf( 335 "Value not expected for %s, but got '%+v'", 336 tc.field, actuals)) 337 } 338 } else { 339 if tc.want == nil { 340 // Got no value and expected no value 341 return 342 } 343 344 assert.Fail(t, fmt.Sprintf( 345 "No value present for %s, but expected '%+v'", 346 tc.field, *tc.want)) 347 } 348 349 if len(actuals) > 1 { 350 assert.Fail(t, fmt.Sprintf( 351 "Got more than one value for %s: %+v", 352 tc.field, actuals)) 353 } 354 355 actual := actuals[0] 356 assert.Equal(t, *tc.want, actual) 357 }) 358 } 359 } 360 361 func TestAppendTo_IgnoredFields(t *testing.T) { 362 form := &Values{} 363 data := &testStruct{Ignored: "value"} 364 AppendTo(form, data) 365 assert.Equal(t, &Values{}, form) 366 } 367 368 func TestAppendTo_ZeroValues(t *testing.T) { 369 form := &Values{} 370 data := &testStruct{} 371 AppendTo(form, data) 372 assert.Equal(t, &Values{}, form) 373 } 374 375 func TestAppendToPrefixed(t *testing.T) { 376 form := &Values{} 377 data := &testStruct{String: "foo"} 378 AppendToPrefixed(form, data, []string{"prefix"}) 379 assert.Equal(t, []string{"foo"}, form.Get("prefix[string]")) 380 } 381 382 func TestEncode(t *testing.T) { 383 form := &Values{} 384 form.Add("foo", "bar") 385 form.Add("foo[bar]", "baz") 386 assert.Equal(t, "foo=bar&foo[bar]=baz", form.Encode()) 387 } 388 389 func TestEncodeDeterministically(t *testing.T) { 390 data := map[string]string{ 391 "first": "foo", 392 "second": "bar", 393 } 394 initialForm := &Values{} 395 AppendTo(initialForm, data) 396 encoded := initialForm.Encode() 397 398 for i := 0; i < 100; i++ { 399 form := &Values{} 400 AppendTo(form, data) 401 assert.Equal(t, encoded, form.Encode(), "iteration %d", i) 402 } 403 } 404 405 func TestEncodeMapNonStringKey(t *testing.T) { 406 // Disable strict mode for this test, so the non-string key is ignored. 407 Strict = false 408 defer func() { 409 Strict = true 410 }() 411 412 form := &Values{} 413 assert.NotPanics(t, func() { 414 AppendTo(form, map[int]string{1: "foo"}) 415 }) 416 assert.Len(t, form.values, 0) 417 } 418 419 func TestFormatKey(t *testing.T) { 420 assert.Equal(t, "param", FormatKey([]string{"param"})) 421 assert.Equal(t, "param[key]", FormatKey([]string{"param", "key"})) 422 assert.Equal(t, "param[key][]", FormatKey([]string{"param", "key", ""})) 423 assert.Equal(t, "param[key][0]", FormatKey([]string{"param", "key", "0"})) 424 } 425 426 // The encoder uses a type cache for speed. This test is designed to help 427 // verify that concurrent access to it is safe. 428 // 429 // I had good success in reproducing concurrent access errors on my computer 430 // using this test, but given that we're just throwing lots of Goroutines out 431 // there and hoping for an error, your mileage may vary depending on your 432 // system. It may be necessary to increase the value of `n` or introduce other 433 // constructs (although hopefully this package will stay concurrency-safe). 434 func TestCacheConcurrency(t *testing.T) { 435 // Clear out anything in the existing cache 436 encoderCache.m = nil 437 structCache.m = nil 438 439 var wg sync.WaitGroup 440 n := 10 441 val := &testStruct{String: "123"} 442 443 for i := 0; i < n; i++ { 444 wg.Add(1) 445 go func() { 446 form := &Values{} 447 AppendTo(form, val) 448 wg.Done() 449 }() 450 } 451 452 wg.Wait() 453 } 454 455 func TestParseTag(t *testing.T) { 456 // Disable strict mode for the duration of this test so that we can test 457 // some malformed tags 458 Strict = false 459 defer func() { 460 Strict = true 461 }() 462 463 testCases := []struct { 464 tag string 465 wantName string 466 wantOptions *formOptions 467 }{ 468 {"id", "id", nil}, 469 {"id,empty", "id", &formOptions{Empty: true}}, 470 471 // invalid invocations 472 {"id,", "id", nil}, 473 {"id,,", "id", nil}, 474 {"id,foo", "id", nil}, 475 {"id,foo=bar", "id", nil}, 476 } 477 for _, tc := range testCases { 478 t.Run(tc.tag, func(t *testing.T) { 479 name, options := parseTag(tc.tag) 480 assert.Equal(t, tc.wantName, name) 481 assert.Equal(t, tc.wantOptions, options) 482 }) 483 } 484 } 485 486 func TestValues(t *testing.T) { 487 values := &Values{} 488 489 assert.Equal(t, "", values.Encode()) 490 assert.True(t, values.Empty()) 491 492 values = &Values{} 493 values.Add("foo", "bar") 494 495 assert.Equal(t, "foo=bar", values.Encode()) 496 assert.False(t, values.Empty()) 497 assert.Equal(t, []string{"bar"}, values.Get("foo")) 498 499 values = &Values{} 500 values.Add("foo", "bar") 501 values.Add("foo", "bar") 502 values.Add("baz", "bar") 503 504 assert.Equal(t, "foo=bar&foo=bar&baz=bar", values.Encode()) 505 assert.Equal(t, []string{"bar", "bar"}, values.Get("foo")) 506 assert.Equal(t, []string{"bar"}, values.Get("baz")) 507 508 values.Set("foo", "firstbar") 509 510 assert.Equal(t, "foo=firstbar&foo=bar&baz=bar", values.Encode()) 511 assert.Equal(t, []string{"firstbar", "bar"}, values.Get("foo")) 512 assert.Equal(t, []string{"bar"}, values.Get("baz")) 513 514 values.Set("new", "appended") 515 516 assert.Equal(t, "foo=firstbar&foo=bar&baz=bar&new=appended", values.Encode()) 517 518 assert.Equal(t, url.Values{ 519 "baz": {"bar"}, 520 "foo": {"firstbar", "bar"}, 521 "new": {"appended"}, 522 }, values.ToValues()) 523 assert.Equal(t, []string{"appended"}, values.Get("new")) 524 525 assert.Nil(t, values.Get("boguskey")) 526 } 527 528 // 529 // Private functions 530 // 531 532 // Trivial helper to get is a string pointer from a string (because you're not 533 // allowed to take the address of a literal directly). 534 func stringPtr(s string) *string { 535 return &s 536 }