github.com/hashicorp/vault/sdk@v0.13.0/framework/field_data_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package framework 5 6 import ( 7 "encoding/json" 8 "net/http" 9 "reflect" 10 "testing" 11 "time" 12 ) 13 14 func TestFieldDataGet(t *testing.T) { 15 cases := map[string]struct { 16 Schema map[string]*FieldSchema 17 Raw map[string]interface{} 18 Key string 19 Value interface{} 20 ExpectError bool 21 }{ 22 "string type, string value": { 23 map[string]*FieldSchema{ 24 "foo": {Type: TypeString}, 25 }, 26 map[string]interface{}{ 27 "foo": "bar", 28 }, 29 "foo", 30 "bar", 31 false, 32 }, 33 34 "string type, int value": { 35 map[string]*FieldSchema{ 36 "foo": {Type: TypeString}, 37 }, 38 map[string]interface{}{ 39 "foo": 42, 40 }, 41 "foo", 42 "42", 43 false, 44 }, 45 46 "string type, unset value": { 47 map[string]*FieldSchema{ 48 "foo": {Type: TypeString}, 49 }, 50 map[string]interface{}{}, 51 "foo", 52 "", 53 false, 54 }, 55 56 "string type, unset value with default": { 57 map[string]*FieldSchema{ 58 "foo": { 59 Type: TypeString, 60 Default: "bar", 61 }, 62 }, 63 map[string]interface{}{}, 64 "foo", 65 "bar", 66 false, 67 }, 68 69 "lowercase string type, lowercase string value": { 70 map[string]*FieldSchema{ 71 "foo": {Type: TypeLowerCaseString}, 72 }, 73 map[string]interface{}{ 74 "foo": "bar", 75 }, 76 "foo", 77 "bar", 78 false, 79 }, 80 81 "lowercase string type, mixed-case string value": { 82 map[string]*FieldSchema{ 83 "foo": {Type: TypeLowerCaseString}, 84 }, 85 map[string]interface{}{ 86 "foo": "BaR", 87 }, 88 "foo", 89 "bar", 90 false, 91 }, 92 93 "lowercase string type, int value": { 94 map[string]*FieldSchema{ 95 "foo": {Type: TypeLowerCaseString}, 96 }, 97 map[string]interface{}{ 98 "foo": 42, 99 }, 100 "foo", 101 "42", 102 false, 103 }, 104 105 "lowercase string type, unset value": { 106 map[string]*FieldSchema{ 107 "foo": {Type: TypeLowerCaseString}, 108 }, 109 map[string]interface{}{}, 110 "foo", 111 "", 112 false, 113 }, 114 115 "lowercase string type, unset value with lowercase default": { 116 map[string]*FieldSchema{ 117 "foo": { 118 Type: TypeLowerCaseString, 119 Default: "bar", 120 }, 121 }, 122 map[string]interface{}{}, 123 "foo", 124 "bar", 125 false, 126 }, 127 128 "int type, int value": { 129 map[string]*FieldSchema{ 130 "foo": {Type: TypeInt}, 131 }, 132 map[string]interface{}{ 133 "foo": 42, 134 }, 135 "foo", 136 42, 137 false, 138 }, 139 140 "bool type, bool value": { 141 map[string]*FieldSchema{ 142 "foo": {Type: TypeBool}, 143 }, 144 map[string]interface{}{ 145 "foo": false, 146 }, 147 "foo", 148 false, 149 false, 150 }, 151 152 "map type, map value": { 153 map[string]*FieldSchema{ 154 "foo": {Type: TypeMap}, 155 }, 156 map[string]interface{}{ 157 "foo": map[string]interface{}{ 158 "child": true, 159 }, 160 }, 161 "foo", 162 map[string]interface{}{ 163 "child": true, 164 }, 165 false, 166 }, 167 168 "duration type, string value": { 169 map[string]*FieldSchema{ 170 "foo": {Type: TypeDurationSecond}, 171 }, 172 map[string]interface{}{ 173 "foo": "42", 174 }, 175 "foo", 176 42, 177 false, 178 }, 179 180 "duration type, string duration value": { 181 map[string]*FieldSchema{ 182 "foo": {Type: TypeDurationSecond}, 183 }, 184 map[string]interface{}{ 185 "foo": "42m", 186 }, 187 "foo", 188 2520, 189 false, 190 }, 191 192 "duration type, int value": { 193 map[string]*FieldSchema{ 194 "foo": {Type: TypeDurationSecond}, 195 }, 196 map[string]interface{}{ 197 "foo": 42, 198 }, 199 "foo", 200 42, 201 false, 202 }, 203 204 "duration type, float value": { 205 map[string]*FieldSchema{ 206 "foo": {Type: TypeDurationSecond}, 207 }, 208 map[string]interface{}{ 209 "foo": 42.0, 210 }, 211 "foo", 212 42, 213 false, 214 }, 215 216 "duration type, nil value": { 217 map[string]*FieldSchema{ 218 "foo": {Type: TypeDurationSecond}, 219 }, 220 map[string]interface{}{ 221 "foo": nil, 222 }, 223 "foo", 224 0, 225 false, 226 }, 227 228 "duration type, 0 value": { 229 map[string]*FieldSchema{ 230 "foo": {Type: TypeDurationSecond}, 231 }, 232 map[string]interface{}{ 233 "foo": 0, 234 }, 235 "foo", 236 0, 237 false, 238 }, 239 240 "signed duration type, positive string value": { 241 map[string]*FieldSchema{ 242 "foo": {Type: TypeSignedDurationSecond}, 243 }, 244 map[string]interface{}{ 245 "foo": "42", 246 }, 247 "foo", 248 42, 249 false, 250 }, 251 252 "signed duration type, positive string duration value": { 253 map[string]*FieldSchema{ 254 "foo": {Type: TypeSignedDurationSecond}, 255 }, 256 map[string]interface{}{ 257 "foo": "42m", 258 }, 259 "foo", 260 2520, 261 false, 262 }, 263 264 "signed duration type, positive int value": { 265 map[string]*FieldSchema{ 266 "foo": {Type: TypeSignedDurationSecond}, 267 }, 268 map[string]interface{}{ 269 "foo": 42, 270 }, 271 "foo", 272 42, 273 false, 274 }, 275 276 "signed duration type, positive float value": { 277 map[string]*FieldSchema{ 278 "foo": {Type: TypeSignedDurationSecond}, 279 }, 280 map[string]interface{}{ 281 "foo": 42.0, 282 }, 283 "foo", 284 42, 285 false, 286 }, 287 288 "signed duration type, negative string value": { 289 map[string]*FieldSchema{ 290 "foo": {Type: TypeSignedDurationSecond}, 291 }, 292 map[string]interface{}{ 293 "foo": "-42", 294 }, 295 "foo", 296 -42, 297 false, 298 }, 299 300 "signed duration type, negative string duration value": { 301 map[string]*FieldSchema{ 302 "foo": {Type: TypeSignedDurationSecond}, 303 }, 304 map[string]interface{}{ 305 "foo": "-42m", 306 }, 307 "foo", 308 -2520, 309 false, 310 }, 311 312 "signed duration type, negative int value": { 313 map[string]*FieldSchema{ 314 "foo": {Type: TypeSignedDurationSecond}, 315 }, 316 map[string]interface{}{ 317 "foo": -42, 318 }, 319 "foo", 320 -42, 321 false, 322 }, 323 324 "signed duration type, negative float value": { 325 map[string]*FieldSchema{ 326 "foo": {Type: TypeSignedDurationSecond}, 327 }, 328 map[string]interface{}{ 329 "foo": -42.0, 330 }, 331 "foo", 332 -42, 333 false, 334 }, 335 336 "signed duration type, nil value": { 337 map[string]*FieldSchema{ 338 "foo": {Type: TypeSignedDurationSecond}, 339 }, 340 map[string]interface{}{ 341 "foo": nil, 342 }, 343 "foo", 344 0, 345 false, 346 }, 347 348 "signed duration type, 0 value": { 349 map[string]*FieldSchema{ 350 "foo": {Type: TypeSignedDurationSecond}, 351 }, 352 map[string]interface{}{ 353 "foo": 0, 354 }, 355 "foo", 356 0, 357 false, 358 }, 359 360 "slice type, empty slice": { 361 map[string]*FieldSchema{ 362 "foo": {Type: TypeSlice}, 363 }, 364 map[string]interface{}{ 365 "foo": []interface{}{}, 366 }, 367 "foo", 368 []interface{}{}, 369 false, 370 }, 371 372 "slice type, filled, mixed slice": { 373 map[string]*FieldSchema{ 374 "foo": {Type: TypeSlice}, 375 }, 376 map[string]interface{}{ 377 "foo": []interface{}{123, "abc"}, 378 }, 379 "foo", 380 []interface{}{123, "abc"}, 381 false, 382 }, 383 384 "string slice type, filled slice": { 385 map[string]*FieldSchema{ 386 "foo": {Type: TypeStringSlice}, 387 }, 388 map[string]interface{}{ 389 "foo": []interface{}{123, "abc"}, 390 }, 391 "foo", 392 []string{"123", "abc"}, 393 false, 394 }, 395 396 "string slice type, single value": { 397 map[string]*FieldSchema{ 398 "foo": {Type: TypeStringSlice}, 399 }, 400 map[string]interface{}{ 401 "foo": "abc", 402 }, 403 "foo", 404 []string{"abc"}, 405 false, 406 }, 407 408 "string slice type, empty string": { 409 map[string]*FieldSchema{ 410 "foo": {Type: TypeStringSlice}, 411 }, 412 map[string]interface{}{ 413 "foo": "", 414 }, 415 "foo", 416 []string{}, 417 false, 418 }, 419 420 "comma string slice type, empty string": { 421 map[string]*FieldSchema{ 422 "foo": {Type: TypeCommaStringSlice}, 423 }, 424 map[string]interface{}{ 425 "foo": "", 426 }, 427 "foo", 428 []string{}, 429 false, 430 }, 431 432 "comma string slice type, comma string with one value": { 433 map[string]*FieldSchema{ 434 "foo": {Type: TypeCommaStringSlice}, 435 }, 436 map[string]interface{}{ 437 "foo": "value1", 438 }, 439 "foo", 440 []string{"value1"}, 441 false, 442 }, 443 444 "comma string slice type, comma string with multi value": { 445 map[string]*FieldSchema{ 446 "foo": {Type: TypeCommaStringSlice}, 447 }, 448 map[string]interface{}{ 449 "foo": "value1,value2,value3", 450 }, 451 "foo", 452 []string{"value1", "value2", "value3"}, 453 false, 454 }, 455 456 "comma string slice type, nil string slice value": { 457 map[string]*FieldSchema{ 458 "foo": {Type: TypeCommaStringSlice}, 459 }, 460 map[string]interface{}{ 461 "foo": "", 462 }, 463 "foo", 464 []string{}, 465 false, 466 }, 467 468 "comma string slice type, string slice with one value": { 469 map[string]*FieldSchema{ 470 "foo": {Type: TypeCommaStringSlice}, 471 }, 472 map[string]interface{}{ 473 "foo": []interface{}{"value1"}, 474 }, 475 "foo", 476 []string{"value1"}, 477 false, 478 }, 479 480 "comma string slice type, string slice with multi value": { 481 map[string]*FieldSchema{ 482 "foo": {Type: TypeCommaStringSlice}, 483 }, 484 map[string]interface{}{ 485 "foo": []interface{}{"value1", "value2", "value3"}, 486 }, 487 "foo", 488 []string{"value1", "value2", "value3"}, 489 false, 490 }, 491 492 "comma string slice type, empty string slice value": { 493 map[string]*FieldSchema{ 494 "foo": {Type: TypeCommaStringSlice}, 495 }, 496 map[string]interface{}{ 497 "foo": []interface{}{}, 498 }, 499 "foo", 500 []string{}, 501 false, 502 }, 503 504 "comma int slice type, comma int with one value": { 505 map[string]*FieldSchema{ 506 "foo": {Type: TypeCommaIntSlice}, 507 }, 508 map[string]interface{}{ 509 "foo": 1, 510 }, 511 "foo", 512 []int{1}, 513 false, 514 }, 515 516 "comma int slice type, comma int with multi value slice": { 517 map[string]*FieldSchema{ 518 "foo": {Type: TypeCommaIntSlice}, 519 }, 520 map[string]interface{}{ 521 "foo": []int{1, 2, 3}, 522 }, 523 "foo", 524 []int{1, 2, 3}, 525 false, 526 }, 527 528 "comma int slice type, comma int with multi value": { 529 map[string]*FieldSchema{ 530 "foo": {Type: TypeCommaIntSlice}, 531 }, 532 map[string]interface{}{ 533 "foo": "1,2,3", 534 }, 535 "foo", 536 []int{1, 2, 3}, 537 false, 538 }, 539 540 "comma int slice type, nil int slice value": { 541 map[string]*FieldSchema{ 542 "foo": {Type: TypeCommaIntSlice}, 543 }, 544 map[string]interface{}{ 545 "foo": "", 546 }, 547 "foo", 548 []int{}, 549 false, 550 }, 551 552 "comma int slice type, int slice with one value": { 553 map[string]*FieldSchema{ 554 "foo": {Type: TypeCommaIntSlice}, 555 }, 556 map[string]interface{}{ 557 "foo": []interface{}{"1"}, 558 }, 559 "foo", 560 []int{1}, 561 false, 562 }, 563 564 "comma int slice type, int slice with multi value strings": { 565 map[string]*FieldSchema{ 566 "foo": {Type: TypeCommaIntSlice}, 567 }, 568 map[string]interface{}{ 569 "foo": []interface{}{"1", "2", "3"}, 570 }, 571 "foo", 572 []int{1, 2, 3}, 573 false, 574 }, 575 576 "comma int slice type, int slice with multi value": { 577 map[string]*FieldSchema{ 578 "foo": {Type: TypeCommaIntSlice}, 579 }, 580 map[string]interface{}{ 581 "foo": []interface{}{1, 2, 3}, 582 }, 583 "foo", 584 []int{1, 2, 3}, 585 false, 586 }, 587 588 "comma int slice type, empty int slice value": { 589 map[string]*FieldSchema{ 590 "foo": {Type: TypeCommaIntSlice}, 591 }, 592 map[string]interface{}{ 593 "foo": []interface{}{}, 594 }, 595 "foo", 596 []int{}, 597 false, 598 }, 599 600 "comma int slice type, json number": { 601 map[string]*FieldSchema{ 602 "foo": {Type: TypeCommaIntSlice}, 603 }, 604 map[string]interface{}{ 605 "foo": json.Number("1"), 606 }, 607 "foo", 608 []int{1}, 609 false, 610 }, 611 612 "name string type, valid string": { 613 map[string]*FieldSchema{ 614 "foo": {Type: TypeNameString}, 615 }, 616 map[string]interface{}{ 617 "foo": "bar", 618 }, 619 "foo", 620 "bar", 621 false, 622 }, 623 624 "name string type, valid value with special characters": { 625 map[string]*FieldSchema{ 626 "foo": {Type: TypeNameString}, 627 }, 628 map[string]interface{}{ 629 "foo": "bar.baz-bay123", 630 }, 631 "foo", 632 "bar.baz-bay123", 633 false, 634 }, 635 636 "keypair type, valid value map type": { 637 map[string]*FieldSchema{ 638 "foo": {Type: TypeKVPairs}, 639 }, 640 map[string]interface{}{ 641 "foo": map[string]interface{}{ 642 "key1": "value1", 643 "key2": "value2", 644 "key3": 1, 645 }, 646 }, 647 "foo", 648 map[string]string{ 649 "key1": "value1", 650 "key2": "value2", 651 "key3": "1", 652 }, 653 false, 654 }, 655 656 "keypair type, list of equal sign delim key pairs type": { 657 map[string]*FieldSchema{ 658 "foo": {Type: TypeKVPairs}, 659 }, 660 map[string]interface{}{ 661 "foo": []interface{}{"key1=value1", "key2=value2", "key3=1"}, 662 }, 663 "foo", 664 map[string]string{ 665 "key1": "value1", 666 "key2": "value2", 667 "key3": "1", 668 }, 669 false, 670 }, 671 672 "keypair type, single equal sign delim value": { 673 map[string]*FieldSchema{ 674 "foo": {Type: TypeKVPairs}, 675 }, 676 map[string]interface{}{ 677 "foo": "key1=value1", 678 }, 679 "foo", 680 map[string]string{ 681 "key1": "value1", 682 }, 683 false, 684 }, 685 686 "type header, keypair string array": { 687 map[string]*FieldSchema{ 688 "foo": {Type: TypeHeader}, 689 }, 690 map[string]interface{}{ 691 "foo": []interface{}{"key1:value1", "key2:value2", "key3:1"}, 692 }, 693 "foo", 694 http.Header{ 695 "Key1": []string{"value1"}, 696 "Key2": []string{"value2"}, 697 "Key3": []string{"1"}, 698 }, 699 false, 700 }, 701 702 "type header, b64 string": { 703 map[string]*FieldSchema{ 704 "foo": {Type: TypeHeader}, 705 }, 706 map[string]interface{}{ 707 "foo": "eyJDb250ZW50LUxlbmd0aCI6IFsiNDMiXSwgIlVzZXItQWdlbnQiOiBbImF3cy1zZGstZ28vMS40LjEyIChnbzEuNy4xOyBsaW51eDsgYW1kNjQpIl0sICJYLVZhdWx0LUFXU0lBTS1TZXJ2ZXItSWQiOiBbInZhdWx0LmV4YW1wbGUuY29tIl0sICJYLUFtei1EYXRlIjogWyIyMDE2MDkzMFQwNDMxMjFaIl0sICJDb250ZW50LVR5cGUiOiBbImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZDsgY2hhcnNldD11dGYtOCJdLCAiQXV0aG9yaXphdGlvbiI6IFsiQVdTNC1ITUFDLVNIQTI1NiBDcmVkZW50aWFsPWZvby8yMDE2MDkzMC91cy1lYXN0LTEvc3RzL2F3czRfcmVxdWVzdCwgU2lnbmVkSGVhZGVycz1jb250ZW50LWxlbmd0aDtjb250ZW50LXR5cGU7aG9zdDt4LWFtei1kYXRlO3gtdmF1bHQtc2VydmVyLCBTaWduYXR1cmU9YTY5ZmQ3NTBhMzQ0NWM0ZTU1M2UxYjNlNzlkM2RhOTBlZWY1NDA0N2YxZWI0ZWZlOGZmYmM5YzQyOGMyNjU1YiJdLCAiRm9vIjogNDJ9", 708 }, 709 "foo", 710 http.Header{ 711 "Content-Length": []string{"43"}, 712 "User-Agent": []string{"aws-sdk-go/1.4.12 (go1.7.1; linux; amd64)"}, 713 "X-Vault-Awsiam-Server-Id": []string{"vault.example.com"}, 714 "X-Amz-Date": []string{"20160930T043121Z"}, 715 "Content-Type": []string{"application/x-www-form-urlencoded; charset=utf-8"}, 716 "Authorization": []string{"AWS4-HMAC-SHA256 Credential=foo/20160930/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-server, Signature=a69fd750a3445c4e553e1b3e79d3da90eef54047f1eb4efe8ffbc9c428c2655b"}, 717 "Foo": []string{"42"}, 718 }, 719 false, 720 }, 721 722 "type header, json string": { 723 map[string]*FieldSchema{ 724 "foo": {Type: TypeHeader}, 725 }, 726 map[string]interface{}{ 727 "foo": `{"hello":"world","bonjour":["monde","dieu"], "Guten Tag": 42, "你好": ["10", 20, 3.14]}`, 728 }, 729 "foo", 730 http.Header{ 731 "Hello": []string{"world"}, 732 "Bonjour": []string{"monde", "dieu"}, 733 "Guten Tag": []string{"42"}, 734 "你好": []string{"10", "20", "3.14"}, 735 }, 736 false, 737 }, 738 739 "type header, keypair string array with dupe key": { 740 map[string]*FieldSchema{ 741 "foo": {Type: TypeHeader}, 742 }, 743 map[string]interface{}{ 744 "foo": []interface{}{"key1:value1", "key2:value2", "key3:1", "key3:true"}, 745 }, 746 "foo", 747 http.Header{ 748 "Key1": []string{"value1"}, 749 "Key2": []string{"value2"}, 750 "Key3": []string{"1", "true"}, 751 }, 752 false, 753 }, 754 755 "type header, map string slice": { 756 map[string]*FieldSchema{ 757 "foo": {Type: TypeHeader}, 758 }, 759 map[string]interface{}{ 760 "foo": map[string][]string{ 761 "key1": {"value1"}, 762 "key2": {"value2"}, 763 "key3": {"1"}, 764 }, 765 }, 766 "foo", 767 http.Header{ 768 "Key1": []string{"value1"}, 769 "Key2": []string{"value2"}, 770 "Key3": []string{"1"}, 771 }, 772 false, 773 }, 774 775 "name string type, not supplied": { 776 map[string]*FieldSchema{ 777 "foo": {Type: TypeNameString}, 778 }, 779 map[string]interface{}{}, 780 "foo", 781 "", 782 false, 783 }, 784 785 "string type, not supplied": { 786 map[string]*FieldSchema{ 787 "foo": {Type: TypeString}, 788 }, 789 map[string]interface{}{}, 790 "foo", 791 "", 792 false, 793 }, 794 795 "type int, not supplied": { 796 map[string]*FieldSchema{ 797 "foo": {Type: TypeInt}, 798 }, 799 map[string]interface{}{}, 800 "foo", 801 0, 802 false, 803 }, 804 805 "type bool, not supplied": { 806 map[string]*FieldSchema{ 807 "foo": {Type: TypeBool}, 808 }, 809 map[string]interface{}{}, 810 "foo", 811 false, 812 false, 813 }, 814 815 "type map, not supplied": { 816 map[string]*FieldSchema{ 817 "foo": {Type: TypeMap}, 818 }, 819 map[string]interface{}{}, 820 "foo", 821 map[string]interface{}{}, 822 false, 823 }, 824 825 "type duration second, not supplied": { 826 map[string]*FieldSchema{ 827 "foo": {Type: TypeDurationSecond}, 828 }, 829 map[string]interface{}{}, 830 "foo", 831 0, 832 false, 833 }, 834 835 "type signed duration second, not supplied": { 836 map[string]*FieldSchema{ 837 "foo": {Type: TypeSignedDurationSecond}, 838 }, 839 map[string]interface{}{}, 840 "foo", 841 0, 842 false, 843 }, 844 845 "type slice, not supplied": { 846 map[string]*FieldSchema{ 847 "foo": {Type: TypeSlice}, 848 }, 849 map[string]interface{}{}, 850 "foo", 851 []interface{}{}, 852 false, 853 }, 854 855 "type string slice, not supplied": { 856 map[string]*FieldSchema{ 857 "foo": {Type: TypeStringSlice}, 858 }, 859 map[string]interface{}{}, 860 "foo", 861 []string{}, 862 false, 863 }, 864 865 "type comma string slice, not supplied": { 866 map[string]*FieldSchema{ 867 "foo": {Type: TypeCommaStringSlice}, 868 }, 869 map[string]interface{}{}, 870 "foo", 871 []string{}, 872 false, 873 }, 874 875 "comma string slice type, single JSON number value": { 876 map[string]*FieldSchema{ 877 "foo": {Type: TypeCommaStringSlice}, 878 }, 879 map[string]interface{}{ 880 "foo": json.Number("123"), 881 }, 882 "foo", 883 []string{"123"}, 884 false, 885 }, 886 887 "type kv pair, not supplied": { 888 map[string]*FieldSchema{ 889 "foo": {Type: TypeKVPairs}, 890 }, 891 map[string]interface{}{}, 892 "foo", 893 map[string]string{}, 894 false, 895 }, 896 897 "type header, not supplied": { 898 map[string]*FieldSchema{ 899 "foo": {Type: TypeHeader}, 900 }, 901 map[string]interface{}{}, 902 "foo", 903 http.Header{}, 904 false, 905 }, 906 907 "float type, positive with decimals, as string": { 908 map[string]*FieldSchema{ 909 "foo": {Type: TypeFloat}, 910 }, 911 map[string]interface{}{ 912 "foo": "1234567.891234567", 913 }, 914 "foo", 915 1234567.891234567, 916 false, 917 }, 918 919 "float type, negative with decimals, as string": { 920 map[string]*FieldSchema{ 921 "foo": {Type: TypeFloat}, 922 }, 923 map[string]interface{}{ 924 "foo": "-1234567.891234567", 925 }, 926 "foo", 927 -1234567.891234567, 928 false, 929 }, 930 931 "float type, positive without decimals": { 932 map[string]*FieldSchema{ 933 "foo": {Type: TypeFloat}, 934 }, 935 map[string]interface{}{ 936 "foo": 1234567, 937 }, 938 "foo", 939 1234567.0, 940 false, 941 }, 942 943 "type float, not supplied": { 944 map[string]*FieldSchema{ 945 "foo": {Type: TypeFloat}, 946 }, 947 map[string]interface{}{}, 948 "foo", 949 0.0, 950 false, 951 }, 952 953 "type float, invalid value": { 954 map[string]*FieldSchema{ 955 "foo": {Type: TypeFloat}, 956 }, 957 map[string]interface{}{ 958 "foo": "invalid0.0", 959 }, 960 "foo", 961 0.0, 962 true, 963 }, 964 965 "type time, not supplied": { 966 map[string]*FieldSchema{ 967 "foo": {Type: TypeTime}, 968 }, 969 map[string]interface{}{}, 970 "foo", 971 time.Time{}, 972 false, 973 }, 974 "type time, string value": { 975 map[string]*FieldSchema{ 976 "foo": {Type: TypeTime}, 977 }, 978 map[string]interface{}{ 979 "foo": "2021-12-11T09:08:07Z", 980 }, 981 "foo", 982 // Comparison uses DeepEqual() so better match exactly, 983 // can't have a different location. 984 time.Date(2021, 12, 11, 9, 8, 7, 0, time.UTC), 985 false, 986 }, 987 "type time, invalid value": { 988 map[string]*FieldSchema{ 989 "foo": {Type: TypeTime}, 990 }, 991 map[string]interface{}{ 992 "foo": "2021-13-11T09:08:07+02:00", 993 }, 994 "foo", 995 time.Time{}, 996 true, 997 }, 998 } 999 1000 for name, tc := range cases { 1001 name, tc := name, tc 1002 t.Run(name, func(t *testing.T) { 1003 t.Parallel() 1004 data := &FieldData{ 1005 Raw: tc.Raw, 1006 Schema: tc.Schema, 1007 } 1008 1009 err := data.Validate() 1010 switch { 1011 case tc.ExpectError && err == nil: 1012 t.Fatalf("expected error") 1013 case tc.ExpectError && err != nil: 1014 return 1015 case !tc.ExpectError && err != nil: 1016 t.Fatal(err) 1017 default: 1018 // Continue if !tc.ExpectError && err == nil 1019 } 1020 1021 actual := data.Get(tc.Key) 1022 if !reflect.DeepEqual(actual, tc.Value) { 1023 t.Fatalf("Expected: %#v\nGot: %#v", tc.Value, actual) 1024 } 1025 }) 1026 } 1027 } 1028 1029 func TestFieldDataGet_Error(t *testing.T) { 1030 cases := map[string]struct { 1031 Schema map[string]*FieldSchema 1032 Raw map[string]interface{} 1033 Key string 1034 }{ 1035 "name string type, invalid value with invalid characters": { 1036 map[string]*FieldSchema{ 1037 "foo": {Type: TypeNameString}, 1038 }, 1039 map[string]interface{}{ 1040 "foo": "bar baz", 1041 }, 1042 "foo", 1043 }, 1044 "name string type, invalid value with special characters at beginning": { 1045 map[string]*FieldSchema{ 1046 "foo": {Type: TypeNameString}, 1047 }, 1048 map[string]interface{}{ 1049 "foo": ".barbaz", 1050 }, 1051 "foo", 1052 }, 1053 "name string type, invalid value with special characters at end": { 1054 map[string]*FieldSchema{ 1055 "foo": {Type: TypeNameString}, 1056 }, 1057 map[string]interface{}{ 1058 "foo": "barbaz-", 1059 }, 1060 "foo", 1061 }, 1062 "name string type, empty string": { 1063 map[string]*FieldSchema{ 1064 "foo": {Type: TypeNameString}, 1065 }, 1066 map[string]interface{}{ 1067 "foo": "", 1068 }, 1069 "foo", 1070 }, 1071 "keypair type, csv version empty key name": { 1072 map[string]*FieldSchema{ 1073 "foo": {Type: TypeKVPairs}, 1074 }, 1075 map[string]interface{}{ 1076 "foo": []interface{}{"=value1", "key2=value2", "key3=1"}, 1077 }, 1078 "foo", 1079 }, 1080 "duration type, negative string value": { 1081 map[string]*FieldSchema{ 1082 "foo": {Type: TypeDurationSecond}, 1083 }, 1084 map[string]interface{}{ 1085 "foo": "-42", 1086 }, 1087 "foo", 1088 }, 1089 "duration type, negative string duration value": { 1090 map[string]*FieldSchema{ 1091 "foo": {Type: TypeDurationSecond}, 1092 }, 1093 map[string]interface{}{ 1094 "foo": "-42m", 1095 }, 1096 "foo", 1097 }, 1098 "duration type, negative int value": { 1099 map[string]*FieldSchema{ 1100 "foo": {Type: TypeDurationSecond}, 1101 }, 1102 map[string]interface{}{ 1103 "foo": -42, 1104 }, 1105 "foo", 1106 }, 1107 "duration type, negative float value": { 1108 map[string]*FieldSchema{ 1109 "foo": {Type: TypeDurationSecond}, 1110 }, 1111 map[string]interface{}{ 1112 "foo": -42.0, 1113 }, 1114 "foo", 1115 }, 1116 } 1117 1118 for name, tc := range cases { 1119 name, tc := name, tc 1120 t.Run(name, func(t *testing.T) { 1121 t.Parallel() 1122 data := &FieldData{ 1123 Raw: tc.Raw, 1124 Schema: tc.Schema, 1125 } 1126 1127 got, _, err := data.GetOkErr(tc.Key) 1128 if err == nil { 1129 t.Fatalf("error expected, none received, got result: %#v", got) 1130 } 1131 }) 1132 } 1133 } 1134 1135 func TestFieldDataGetFirst(t *testing.T) { 1136 data := &FieldData{ 1137 Raw: map[string]interface{}{ 1138 "foo": "bar", 1139 "fizz": "buzz", 1140 }, 1141 Schema: map[string]*FieldSchema{ 1142 "foo": {Type: TypeNameString}, 1143 "fizz": {Type: TypeNameString}, 1144 }, 1145 } 1146 1147 result, ok := data.GetFirst("foo", "fizz") 1148 if !ok { 1149 t.Fatal("should have found value for foo") 1150 } 1151 if result.(string) != "bar" { 1152 t.Fatal("should have gotten bar for foo") 1153 } 1154 1155 result, ok = data.GetFirst("fizz", "foo") 1156 if !ok { 1157 t.Fatal("should have found value for fizz") 1158 } 1159 if result.(string) != "buzz" { 1160 t.Fatal("should have gotten buzz for fizz") 1161 } 1162 1163 _, ok = data.GetFirst("cats") 1164 if ok { 1165 t.Fatal("shouldn't have gotten anything for cats") 1166 } 1167 } 1168 1169 func TestValidateStrict(t *testing.T) { 1170 cases := map[string]struct { 1171 Schema map[string]*FieldSchema 1172 Raw map[string]interface{} 1173 ExpectError bool 1174 }{ 1175 "string type, string value": { 1176 map[string]*FieldSchema{ 1177 "foo": {Type: TypeString}, 1178 }, 1179 map[string]interface{}{ 1180 "foo": "bar", 1181 }, 1182 false, 1183 }, 1184 1185 "string type, int value": { 1186 map[string]*FieldSchema{ 1187 "foo": {Type: TypeString}, 1188 }, 1189 map[string]interface{}{ 1190 "foo": 42, 1191 }, 1192 false, 1193 }, 1194 1195 "string type, unset value": { 1196 map[string]*FieldSchema{ 1197 "foo": {Type: TypeString}, 1198 }, 1199 map[string]interface{}{}, 1200 false, 1201 }, 1202 1203 "string type, unset required value": { 1204 map[string]*FieldSchema{ 1205 "foo": { 1206 Type: TypeString, 1207 Required: true, 1208 }, 1209 }, 1210 map[string]interface{}{}, 1211 true, 1212 }, 1213 1214 "value not in schema": { 1215 map[string]*FieldSchema{ 1216 "foo": { 1217 Type: TypeString, 1218 Required: true, 1219 }, 1220 }, 1221 map[string]interface{}{ 1222 "foo": 42, 1223 "bar": 43, 1224 }, 1225 true, 1226 }, 1227 1228 "value not in schema, empty schema": { 1229 map[string]*FieldSchema{}, 1230 map[string]interface{}{ 1231 "foo": 42, 1232 "bar": 43, 1233 }, 1234 true, 1235 }, 1236 1237 "value not in schema, nil schema": { 1238 nil, 1239 map[string]interface{}{ 1240 "foo": 42, 1241 "bar": 43, 1242 }, 1243 false, 1244 }, 1245 1246 "type time, invalid value": { 1247 map[string]*FieldSchema{ 1248 "foo": {Type: TypeTime}, 1249 }, 1250 map[string]interface{}{ 1251 "foo": "2021-13-11T09:08:07+02:00", 1252 }, 1253 true, 1254 }, 1255 } 1256 1257 for name, tc := range cases { 1258 name, tc := name, tc 1259 t.Run(name, func(t *testing.T) { 1260 t.Parallel() 1261 1262 data := &FieldData{ 1263 Raw: tc.Raw, 1264 Schema: tc.Schema, 1265 } 1266 1267 err := data.ValidateStrict() 1268 1269 if err == nil && tc.ExpectError == true { 1270 t.Fatalf("expected an error, got nil") 1271 } 1272 if err != nil && tc.ExpectError == false { 1273 t.Fatalf("unexpected error: %v", err) 1274 } 1275 }) 1276 } 1277 }