Skip to content

Commit af97516

Browse files
ja7adElyarSadig
authored andcommitted
test: add comprehensive tests for Hit decoding with various data types and error handling
1 parent a645f7d commit af97516

File tree

1 file changed

+323
-0
lines changed

1 file changed

+323
-0
lines changed

hit_test.go

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,3 +678,326 @@ func TestIsJSONNull(t *testing.T) {
678678
assert.False(t, isJSONNull([]byte(`"null"`)))
679679
assert.False(t, isJSONNull(nil))
680680
}
681+
682+
func rm(v any) json.RawMessage {
683+
b, _ := json.Marshal(v)
684+
return json.RawMessage(b)
685+
}
686+
687+
type covStringOpt struct {
688+
N int `json:"n"`
689+
SN int `json:"sn,string"` // triggers f.hasString branch
690+
}
691+
692+
type covBadType struct {
693+
Count int `json:"count"` // we will feed an object here to force field-level unmarshal error
694+
}
695+
696+
func TestHitDecodeInto_Struct_SkipEmptyRaw(t *testing.T) {
697+
h := Hit{
698+
"n": json.RawMessage{}, // len(raw) == 0 => skip
699+
"sn": rm("123"), // ,string branch should set 123
700+
}
701+
var out covStringOpt
702+
require.NoError(t, h.DecodeInto(&out))
703+
assert.Equal(t, 0, out.N) // untouched (skipped)
704+
assert.Equal(t, 123, out.SN) // set via unmarshalSingleField
705+
}
706+
707+
func TestHitDecodeInto_Struct_UnmarshalErrorOnField(t *testing.T) {
708+
h := Hit{
709+
"count": rm(map[string]int{"oops": 1}), // object into int => error
710+
}
711+
var out covBadType
712+
err := h.DecodeInto(&out)
713+
require.Error(t, err)
714+
assert.Contains(t, err.Error(), `decode field "count"`)
715+
assert.Contains(t, err.Error(), "cannot unmarshal object")
716+
}
717+
718+
func TestHitDecodeInto_Map_StringKeyRequired(t *testing.T) {
719+
h := Hit{
720+
"a": rm(1),
721+
}
722+
var bad map[int]int
723+
err := h.DecodeInto(&bad)
724+
require.Error(t, err)
725+
assert.Contains(t, err.Error(), "map key must be string")
726+
}
727+
728+
func TestHitDecodeInto_Map_NullZeroAndValueErrors(t *testing.T) {
729+
// null → zero value of elem type.
730+
// also force a value-unmarshal error on key "err".
731+
h := Hit{
732+
"ok": rm(7),
733+
"nil": json.RawMessage("null"),
734+
"err": rm(map[string]int{"x": 1}), // object into int => error
735+
}
736+
737+
// First, map[string]any: null -> nil interface{}
738+
var m1 map[string]any
739+
require.NoError(t, h.DecodeInto(&m1))
740+
assert.Equal(t, float64(7), m1["ok"]) // numbers into interface{} become float64
741+
assert.Nil(t, m1["nil"])
742+
// We didn't touch "err" yet because we returned early above; to test error path,
743+
// try a typed map where we decode all keys and catch the error.
744+
745+
// Now, map[string]int: null -> 0; "err" should fail
746+
var m2 map[string]int
747+
err := h.DecodeInto(&m2)
748+
require.Error(t, err)
749+
assert.Contains(t, err.Error(), `decode map value for key "err"`)
750+
assert.Contains(t, err.Error(), "cannot unmarshal object")
751+
}
752+
753+
func TestHitsDecodeInto_SliceOfMap_InterfaceHappy(t *testing.T) {
754+
h1 := Hit{"a": rm(1), "b": rm("x")}
755+
h2 := Hit{"a": rm(2), "b": rm("y")}
756+
hs := Hits{h1, h2}
757+
758+
var out []map[string]any
759+
require.NoError(t, hs.DecodeInto(&out))
760+
require.Len(t, out, 2)
761+
assert.Equal(t, float64(1), out[0]["a"])
762+
assert.Equal(t, "x", out[0]["b"])
763+
assert.Equal(t, float64(2), out[1]["a"])
764+
assert.Equal(t, "y", out[1]["b"])
765+
}
766+
767+
func TestHitsDecodeInto_SliceOfPtrMap_InterfaceHappy(t *testing.T) {
768+
h1 := Hit{"a": rm(1)}
769+
h2 := Hit{"a": rm(2)}
770+
hs := Hits{h1, h2}
771+
772+
var out []*map[string]any
773+
require.NoError(t, hs.DecodeInto(&out))
774+
require.Len(t, out, 2)
775+
require.NotNil(t, out[0])
776+
require.NotNil(t, out[1])
777+
assert.Equal(t, float64(1), (*out[0])["a"])
778+
assert.Equal(t, float64(2), (*out[1])["a"])
779+
}
780+
781+
type covElem struct {
782+
V int `json:"v"`
783+
}
784+
785+
func TestHitsDecodeInto_ErrorIndex_PropagatesForStruct(t *testing.T) {
786+
// First ok, second bad (object into int)
787+
ok := Hit{"v": rm(1)}
788+
bad := Hit{"v": rm(map[string]int{"x": 1})}
789+
hs := Hits{ok, bad}
790+
791+
var out []covElem
792+
err := hs.DecodeInto(&out)
793+
require.Error(t, err)
794+
assert.Contains(t, err.Error(), "decode hits[1]") // index included
795+
assert.Contains(t, err.Error(), "decode field \"v\"")
796+
}
797+
798+
func TestHitsDecodeInto_ErrorIndex_PropagatesForMap(t *testing.T) {
799+
// First ok, second bad for typed map[string]int
800+
ok := Hit{"k": rm(5)}
801+
bad := Hit{"k": rm(map[string]int{"x": 1})}
802+
hs := Hits{ok, bad}
803+
804+
var out []map[string]int
805+
err := hs.DecodeInto(&out)
806+
require.Error(t, err)
807+
assert.Contains(t, err.Error(), "decode hits[1]")
808+
assert.Contains(t, err.Error(), `decode map value for key "k"`)
809+
}
810+
811+
func TestHitsDecodeInto_SliceElemPtrMap_NonStringKeyError(t *testing.T) {
812+
hs := Hits{Hit{"k": rm(1)}}
813+
var out []*map[int]int
814+
err := hs.DecodeInto(&out)
815+
require.Error(t, err)
816+
assert.Contains(t, err.Error(), "slice element must be map with string key")
817+
}
818+
819+
func TestHitsDecodeInto_SliceElemMap_NonStringKeyError(t *testing.T) {
820+
hs := Hits{Hit{"k": rm(1)}}
821+
var out []map[int]int
822+
err := hs.DecodeInto(&out)
823+
require.Error(t, err)
824+
assert.Contains(t, err.Error(), "slice element must be map with string key")
825+
}
826+
827+
func TestHitsDecodeInto_SliceElemPtrStruct_Happy(t *testing.T) {
828+
hs := Hits{
829+
{"v": rm(10)},
830+
{"v": rm(20)},
831+
}
832+
var out []*covElem
833+
require.NoError(t, hs.DecodeInto(&out))
834+
require.Len(t, out, 2)
835+
assert.Equal(t, 10, out[0].V)
836+
assert.Equal(t, 20, out[1].V)
837+
}
838+
839+
func TestHitsDecodeInto_SliceElemStruct_Happy(t *testing.T) {
840+
hs := Hits{
841+
{"v": rm(3)},
842+
{"v": rm(4)},
843+
}
844+
var out []covElem
845+
require.NoError(t, hs.DecodeInto(&out))
846+
require.Len(t, out, 2)
847+
assert.Equal(t, 3, out[0].V)
848+
assert.Equal(t, 4, out[1].V)
849+
}
850+
851+
func TestHitDecodeInto_Struct_StringTagBranch(t *testing.T) {
852+
h := Hit{"sn": rm("42")}
853+
var out covStringOpt
854+
require.NoError(t, h.DecodeInto(&out))
855+
assert.Equal(t, 42, out.SN)
856+
}
857+
858+
func TestHitDecodeInto_Map_NullToZero_TypedInt(t *testing.T) {
859+
h := Hit{
860+
"x": rm(9),
861+
"z": json.RawMessage("null"),
862+
}
863+
var m map[string]int
864+
require.NoError(t, h.DecodeInto(&m))
865+
assert.Equal(t, 9, m["x"])
866+
assert.Equal(t, 0, m["z"]) // zero value for int
867+
}
868+
869+
func TestHitDecodeInto_DispatchError(t *testing.T) {
870+
var s string
871+
err := Hit{"x": rm(1)}.DecodeInto(&s)
872+
require.Error(t, err)
873+
assert.Contains(t, err.Error(), "struct or map")
874+
}
875+
876+
func TestHitDecodeInto_EmptyRawUnknownKey_NoCrash(t *testing.T) {
877+
type S struct {
878+
A int `json:"a"`
879+
}
880+
h := Hit{
881+
"zzz": json.RawMessage{}, // unknown + empty
882+
"a": rm(5),
883+
}
884+
var s S
885+
require.NoError(t, h.DecodeInto(&s))
886+
assert.Equal(t, 5, s.A)
887+
}
888+
889+
func TestHitDecodeInto_Map_InterfaceTypes(t *testing.T) {
890+
h := Hit{
891+
"n": rm(1),
892+
"s": rm("str"),
893+
"o": rm(map[string]any{"k": 2}),
894+
"a": rm([]any{1, "x"}),
895+
}
896+
var m map[string]any
897+
require.NoError(t, h.DecodeInto(&m))
898+
assert.Equal(t, float64(1), m["n"])
899+
assert.Equal(t, "str", m["s"])
900+
assert.Equal(t, map[string]any{"k": float64(2)}, m["o"])
901+
assert.Equal(t, []any{float64(1), "x"}, m["a"])
902+
903+
// Double-check types match what encoding/json would produce
904+
b, _ := json.Marshal(h) // {"n":1,"s":"str","o":{"k":2},"a":[1,"x"]}
905+
var std map[string]any
906+
require.NoError(t, json.Unmarshal(b, &std))
907+
assert.Equal(t, std, m)
908+
}
909+
910+
func TestHitDecodeInto_Map_PreserveAndOverwrite(t *testing.T) {
911+
h := Hit{
912+
"a": rm(10),
913+
"b": rm(20),
914+
}
915+
m := map[string]int{"a": 1} // pre-populated
916+
require.NoError(t, h.DecodeInto(&m))
917+
// "a" should be overwritten, "b" added
918+
assert.Equal(t, 10, m["a"])
919+
assert.Equal(t, 20, m["b"])
920+
}
921+
922+
func TestHitsDecodeInto_SliceElemInvalidKind(t *testing.T) {
923+
hs := Hits{{"x": rm(1)}}
924+
var out []int
925+
err := hs.DecodeInto(&out)
926+
require.Error(t, err)
927+
assert.Contains(t, err.Error(), "slice element must be struct, *struct, map[string]T, or *map[string]T")
928+
}
929+
930+
func TestHitsDecodeInto_NonSlicePointer(t *testing.T) {
931+
hs := Hits{}
932+
var x int
933+
err := hs.DecodeInto(&x)
934+
require.Error(t, err)
935+
assert.Contains(t, err.Error(), "must point to a slice")
936+
}
937+
938+
func TestHitsDecodeInto_BadPtr(t *testing.T) {
939+
var hs Hits
940+
err := hs.DecodeInto(nil)
941+
require.Error(t, err)
942+
943+
var notPtr []map[string]any
944+
err = hs.DecodeInto(notPtr)
945+
require.Error(t, err)
946+
assert.Contains(t, err.Error(), "non-nil pointer")
947+
}
948+
949+
func TestHitDecodeInto_BadKinds(t *testing.T) {
950+
h := Hit{}
951+
var ch chan int
952+
err := h.DecodeInto(&ch)
953+
require.Error(t, err)
954+
assert.Contains(t, err.Error(), "struct or map")
955+
956+
// Not pointer
957+
var s covStringOpt
958+
err = h.DecodeInto(s)
959+
require.Error(t, err)
960+
assert.Contains(t, err.Error(), "non-nil pointer")
961+
}
962+
963+
func TestHitDecodeInto_Map_CreatesMapIfNil(t *testing.T) {
964+
h := Hit{"x": rm(1)}
965+
var m map[string]any // nil
966+
require.NoError(t, h.DecodeInto(&m))
967+
require.NotNil(t, m)
968+
assert.Equal(t, float64(1), m["x"])
969+
}
970+
971+
func TestHitsDecodeInto_PtrMap_AllocAndFill(t *testing.T) {
972+
hs := Hits{
973+
{"x": rm(1)},
974+
{"y": rm(2)},
975+
}
976+
var out []*map[string]any
977+
require.NoError(t, hs.DecodeInto(&out))
978+
require.Len(t, out, 2)
979+
assert.Equal(t, float64(1), (*out[0])["x"])
980+
assert.Equal(t, float64(2), (*out[1])["y"])
981+
}
982+
983+
func TestHitDecodeInto_Struct_PointerEmbeddedAlloc(t *testing.T) {
984+
type Inner struct {
985+
Z int `json:"z"`
986+
}
987+
type Wrap struct{ *Inner }
988+
h := Hit{"z": rm(9)}
989+
var w Wrap
990+
require.NoError(t, h.DecodeInto(&w))
991+
require.NotNil(t, w.Inner)
992+
assert.Equal(t, 9, w.Z)
993+
}
994+
995+
func TestHitDecodeInto_Struct_UnknownKeysIgnored(t *testing.T) {
996+
type S struct {
997+
A int `json:"a"`
998+
}
999+
h := Hit{"xxx": rm(1), "a": rm(2)}
1000+
var s S
1001+
require.NoError(t, h.DecodeInto(&s))
1002+
assert.Equal(t, 2, s.A)
1003+
}

0 commit comments

Comments
 (0)