Skip to content

Commit e362fd7

Browse files
committed
test: add tests for key precedence in error handling to ensure correct behavior of value overrides and original error integrity
1 parent 3b2157c commit e362fd7

File tree

2 files changed

+188
-0
lines changed

2 files changed

+188
-0
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ originalErr := goerr.New("original error")
7777
enhanced := goerr.With(originalErr, goerr.Value("context", "added"))
7878
// enhanced has same stacktrace as originalErr, originalErr unchanged
7979

80+
// Key precedence: later values override earlier ones
81+
err := goerr.New("error", goerr.Value("key", "first"))
82+
enhanced := goerr.With(err,
83+
goerr.Value("key", "second"), // Overrides "first"
84+
goerr.Value("key", "final")) // Overrides "second"
85+
// enhanced.Values()["key"] == "final"
86+
8087
// Extract goerr.Error from any error
8188
if goErr := goerr.Unwrap(err); goErr != nil {
8289
values := goErr.Values() // Get all contextual values

error_test.go

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

0 commit comments

Comments
 (0)