Skip to content

Commit 4876abf

Browse files
authored
add(test): Key precedence test in error handling (#31)
* test: add tests for key precedence in error handling to ensure correct behavior of value overrides and original error integrity * test(error_test.go): improve error comparison in TestWith_KeyPrecedence for better clarity and accuracy
1 parent 3b2157c commit 4876abf

File tree

2 files changed

+194
-0
lines changed

2 files changed

+194
-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: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"log/slog"
8+
"reflect"
89
"strings"
910
"testing"
1011

@@ -605,3 +606,189 @@ func TestWith_MultipleOptions(t *testing.T) {
605606
t.Error("Original error was modified")
606607
}
607608
}
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

Comments
 (0)