package jsonvalue import ( "encoding/json" "math" "reflect" "strings" "testing" ) type ( namedBool bool namedString string namedInt64 int64 namedUint64 uint64 namedFloat32 float32 namedFloat64 float64 namedKey string namedMap map[namedKey]namedInt64 namedSlice []namedString namedArray [1]map[string]int ) func TestCopyPreservesSupportedScalarAndNumberTypes(t *testing.T) { maxInt := int(^uint(0) >> 1) minInt := -maxInt - 1 tests := []struct { name string value any }{ {name: "bool", value: true}, {name: "named bool", value: namedBool(true)}, {name: "string", value: "value"}, {name: "named string", value: namedString("value")}, {name: "int", value: minInt}, {name: "int8", value: int8(-1 << 7)}, {name: "int16", value: int16(-1 << 15)}, {name: "int32", value: int32(-1 << 31)}, {name: "int64", value: int64(-1 << 63)}, {name: "named int64", value: namedInt64(1<<63 - 1)}, {name: "uint", value: ^uint(0)}, {name: "uint8", value: ^uint8(0)}, {name: "uint16", value: ^uint16(0)}, {name: "uint32", value: ^uint32(0)}, {name: "uint64", value: ^uint64(0)}, {name: "uintptr", value: ^uintptr(0)}, {name: "named uint64", value: namedUint64(^uint64(0))}, {name: "float32", value: float32(1.25)}, {name: "float64", value: float64(-2.5e100)}, {name: "named float32", value: namedFloat32(3.5)}, {name: "named float64", value: namedFloat64(-4.5e200)}, {name: "JSON number integer", value: json.Number("18446744073709551615")}, {name: "JSON number fraction", value: json.Number("-1.25e+2")}, {name: "JSON number beyond float64", value: json.Number("1e9999")}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { got, err := Copy(tc.value) if err != nil { t.Fatalf("copy value: %v", err) } if !reflect.DeepEqual(got, tc.value) { t.Fatalf("value or concrete type changed: got %#v (%T), want %#v (%T)", got, got, tc.value, tc.value) } }) } } func TestCopyRejectsInvalidNumbers(t *testing.T) { tests := []struct { name string value any }{ {name: "float32 NaN", value: float32(math.NaN())}, {name: "float64 NaN", value: math.NaN()}, {name: "named float NaN", value: namedFloat64(math.NaN())}, {name: "positive infinity", value: math.Inf(1)}, {name: "negative infinity", value: math.Inf(-1)}, {name: "empty JSON number", value: json.Number("")}, {name: "leading zero JSON number", value: json.Number("01")}, {name: "leading plus JSON number", value: json.Number("+1")}, {name: "trailing decimal JSON number", value: json.Number("1.")}, {name: "leading decimal JSON number", value: json.Number(".1")}, {name: "non-number JSON number", value: json.Number("NaN")}, {name: "spaced JSON number", value: json.Number(" 1")}, {name: "quoted JSON number", value: json.Number(`"1"`)}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { if _, err := Copy(tc.value); err == nil { t.Fatal("expected validation error") } }) } } func TestCopyPreservesCompatibleCollectionsAndNilEmptyDistinctions(t *testing.T) { collections := []struct { name string value any }{ {name: "unnamed map", value: map[string]int{"limit": 2}}, {name: "named map", value: namedMap{"limit": 2}}, {name: "unnamed slice", value: []string{"one", "two"}}, {name: "named slice", value: namedSlice{"one", "two"}}, {name: "unnamed array", value: [2]int{1, 2}}, {name: "named array", value: namedArray{{"limit": 2}}}, {name: "empty map", value: map[string]int{}}, {name: "empty named map", value: namedMap{}}, {name: "empty slice", value: []string{}}, {name: "empty named slice", value: namedSlice{}}, {name: "empty array", value: [0]string{}}, } for _, tc := range collections { t.Run(tc.name, func(t *testing.T) { got, err := Copy(tc.value) if err != nil { t.Fatalf("copy collection: %v", err) } if !reflect.DeepEqual(got, tc.value) || reflect.TypeOf(got) != reflect.TypeOf(tc.value) { t.Fatalf("collection changed: got %#v (%T), want %#v (%T)", got, got, tc.value, tc.value) } kind := reflect.ValueOf(got).Kind() if (kind == reflect.Map || kind == reflect.Slice) && reflect.ValueOf(got).IsNil() { t.Fatal("non-nil collection became nil") } }) } var nilMap map[string]int var nilSlice []string var nilPointer *namedInt64 for _, value := range []any{nil, nilMap, nilSlice, nilPointer} { got, err := Copy(value) if err != nil { t.Fatalf("copy null value: %v", err) } if got != nil { t.Fatalf("null value became %#v (%T)", got, got) } } gotNil, err := CopyMap(nil) if err != nil || gotNil != nil { t.Fatalf("nil CopyMap result = %#v, %v", gotNil, err) } gotEmpty, err := CopyMap(map[string]any{}) if err != nil || gotEmpty == nil || len(gotEmpty) != 0 { t.Fatalf("empty CopyMap result = %#v, %v", gotEmpty, err) } } func TestCopyHandlesIndirectionAndIsolatesNestedMutations(t *testing.T) { integer := namedInt64(7) nestedMap := namedMap{"limit": 2} nestedSlice := namedSlice{"original"} nestedArray := namedArray{{"limit": 3}} shared := []any{map[string]int{"value": 4}} input := map[string]any{ "integer": &integer, "map": nestedMap, "slice": nestedSlice, "array": nestedArray, "first": shared, "second": shared, } copiedValue, err := Copy(input) if err != nil { t.Fatalf("copy mixed tree: %v", err) } copied := copiedValue.(map[string]any) nestedMap["limit"] = 20 nestedSlice[0] = "changed" nestedArray[0]["limit"] = 30 shared[0].(map[string]int)["value"] = 40 if got, ok := copied["integer"].(namedInt64); !ok || got != 7 { t.Fatalf("pointer target changed: %#v", copied["integer"]) } if got := copied["map"].(namedMap)["limit"]; got != 2 { t.Fatalf("nested map aliased input: %d", got) } if got := copied["slice"].(namedSlice)[0]; got != "original" { t.Fatalf("nested slice aliased input: %q", got) } if got := copied["array"].(namedArray)[0]["limit"]; got != 3 { t.Fatalf("nested array aliased input: %d", got) } first := copied["first"].([]any) second := copied["second"].([]any) if got := first[0].(map[string]int)["value"]; got != 4 { t.Fatalf("shared child aliased input: %d", got) } first[0].(map[string]int)["value"] = 99 if got := second[0].(map[string]int)["value"]; got != 4 { t.Fatalf("repeated acyclic value shared copied output: %d", got) } } func TestCopyAndCopyMapApplyDistinctEmptyKeyRules(t *testing.T) { nested := map[string]any{"": []any{"original"}} copiedValue, err := Copy(nested) if err != nil { t.Fatalf("Copy rejected empty schema key: %v", err) } nested[""].([]any)[0] = "changed" if got := copiedValue.(map[string]any)[""].([]any)[0]; got != "original" { t.Fatalf("copied schema value was not isolated: %v", got) } _, err = CopyMap(map[string]any{"nested": map[string]any{"": true}}) if err == nil || !strings.Contains(err.Error(), "extra_params.nested") { t.Fatalf("CopyMap empty-key error = %v", err) } } func TestCopyRejectsUnsupportedValuesAndActiveCycles(t *testing.T) { cyclicMap := map[string]any{} cyclicMap["self"] = cyclicMap cyclicSlice := []any{nil} cyclicSlice[0] = cyclicSlice var cyclicPointer any cyclicPointer = &cyclicPointer tests := []struct { name string value any wantPath string }{ {name: "non-string map key", value: map[int]string{1: "one"}, wantPath: "value"}, {name: "unsupported channel", value: make(chan int), wantPath: "value"}, {name: "deterministic map path", value: map[string]any{"z": make(chan int), "a": make(chan int)}, wantPath: "value.a"}, {name: "cyclic map", value: cyclicMap, wantPath: "value.self"}, {name: "cyclic slice", value: cyclicSlice, wantPath: "value[0]"}, {name: "cyclic pointer", value: cyclicPointer, wantPath: "value"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { _, err := Copy(tc.value) if err == nil || !strings.Contains(err.Error(), tc.wantPath) { t.Fatalf("error = %v, want structural path %q", err, tc.wantPath) } }) } } func TestCopyEnforcesContainerDepth(t *testing.T) { tests := []struct { name string depth int wantErr bool }{ {name: "just below", depth: maxContainerDepth - 1}, {name: "at limit", depth: maxContainerDepth}, {name: "over limit", depth: maxContainerDepth + 1, wantErr: true}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { _, err := Copy(alternatingContainers(tc.depth)) if tc.wantErr { if err == nil || !strings.HasPrefix(err.Error(), "value") || !strings.Contains(err.Error(), "container depth limit") { t.Fatalf("depth error = %v", err) } return } if err != nil { t.Fatalf("copy depth %d: %v", tc.depth, err) } }) } } func TestCopyEnforcesProducedNodeBudgetForRepeatedAcyclicValues(t *testing.T) { shared := []any{true} sharedOccurrences := (maxProducedNodes - 2) / 2 justBelow := repeatedValues(shared, sharedOccurrences, 0) atLimit := repeatedValues(shared, sharedOccurrences, 1) overLimit := repeatedValues(shared, sharedOccurrences, 2) for name, value := range map[string]any{ "just below": justBelow, "at limit": atLimit, } { t.Run(name, func(t *testing.T) { if _, err := Copy(value); err != nil { t.Fatalf("copy value within work budget: %v", err) } }) } _, err := Copy(overLimit) if err == nil || !strings.HasPrefix(err.Error(), "value[") || !strings.Contains(err.Error(), "value work limit") { t.Fatalf("work-budget error = %v", err) } _, err = Copy(make([]any, maxProducedNodes)) if err == nil || !strings.HasPrefix(err.Error(), "value:") || !strings.Contains(err.Error(), "value work limit") { t.Fatalf("flat work-budget error = %v", err) } } func alternatingContainers(depth int) any { var value any = true for level := 0; level < depth; level++ { switch level % 3 { case 0: value = map[string]any{"child": value} case 1: value = []any{value} default: value = [1]any{value} } } return value } func repeatedValues(shared []any, occurrences, leadingScalars int) []any { values := make([]any, 0, leadingScalars+occurrences) for i := 0; i < leadingScalars; i++ { values = append(values, false) } for i := 0; i < occurrences; i++ { values = append(values, shared) } return values }