|
5 | 5 | "errors" |
6 | 6 | "fmt" |
7 | 7 | "log/slog" |
| 8 | + "reflect" |
8 | 9 | "strings" |
9 | 10 | "testing" |
10 | 11 |
|
@@ -605,3 +606,189 @@ func TestWith_MultipleOptions(t *testing.T) { |
605 | 606 | t.Error("Original error was modified") |
606 | 607 | } |
607 | 608 | } |
| 609 | + |
| 610 | +func TestWith_KeyPrecedence(t *testing.T) { |
| 611 | + t.Run("same key within single With call", func(t *testing.T) { |
| 612 | + original := goerr.New("original error") |
| 613 | + |
| 614 | + // Same key specified multiple times in single With call |
| 615 | + enhanced := goerr.With(original, |
| 616 | + goerr.Value("key1", "first_value"), |
| 617 | + goerr.Value("key1", "second_value"), // Should override first_value |
| 618 | + goerr.Value("key1", "final_value"), // Should override second_value |
| 619 | + ) |
| 620 | + |
| 621 | + values := enhanced.Values() |
| 622 | + if values["key1"] != "final_value" { |
| 623 | + t.Errorf("Expected 'final_value', got %v", values["key1"]) |
| 624 | + } |
| 625 | + |
| 626 | + // Original should remain unchanged |
| 627 | + if len(original.Values()) != 0 { |
| 628 | + t.Error("Original error was modified") |
| 629 | + } |
| 630 | + }) |
| 631 | + |
| 632 | + t.Run("consecutive With calls with same key", func(t *testing.T) { |
| 633 | + original := goerr.New("original error") |
| 634 | + |
| 635 | + // Multiple With calls with same key |
| 636 | + step1 := goerr.With(original, goerr.Value("key1", "first_value")) |
| 637 | + step2 := goerr.With(step1, goerr.Value("key1", "second_value")) |
| 638 | + final := goerr.With(step2, goerr.Value("key1", "final_value")) |
| 639 | + |
| 640 | + values := final.Values() |
| 641 | + if values["key1"] != "final_value" { |
| 642 | + t.Errorf("Expected 'final_value', got %v", values["key1"]) |
| 643 | + } |
| 644 | + |
| 645 | + // Previous steps should have their own values |
| 646 | + if step1.Values()["key1"] != "first_value" { |
| 647 | + t.Errorf("Step1 should have 'first_value', got %v", step1.Values()["key1"]) |
| 648 | + } |
| 649 | + if step2.Values()["key1"] != "second_value" { |
| 650 | + t.Errorf("Step2 should have 'second_value', got %v", step2.Values()["key1"]) |
| 651 | + } |
| 652 | + |
| 653 | + // Original should remain unchanged |
| 654 | + if len(original.Values()) != 0 { |
| 655 | + t.Error("Original error was modified") |
| 656 | + } |
| 657 | + }) |
| 658 | + |
| 659 | + t.Run("overwrite existing error key with With", func(t *testing.T) { |
| 660 | + // Create error with existing key |
| 661 | + original := goerr.New("original error", goerr.Value("existing_key", "original_value")) |
| 662 | + |
| 663 | + // Override existing key with With |
| 664 | + enhanced := goerr.With(original, goerr.Value("existing_key", "new_value")) |
| 665 | + |
| 666 | + values := enhanced.Values() |
| 667 | + if values["existing_key"] != "new_value" { |
| 668 | + t.Errorf("Expected 'new_value', got %v", values["existing_key"]) |
| 669 | + } |
| 670 | + |
| 671 | + // Original should keep its value |
| 672 | + originalValues := original.Values() |
| 673 | + if originalValues["existing_key"] != "original_value" { |
| 674 | + t.Errorf("Original should keep 'original_value', got %v", originalValues["existing_key"]) |
| 675 | + } |
| 676 | + }) |
| 677 | + |
| 678 | + t.Run("TypedValue key precedence", func(t *testing.T) { |
| 679 | + stringKey := goerr.NewTypedKey[string]("typed_key") |
| 680 | + |
| 681 | + original := goerr.New("original error") |
| 682 | + |
| 683 | + // Multiple TypedValue with same key |
| 684 | + enhanced := goerr.With(original, |
| 685 | + goerr.TV(stringKey, "first_typed_value"), |
| 686 | + goerr.TV(stringKey, "final_typed_value"), // Should override first |
| 687 | + ) |
| 688 | + |
| 689 | + // Check via GetTypedValue |
| 690 | + if value, ok := goerr.GetTypedValue(enhanced, stringKey); !ok || value != "final_typed_value" { |
| 691 | + t.Errorf("Expected 'final_typed_value', got %v (ok=%v)", value, ok) |
| 692 | + } |
| 693 | + |
| 694 | + // Check via TypedValues |
| 695 | + typedValues := enhanced.TypedValues() |
| 696 | + if typedValues["typed_key"] != "final_typed_value" { |
| 697 | + t.Errorf("Expected 'final_typed_value' in TypedValues, got %v", typedValues["typed_key"]) |
| 698 | + } |
| 699 | + |
| 700 | + // Original should remain unchanged |
| 701 | + if len(original.TypedValues()) != 0 { |
| 702 | + t.Error("Original error was modified") |
| 703 | + } |
| 704 | + }) |
| 705 | + |
| 706 | + t.Run("mixed Value and TypedValue with same key name", func(t *testing.T) { |
| 707 | + stringKey := goerr.NewTypedKey[string]("same_key") |
| 708 | + |
| 709 | + original := goerr.New("original error") |
| 710 | + |
| 711 | + // Mix string-based Value and TypedValue with same key name |
| 712 | + enhanced := goerr.With(original, |
| 713 | + goerr.Value("same_key", "string_value"), |
| 714 | + goerr.TV(stringKey, "typed_value"), |
| 715 | + ) |
| 716 | + |
| 717 | + // Both should exist independently |
| 718 | + values := enhanced.Values() |
| 719 | + typedValues := enhanced.TypedValues() |
| 720 | + |
| 721 | + if values["same_key"] != "string_value" { |
| 722 | + t.Errorf("Expected 'string_value' in Values, got %v", values["same_key"]) |
| 723 | + } |
| 724 | + if typedValues["same_key"] != "typed_value" { |
| 725 | + t.Errorf("Expected 'typed_value' in TypedValues, got %v", typedValues["same_key"]) |
| 726 | + } |
| 727 | + |
| 728 | + // GetTypedValue should return typed value |
| 729 | + if value, ok := goerr.GetTypedValue(enhanced, stringKey); !ok || value != "typed_value" { |
| 730 | + t.Errorf("Expected 'typed_value' from GetTypedValue, got %v (ok=%v)", value, ok) |
| 731 | + } |
| 732 | + }) |
| 733 | + |
| 734 | + t.Run("complex With chain with multiple overwrites", func(t *testing.T) { |
| 735 | + stringKey := goerr.NewTypedKey[string]("chain_key") |
| 736 | + |
| 737 | + // Start with error having some values |
| 738 | + original := goerr.New("original error", |
| 739 | + goerr.Value("key1", "orig1"), |
| 740 | + goerr.Value("key2", "orig2"), |
| 741 | + goerr.TV(stringKey, "orig_typed"), |
| 742 | + ) |
| 743 | + |
| 744 | + // Chain multiple With calls with overlapping keys |
| 745 | + step1 := goerr.With(original, |
| 746 | + goerr.Value("key1", "step1_val1"), // Override key1 |
| 747 | + goerr.Value("key3", "step1_val3"), // New key |
| 748 | + ) |
| 749 | + |
| 750 | + step2 := goerr.With(step1, |
| 751 | + goerr.Value("key2", "step2_val2"), // Override key2 |
| 752 | + goerr.TV(stringKey, "step2_typed"), // Override typed |
| 753 | + ) |
| 754 | + |
| 755 | + finalStep := goerr.With(step2, |
| 756 | + goerr.Value("key1", "final_val1"), // Override key1 again |
| 757 | + goerr.Value("key4", "final_val4"), // New key |
| 758 | + ) |
| 759 | + |
| 760 | + // Check final values |
| 761 | + values := finalStep.Values() |
| 762 | + expectedValues := map[string]any{ |
| 763 | + "key1": "final_val1", // Final override |
| 764 | + "key2": "step2_val2", // From step2 |
| 765 | + "key3": "step1_val3", // From step1 |
| 766 | + "key4": "final_val4", // From final step |
| 767 | + } |
| 768 | + |
| 769 | + if !reflect.DeepEqual(values, expectedValues) { |
| 770 | + t.Errorf("Final values mismatch.\nGot: %v\nWant: %v", values, expectedValues) |
| 771 | + } |
| 772 | + |
| 773 | + // Check typed value |
| 774 | + if value, ok := goerr.GetTypedValue(finalStep, stringKey); !ok || value != "step2_typed" { |
| 775 | + t.Errorf("Expected 'step2_typed' from GetTypedValue, got %v (ok=%v)", value, ok) |
| 776 | + } |
| 777 | + |
| 778 | + // Original should remain unchanged |
| 779 | + originalValues := original.Values() |
| 780 | + expectedOriginalValues := map[string]any{ |
| 781 | + "key1": "orig1", |
| 782 | + "key2": "orig2", |
| 783 | + } |
| 784 | + if !reflect.DeepEqual(originalValues, expectedOriginalValues) { |
| 785 | + t.Errorf("Original error string values were modified.\nGot: %v\nWant: %v", originalValues, expectedOriginalValues) |
| 786 | + } |
| 787 | + if value, ok := goerr.GetTypedValue(original, stringKey); !ok || value != "orig_typed" { |
| 788 | + t.Error("Original error typed value was modified") |
| 789 | + } |
| 790 | + if len(original.TypedValues()) != 1 { |
| 791 | + t.Errorf("Original error typed values count changed, got %d, want 1", len(original.TypedValues())) |
| 792 | + } |
| 793 | + }) |
| 794 | +} |
0 commit comments