github.com/aavshr/aws-sdk-go@v1.41.3/service/dynamodb/expression/expression_test.go (about) 1 //go:build go1.7 2 // +build go1.7 3 4 package expression 5 6 import ( 7 "reflect" 8 "strings" 9 "testing" 10 11 "github.com/aavshr/aws-sdk-go/aws" 12 "github.com/aavshr/aws-sdk-go/service/dynamodb" 13 ) 14 15 type exprErrorMode string 16 17 const ( 18 noExpressionError exprErrorMode = "" 19 // invalidEscChar error will occer if the escape char '$' is either followed 20 // by an unsupported character or if the escape char is the last character 21 invalidEscChar = "invalid escape" 22 // outOfRange error will occur if there are more escaped chars than there are 23 // actual values to be aliased. 24 outOfRange = "out of range" 25 // invalidBuilderOperand error will occur if an invalid operand is used 26 // as input for Build() 27 invalidExpressionBuildOperand = "BuildOperand error" 28 // unsetBuilder error will occur if Build() is called on an unset Builder 29 unsetBuilder = "unset parameter: Builder" 30 // unsetConditionBuilder error will occur if an unset ConditionBuilder is 31 // used in WithCondition() 32 unsetConditionBuilder = "unset parameter: ConditionBuilder" 33 ) 34 35 func TestBuild(t *testing.T) { 36 cases := []struct { 37 name string 38 input Builder 39 expected Expression 40 err exprErrorMode 41 }{ 42 { 43 name: "condition", 44 input: NewBuilder().WithCondition(Name("foo").Equal(Value(5))), 45 expected: Expression{ 46 expressionMap: map[expressionType]string{ 47 condition: "#0 = :0", 48 }, 49 namesMap: map[string]*string{ 50 "#0": aws.String("foo"), 51 }, 52 valuesMap: map[string]*dynamodb.AttributeValue{ 53 ":0": { 54 N: aws.String("5"), 55 }, 56 }, 57 }, 58 }, 59 { 60 name: "projection", 61 input: NewBuilder().WithProjection(NamesList(Name("foo"), Name("bar"), Name("baz"))), 62 expected: Expression{ 63 expressionMap: map[expressionType]string{ 64 projection: "#0, #1, #2", 65 }, 66 namesMap: map[string]*string{ 67 "#0": aws.String("foo"), 68 "#1": aws.String("bar"), 69 "#2": aws.String("baz"), 70 }, 71 }, 72 }, 73 { 74 name: "keyCondition", 75 input: NewBuilder().WithKeyCondition(Key("foo").Equal(Value(5))), 76 expected: Expression{ 77 expressionMap: map[expressionType]string{ 78 keyCondition: "#0 = :0", 79 }, 80 namesMap: map[string]*string{ 81 "#0": aws.String("foo"), 82 }, 83 valuesMap: map[string]*dynamodb.AttributeValue{ 84 ":0": { 85 N: aws.String("5"), 86 }, 87 }, 88 }, 89 }, 90 { 91 name: "filter", 92 input: NewBuilder().WithFilter(Name("foo").Equal(Value(5))), 93 expected: Expression{ 94 expressionMap: map[expressionType]string{ 95 filter: "#0 = :0", 96 }, 97 namesMap: map[string]*string{ 98 "#0": aws.String("foo"), 99 }, 100 valuesMap: map[string]*dynamodb.AttributeValue{ 101 ":0": { 102 N: aws.String("5"), 103 }, 104 }, 105 }, 106 }, 107 { 108 name: "update", 109 input: NewBuilder().WithUpdate(Set(Name("foo"), (Value(5)))), 110 expected: Expression{ 111 expressionMap: map[expressionType]string{ 112 update: "SET #0 = :0\n", 113 }, 114 namesMap: map[string]*string{ 115 "#0": aws.String("foo"), 116 }, 117 valuesMap: map[string]*dynamodb.AttributeValue{ 118 ":0": { 119 N: aws.String("5"), 120 }, 121 }, 122 }, 123 }, 124 { 125 name: "compound", 126 input: NewBuilder(). 127 WithCondition(Name("foo").Equal(Value(5))). 128 WithFilter(Name("bar").LessThan(Value(6))). 129 WithProjection(NamesList(Name("foo"), Name("bar"), Name("baz"))). 130 WithKeyCondition(Key("foo").Equal(Value(5))). 131 WithUpdate(Set(Name("foo"), Value(5))), 132 expected: Expression{ 133 expressionMap: map[expressionType]string{ 134 condition: "#0 = :0", 135 filter: "#1 < :1", 136 projection: "#0, #1, #2", 137 keyCondition: "#0 = :2", 138 update: "SET #0 = :3\n", 139 }, 140 namesMap: map[string]*string{ 141 "#0": aws.String("foo"), 142 "#1": aws.String("bar"), 143 "#2": aws.String("baz"), 144 }, 145 valuesMap: map[string]*dynamodb.AttributeValue{ 146 ":0": { 147 N: aws.String("5"), 148 }, 149 ":1": { 150 N: aws.String("6"), 151 }, 152 ":2": { 153 N: aws.String("5"), 154 }, 155 ":3": { 156 N: aws.String("5"), 157 }, 158 }, 159 }, 160 }, 161 { 162 name: "invalid Builder", 163 input: NewBuilder().WithCondition(Name("").Equal(Value(5))), 164 err: invalidExpressionBuildOperand, 165 }, 166 { 167 name: "unset Builder", 168 input: Builder{}, 169 err: unsetBuilder, 170 }, 171 } 172 for _, c := range cases { 173 t.Run(c.name, func(t *testing.T) { 174 actual, err := c.input.Build() 175 if c.err != noExpressionError { 176 if err == nil { 177 t.Errorf("expect error %q, got no error", c.err) 178 } else { 179 if e, a := string(c.err), err.Error(); !strings.Contains(a, e) { 180 t.Errorf("expect %q error message to be in %q", e, a) 181 } 182 } 183 } else { 184 if err != nil { 185 t.Errorf("expect no error, got unexpected Error %q", err) 186 } 187 188 if e, a := c.expected, actual; !reflect.DeepEqual(a, e) { 189 t.Errorf("expect %v, got %v", e, a) 190 } 191 } 192 }) 193 } 194 } 195 196 func TestCondition(t *testing.T) { 197 cases := []struct { 198 name string 199 input Builder 200 expected *string 201 err exprErrorMode 202 }{ 203 { 204 name: "condition", 205 input: Builder{ 206 expressionMap: map[expressionType]treeBuilder{ 207 condition: Name("foo").Equal(Value(5)), 208 }, 209 }, 210 expected: aws.String("#0 = :0"), 211 }, 212 { 213 name: "unset builder", 214 input: Builder{}, 215 err: unsetBuilder, 216 }, 217 } 218 for _, c := range cases { 219 t.Run(c.name, func(t *testing.T) { 220 expr, err := c.input.Build() 221 if c.err != noExpressionError { 222 if err == nil { 223 t.Errorf("expect error %q, got no error", c.err) 224 } else { 225 if e, a := string(c.err), err.Error(); !strings.Contains(a, e) { 226 t.Errorf("expect %q error message to be in %q", e, a) 227 } 228 } 229 } else { 230 if err != nil { 231 t.Errorf("expect no error, got unexpected Error %q", err) 232 } 233 } 234 actual := expr.Condition() 235 if e, a := c.expected, actual; !reflect.DeepEqual(a, e) { 236 t.Errorf("expect %v, got %v", e, a) 237 } 238 }) 239 } 240 } 241 242 func TestFilter(t *testing.T) { 243 cases := []struct { 244 name string 245 input Builder 246 expected *string 247 err exprErrorMode 248 }{ 249 { 250 name: "filter", 251 input: Builder{ 252 expressionMap: map[expressionType]treeBuilder{ 253 filter: Name("foo").Equal(Value(5)), 254 }, 255 }, 256 expected: aws.String("#0 = :0"), 257 }, 258 { 259 name: "unset builder", 260 input: Builder{}, 261 err: unsetBuilder, 262 }, 263 } 264 for _, c := range cases { 265 t.Run(c.name, func(t *testing.T) { 266 expr, err := c.input.Build() 267 if c.err != noExpressionError { 268 if err == nil { 269 t.Errorf("expect error %q, got no error", c.err) 270 } else { 271 if e, a := string(c.err), err.Error(); !strings.Contains(a, e) { 272 t.Errorf("expect %q error message to be in %q", e, a) 273 } 274 } 275 } else { 276 if err != nil { 277 t.Errorf("expect no error, got unexpected Error %q", err) 278 } 279 } 280 actual := expr.Filter() 281 if e, a := c.expected, actual; !reflect.DeepEqual(a, e) { 282 t.Errorf("expect %v, got %v", e, a) 283 } 284 }) 285 } 286 } 287 288 func TestProjection(t *testing.T) { 289 cases := []struct { 290 name string 291 input Builder 292 expected *string 293 err exprErrorMode 294 }{ 295 { 296 name: "projection", 297 input: Builder{ 298 expressionMap: map[expressionType]treeBuilder{ 299 projection: NamesList(Name("foo"), Name("bar"), Name("baz")), 300 }, 301 }, 302 expected: aws.String("#0, #1, #2"), 303 }, 304 { 305 name: "unset builder", 306 input: Builder{}, 307 err: unsetBuilder, 308 }, 309 } 310 for _, c := range cases { 311 t.Run(c.name, func(t *testing.T) { 312 expr, err := c.input.Build() 313 if c.err != noExpressionError { 314 if err == nil { 315 t.Errorf("expect error %q, got no error", c.err) 316 } else { 317 if e, a := string(c.err), err.Error(); !strings.Contains(a, e) { 318 t.Errorf("expect %q error message to be in %q", e, a) 319 } 320 } 321 } else { 322 if err != nil { 323 t.Errorf("expect no error, got unexpected Error %q", err) 324 } 325 } 326 actual := expr.Projection() 327 if e, a := c.expected, actual; !reflect.DeepEqual(a, e) { 328 t.Errorf("expect %v, got %v", e, a) 329 } 330 }) 331 } 332 } 333 334 func TestKeyCondition(t *testing.T) { 335 cases := []struct { 336 name string 337 input Builder 338 expected *string 339 err exprErrorMode 340 }{ 341 { 342 name: "keyCondition", 343 input: Builder{ 344 expressionMap: map[expressionType]treeBuilder{ 345 keyCondition: KeyConditionBuilder{ 346 operandList: []OperandBuilder{ 347 KeyBuilder{ 348 key: "foo", 349 }, 350 ValueBuilder{ 351 value: 5, 352 }, 353 }, 354 mode: equalKeyCond, 355 }, 356 }, 357 }, 358 expected: aws.String("#0 = :0"), 359 }, 360 { 361 name: "empty builder", 362 input: Builder{}, 363 err: unsetBuilder, 364 }, 365 } 366 for _, c := range cases { 367 t.Run(c.name, func(t *testing.T) { 368 expr, err := c.input.Build() 369 if c.err != noExpressionError { 370 if err == nil { 371 t.Errorf("expect error %q, got no error", c.err) 372 } else { 373 if e, a := string(c.err), err.Error(); !strings.Contains(a, e) { 374 t.Errorf("expect %q error message to be in %q", e, a) 375 } 376 } 377 } else { 378 if err != nil { 379 t.Errorf("expect no error, got unexpected Error %q", err) 380 } 381 } 382 actual := expr.KeyCondition() 383 if e, a := c.expected, actual; !reflect.DeepEqual(a, e) { 384 t.Errorf("expect %v, got %v", e, a) 385 } 386 }) 387 } 388 } 389 390 func TestUpdate(t *testing.T) { 391 cases := []struct { 392 name string 393 input Builder 394 expected *string 395 err exprErrorMode 396 }{ 397 { 398 name: "update", 399 input: Builder{ 400 expressionMap: map[expressionType]treeBuilder{ 401 update: UpdateBuilder{ 402 operationList: map[operationMode][]operationBuilder{ 403 setOperation: { 404 { 405 name: NameBuilder{ 406 name: "foo", 407 }, 408 value: ValueBuilder{ 409 value: 5, 410 }, 411 mode: setOperation, 412 }, 413 }, 414 }, 415 }, 416 }, 417 }, 418 expected: aws.String("SET #0 = :0\n"), 419 }, 420 { 421 name: "multiple sets", 422 input: Builder{ 423 expressionMap: map[expressionType]treeBuilder{ 424 update: UpdateBuilder{ 425 operationList: map[operationMode][]operationBuilder{ 426 setOperation: { 427 { 428 name: NameBuilder{ 429 name: "foo", 430 }, 431 value: ValueBuilder{ 432 value: 5, 433 }, 434 mode: setOperation, 435 }, 436 { 437 name: NameBuilder{ 438 name: "bar", 439 }, 440 value: ValueBuilder{ 441 value: 6, 442 }, 443 mode: setOperation, 444 }, 445 { 446 name: NameBuilder{ 447 name: "baz", 448 }, 449 value: ValueBuilder{ 450 value: 7, 451 }, 452 mode: setOperation, 453 }, 454 }, 455 }, 456 }, 457 }, 458 }, 459 expected: aws.String("SET #0 = :0, #1 = :1, #2 = :2\n"), 460 }, 461 { 462 name: "unset builder", 463 input: Builder{}, 464 err: unsetBuilder, 465 }, 466 } 467 for _, c := range cases { 468 t.Run(c.name, func(t *testing.T) { 469 expr, err := c.input.Build() 470 if c.err != noExpressionError { 471 if err == nil { 472 t.Errorf("expect error %q, got no error", c.err) 473 } else { 474 if e, a := string(c.err), err.Error(); !strings.Contains(a, e) { 475 t.Errorf("expect %q error message to be in %q", e, a) 476 } 477 } 478 } else { 479 if err != nil { 480 t.Errorf("expect no error, got unexpected Error %q", err) 481 } 482 } 483 actual := expr.Update() 484 if e, a := c.expected, actual; !reflect.DeepEqual(a, e) { 485 t.Errorf("expect %v, got %v", e, a) 486 } 487 }) 488 } 489 } 490 491 func TestNames(t *testing.T) { 492 cases := []struct { 493 name string 494 input Builder 495 expected map[string]*string 496 err exprErrorMode 497 }{ 498 { 499 name: "projection", 500 input: Builder{ 501 expressionMap: map[expressionType]treeBuilder{ 502 projection: NamesList(Name("foo"), Name("bar"), Name("baz")), 503 }, 504 }, 505 expected: map[string]*string{ 506 "#0": aws.String("foo"), 507 "#1": aws.String("bar"), 508 "#2": aws.String("baz"), 509 }, 510 }, 511 { 512 name: "aggregate", 513 input: Builder{ 514 expressionMap: map[expressionType]treeBuilder{ 515 condition: ConditionBuilder{ 516 operandList: []OperandBuilder{ 517 NameBuilder{ 518 name: "foo", 519 }, 520 ValueBuilder{ 521 value: 5, 522 }, 523 }, 524 mode: equalCond, 525 }, 526 filter: ConditionBuilder{ 527 operandList: []OperandBuilder{ 528 NameBuilder{ 529 name: "bar", 530 }, 531 ValueBuilder{ 532 value: 6, 533 }, 534 }, 535 mode: lessThanCond, 536 }, 537 projection: ProjectionBuilder{ 538 names: []NameBuilder{ 539 { 540 name: "foo", 541 }, 542 { 543 name: "bar", 544 }, 545 { 546 name: "baz", 547 }, 548 }, 549 }, 550 }, 551 }, 552 expected: map[string]*string{ 553 "#0": aws.String("foo"), 554 "#1": aws.String("bar"), 555 "#2": aws.String("baz"), 556 }, 557 }, 558 { 559 name: "unset", 560 input: Builder{}, 561 err: unsetBuilder, 562 }, 563 { 564 name: "unset ConditionBuilder", 565 input: NewBuilder().WithCondition(ConditionBuilder{}), 566 err: unsetConditionBuilder, 567 }, 568 } 569 for _, c := range cases { 570 t.Run(c.name, func(t *testing.T) { 571 expr, err := c.input.Build() 572 if c.err != noExpressionError { 573 if err == nil { 574 t.Errorf("expect error %q, got no error", c.err) 575 } else { 576 if e, a := string(c.err), err.Error(); !strings.Contains(a, e) { 577 t.Errorf("expect %q error message to be in %q", e, a) 578 } 579 } 580 } else { 581 if err != nil { 582 t.Errorf("expect no error, got unexpected Error %q", err) 583 } 584 } 585 actual := expr.Names() 586 if e, a := c.expected, actual; !reflect.DeepEqual(a, e) { 587 t.Errorf("expect %v, got %v", e, a) 588 } 589 }) 590 } 591 } 592 593 func TestValues(t *testing.T) { 594 cases := []struct { 595 name string 596 input Builder 597 expected map[string]*dynamodb.AttributeValue 598 err exprErrorMode 599 }{ 600 { 601 name: "empty lists become null", 602 input: Builder{ 603 expressionMap: map[expressionType]treeBuilder{ 604 update: Name("groups").Equal(Value([]string{})), 605 }, 606 }, 607 expected: map[string]*dynamodb.AttributeValue{ 608 ":0": { 609 NULL: aws.Bool(true), 610 }, 611 }, 612 }, 613 { 614 name: "dynamodb.AttributeValue values are used directly", 615 input: Builder{ 616 expressionMap: map[expressionType]treeBuilder{ 617 update: Name("key").Equal(Value(dynamodb.AttributeValue{ 618 S: aws.String("value"), 619 })), 620 }, 621 }, 622 expected: map[string]*dynamodb.AttributeValue{ 623 ":0": { 624 S: aws.String("value"), 625 }, 626 }, 627 }, 628 { 629 name: "condition", 630 input: Builder{ 631 expressionMap: map[expressionType]treeBuilder{ 632 condition: Name("foo").Equal(Value(5)), 633 }, 634 }, 635 expected: map[string]*dynamodb.AttributeValue{ 636 ":0": { 637 N: aws.String("5"), 638 }, 639 }, 640 }, 641 { 642 name: "aggregate", 643 input: Builder{ 644 expressionMap: map[expressionType]treeBuilder{ 645 condition: ConditionBuilder{ 646 operandList: []OperandBuilder{ 647 NameBuilder{ 648 name: "foo", 649 }, 650 ValueBuilder{ 651 value: 5, 652 }, 653 }, 654 mode: equalCond, 655 }, 656 filter: ConditionBuilder{ 657 operandList: []OperandBuilder{ 658 NameBuilder{ 659 name: "bar", 660 }, 661 ValueBuilder{ 662 value: 6, 663 }, 664 }, 665 mode: lessThanCond, 666 }, 667 projection: ProjectionBuilder{ 668 names: []NameBuilder{ 669 { 670 name: "foo", 671 }, 672 { 673 name: "bar", 674 }, 675 { 676 name: "baz", 677 }, 678 }, 679 }, 680 }, 681 }, 682 expected: map[string]*dynamodb.AttributeValue{ 683 ":0": { 684 N: aws.String("5"), 685 }, 686 ":1": { 687 N: aws.String("6"), 688 }, 689 }, 690 }, 691 { 692 name: "unset", 693 input: Builder{}, 694 err: unsetBuilder, 695 }, 696 } 697 for _, c := range cases { 698 t.Run(c.name, func(t *testing.T) { 699 expr, err := c.input.Build() 700 if c.err != noExpressionError { 701 if err == nil { 702 t.Errorf("expect error %q, got no error", c.err) 703 } else { 704 if e, a := string(c.err), err.Error(); !strings.Contains(a, e) { 705 t.Errorf("expect %q error message to be in %q", e, a) 706 } 707 } 708 } else { 709 if err != nil { 710 t.Errorf("expect no error, got unexpected Error %q", err) 711 } 712 } 713 actual := expr.Values() 714 if e, a := c.expected, actual; !reflect.DeepEqual(a, e) { 715 t.Errorf("expect %v, got %v", e, a) 716 } 717 }) 718 } 719 } 720 721 func TestBuildChildTrees(t *testing.T) { 722 cases := []struct { 723 name string 724 input Builder 725 expectedaliasList aliasList 726 expectedStringMap map[expressionType]string 727 err exprErrorMode 728 }{ 729 { 730 name: "aggregate", 731 input: Builder{ 732 expressionMap: map[expressionType]treeBuilder{ 733 condition: ConditionBuilder{ 734 operandList: []OperandBuilder{ 735 NameBuilder{ 736 name: "foo", 737 }, 738 ValueBuilder{ 739 value: 5, 740 }, 741 }, 742 mode: equalCond, 743 }, 744 filter: ConditionBuilder{ 745 operandList: []OperandBuilder{ 746 NameBuilder{ 747 name: "bar", 748 }, 749 ValueBuilder{ 750 value: 6, 751 }, 752 }, 753 mode: lessThanCond, 754 }, 755 projection: ProjectionBuilder{ 756 names: []NameBuilder{ 757 { 758 name: "foo", 759 }, 760 { 761 name: "bar", 762 }, 763 { 764 name: "baz", 765 }, 766 }, 767 }, 768 }, 769 }, 770 expectedaliasList: aliasList{ 771 namesList: []string{"foo", "bar", "baz"}, 772 valuesList: []dynamodb.AttributeValue{ 773 { 774 N: aws.String("5"), 775 }, 776 { 777 N: aws.String("6"), 778 }, 779 }, 780 }, 781 expectedStringMap: map[expressionType]string{ 782 condition: "#0 = :0", 783 filter: "#1 < :1", 784 projection: "#0, #1, #2", 785 }, 786 }, 787 { 788 name: "unset", 789 input: Builder{}, 790 expectedaliasList: aliasList{}, 791 expectedStringMap: map[expressionType]string{}, 792 }, 793 } 794 for _, c := range cases { 795 t.Run(c.name, func(t *testing.T) { 796 actualAL, actualSM, err := c.input.buildChildTrees() 797 if c.err != noExpressionError { 798 if err == nil { 799 t.Errorf("expect error %q, got no error", c.err) 800 } else { 801 if e, a := string(c.err), err.Error(); !strings.Contains(a, e) { 802 t.Errorf("expect %q error message to be in %q", e, a) 803 } 804 } 805 } else { 806 if err != nil { 807 t.Errorf("expect no error, got unexpected Error %q", err) 808 } 809 } 810 if e, a := c.expectedaliasList, actualAL; !reflect.DeepEqual(a, e) { 811 t.Errorf("expect %v, got %v", e, a) 812 } 813 if e, a := c.expectedStringMap, actualSM; !reflect.DeepEqual(a, e) { 814 t.Errorf("expect %v, got %v", e, a) 815 } 816 }) 817 } 818 } 819 820 func TestBuildExpressionString(t *testing.T) { 821 cases := []struct { 822 name string 823 input exprNode 824 expectedNames map[string]*string 825 expectedValues map[string]*dynamodb.AttributeValue 826 expectedExpression string 827 err exprErrorMode 828 }{ 829 { 830 name: "basic name", 831 input: exprNode{ 832 names: []string{"foo"}, 833 fmtExpr: "$n", 834 }, 835 836 expectedValues: map[string]*dynamodb.AttributeValue{}, 837 expectedNames: map[string]*string{ 838 "#0": aws.String("foo"), 839 }, 840 expectedExpression: "#0", 841 }, 842 { 843 name: "basic value", 844 input: exprNode{ 845 values: []dynamodb.AttributeValue{ 846 { 847 N: aws.String("5"), 848 }, 849 }, 850 fmtExpr: "$v", 851 }, 852 expectedNames: map[string]*string{}, 853 expectedValues: map[string]*dynamodb.AttributeValue{ 854 ":0": { 855 N: aws.String("5"), 856 }, 857 }, 858 expectedExpression: ":0", 859 }, 860 { 861 name: "nested path", 862 input: exprNode{ 863 names: []string{"foo", "bar"}, 864 fmtExpr: "$n.$n", 865 }, 866 867 expectedValues: map[string]*dynamodb.AttributeValue{}, 868 expectedNames: map[string]*string{ 869 "#0": aws.String("foo"), 870 "#1": aws.String("bar"), 871 }, 872 expectedExpression: "#0.#1", 873 }, 874 { 875 name: "nested path with index", 876 input: exprNode{ 877 names: []string{"foo", "bar", "baz"}, 878 fmtExpr: "$n.$n[0].$n", 879 }, 880 expectedValues: map[string]*dynamodb.AttributeValue{}, 881 expectedNames: map[string]*string{ 882 "#0": aws.String("foo"), 883 "#1": aws.String("bar"), 884 "#2": aws.String("baz"), 885 }, 886 expectedExpression: "#0.#1[0].#2", 887 }, 888 { 889 name: "basic size", 890 input: exprNode{ 891 names: []string{"foo"}, 892 fmtExpr: "size ($n)", 893 }, 894 expectedValues: map[string]*dynamodb.AttributeValue{}, 895 expectedNames: map[string]*string{ 896 "#0": aws.String("foo"), 897 }, 898 expectedExpression: "size (#0)", 899 }, 900 { 901 name: "duplicate path name", 902 input: exprNode{ 903 names: []string{"foo", "foo"}, 904 fmtExpr: "$n.$n", 905 }, 906 expectedValues: map[string]*dynamodb.AttributeValue{}, 907 expectedNames: map[string]*string{ 908 "#0": aws.String("foo"), 909 }, 910 expectedExpression: "#0.#0", 911 }, 912 { 913 name: "equal expression", 914 input: exprNode{ 915 children: []exprNode{ 916 { 917 names: []string{"foo"}, 918 fmtExpr: "$n", 919 }, 920 { 921 values: []dynamodb.AttributeValue{ 922 { 923 N: aws.String("5"), 924 }, 925 }, 926 fmtExpr: "$v", 927 }, 928 }, 929 fmtExpr: "$c = $c", 930 }, 931 932 expectedNames: map[string]*string{ 933 "#0": aws.String("foo"), 934 }, 935 expectedValues: map[string]*dynamodb.AttributeValue{ 936 ":0": { 937 N: aws.String("5"), 938 }, 939 }, 940 expectedExpression: "#0 = :0", 941 }, 942 { 943 name: "missing char after $", 944 input: exprNode{ 945 names: []string{"foo", "foo"}, 946 fmtExpr: "$n.$", 947 }, 948 err: invalidEscChar, 949 }, 950 { 951 name: "names out of range", 952 input: exprNode{ 953 names: []string{"foo"}, 954 fmtExpr: "$n.$n", 955 }, 956 err: outOfRange, 957 }, 958 { 959 name: "values out of range", 960 input: exprNode{ 961 fmtExpr: "$v", 962 }, 963 err: outOfRange, 964 }, 965 { 966 name: "children out of range", 967 input: exprNode{ 968 fmtExpr: "$c", 969 }, 970 err: outOfRange, 971 }, 972 { 973 name: "invalid escape char", 974 input: exprNode{ 975 fmtExpr: "$!", 976 }, 977 err: invalidEscChar, 978 }, 979 { 980 name: "unset exprNode", 981 input: exprNode{}, 982 expectedExpression: "", 983 }, 984 } 985 986 for _, c := range cases { 987 t.Run(c.name, func(t *testing.T) { 988 expr, err := c.input.buildExpressionString(&aliasList{}) 989 if c.err != noExpressionError { 990 if err == nil { 991 t.Errorf("expect error %q, got no error", c.err) 992 } else { 993 if e, a := string(c.err), err.Error(); !strings.Contains(a, e) { 994 t.Errorf("expect %q error message to be in %q", e, a) 995 } 996 } 997 } else { 998 if err != nil { 999 t.Errorf("expect no error, got unexpected Error %q", err) 1000 } 1001 1002 if e, a := c.expectedExpression, expr; !reflect.DeepEqual(a, e) { 1003 t.Errorf("expect %v, got %v", e, a) 1004 } 1005 } 1006 }) 1007 } 1008 } 1009 1010 func TestReturnExpression(t *testing.T) { 1011 cases := []struct { 1012 name string 1013 input Expression 1014 expected *string 1015 }{ 1016 { 1017 name: "projection exists", 1018 input: Expression{ 1019 expressionMap: map[expressionType]string{ 1020 projection: "#0, #1, #2", 1021 }, 1022 }, 1023 expected: aws.String("#0, #1, #2"), 1024 }, 1025 { 1026 name: "projection not exists", 1027 input: Expression{ 1028 expressionMap: map[expressionType]string{}, 1029 }, 1030 expected: nil, 1031 }, 1032 } 1033 for _, c := range cases { 1034 t.Run(c.name, func(t *testing.T) { 1035 actual := c.input.returnExpression(projection) 1036 if e, a := c.expected, actual; !reflect.DeepEqual(a, e) { 1037 t.Errorf("expect %v, got %v", e, a) 1038 } 1039 }) 1040 } 1041 } 1042 1043 func TestAliasValue(t *testing.T) { 1044 cases := []struct { 1045 name string 1046 input *aliasList 1047 expected string 1048 err exprErrorMode 1049 }{ 1050 { 1051 name: "first item", 1052 input: &aliasList{}, 1053 expected: ":0", 1054 }, 1055 { 1056 name: "fifth item", 1057 input: &aliasList{ 1058 valuesList: []dynamodb.AttributeValue{ 1059 {}, 1060 {}, 1061 {}, 1062 {}, 1063 }, 1064 }, 1065 expected: ":4", 1066 }, 1067 } 1068 1069 for _, c := range cases { 1070 t.Run(c.name, func(t *testing.T) { 1071 str, err := c.input.aliasValue(dynamodb.AttributeValue{}) 1072 1073 if c.err != noExpressionError { 1074 if err == nil { 1075 t.Errorf("expect error %q, got no error", c.err) 1076 } else { 1077 if e, a := string(c.err), err.Error(); !strings.Contains(a, e) { 1078 t.Errorf("expect %q error message to be in %q", e, a) 1079 } 1080 } 1081 } else { 1082 if err != nil { 1083 t.Errorf("expect no error, got unexpected Error %q", err) 1084 } 1085 1086 if e, a := c.expected, str; e != a { 1087 t.Errorf("expect %v, got %v", e, a) 1088 } 1089 } 1090 }) 1091 } 1092 } 1093 1094 func TestAliasPath(t *testing.T) { 1095 cases := []struct { 1096 name string 1097 inputList *aliasList 1098 inputName string 1099 expected string 1100 err exprErrorMode 1101 }{ 1102 { 1103 name: "new unique item", 1104 inputList: &aliasList{}, 1105 inputName: "foo", 1106 expected: "#0", 1107 }, 1108 { 1109 name: "duplicate item", 1110 inputList: &aliasList{ 1111 namesList: []string{ 1112 "foo", 1113 "bar", 1114 }, 1115 }, 1116 inputName: "foo", 1117 expected: "#0", 1118 }, 1119 } 1120 1121 for _, c := range cases { 1122 t.Run(c.name, func(t *testing.T) { 1123 str, err := c.inputList.aliasPath(c.inputName) 1124 1125 if c.err != noExpressionError { 1126 if err == nil { 1127 t.Errorf("expect error %q, got no error", c.err) 1128 } else { 1129 if e, a := string(c.err), err.Error(); !strings.Contains(a, e) { 1130 t.Errorf("expect %q error message to be in %q", e, a) 1131 } 1132 } 1133 } else { 1134 if err != nil { 1135 t.Errorf("expect no error, got unexpected Error %q", err) 1136 } 1137 1138 if e, a := c.expected, str; e != a { 1139 t.Errorf("expect %v, got %v", e, a) 1140 } 1141 } 1142 }) 1143 } 1144 }