98 lines
2.8 KiB
Go
98 lines
2.8 KiB
Go
package jsonvalue_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"math"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"gitea.maximumdirect.net/eric/promptkit/internal/jsonvalue"
|
|
)
|
|
|
|
func TestCopyMapPreservesTypesAndIsolatesMutations(t *testing.T) {
|
|
nested := map[string]int{"limit": 2}
|
|
sequence := []string{"one", "two"}
|
|
input := map[string]any{
|
|
"count": int64(7),
|
|
"number": json.Number("-1.25e+2"),
|
|
"nested": nested,
|
|
"sequence": sequence,
|
|
}
|
|
|
|
copied, err := jsonvalue.CopyMap(input)
|
|
if err != nil {
|
|
t.Fatalf("copy map: %v", err)
|
|
}
|
|
nested["limit"] = 99
|
|
sequence[0] = "changed"
|
|
input["added"] = true
|
|
|
|
if got, ok := copied["count"].(int64); !ok || got != 7 {
|
|
t.Fatalf("integer type or value changed: %#v", copied["count"])
|
|
}
|
|
if got, ok := copied["number"].(json.Number); !ok || got != "-1.25e+2" {
|
|
t.Fatalf("JSON number type or value changed: %#v", copied["number"])
|
|
}
|
|
if got := copied["nested"].(map[string]int)["limit"]; got != 2 {
|
|
t.Fatalf("nested map was not isolated: %d", got)
|
|
}
|
|
if got := copied["sequence"].([]string)[0]; got != "one" {
|
|
t.Fatalf("sequence was not isolated: %q", got)
|
|
}
|
|
if _, ok := copied["added"]; ok {
|
|
t.Fatalf("top-level map was not isolated: %#v", copied)
|
|
}
|
|
}
|
|
|
|
func TestCopyMapRejectsInvalidValues(t *testing.T) {
|
|
cyclicMap := map[string]any{}
|
|
cyclicMap["self"] = cyclicMap
|
|
cyclicSlice := []any{nil}
|
|
cyclicSlice[0] = cyclicSlice
|
|
|
|
tests := []struct {
|
|
name string
|
|
value any
|
|
}{
|
|
{name: "empty nested key", value: map[string]int{"": 1}},
|
|
{name: "non-string map key", value: map[int]string{1: "one"}},
|
|
{name: "unsupported value", value: make(chan int)},
|
|
{name: "cyclic map", value: cyclicMap},
|
|
{name: "cyclic slice", value: cyclicSlice},
|
|
{name: "NaN", value: math.NaN()},
|
|
{name: "positive infinity", value: math.Inf(1)},
|
|
{name: "unsafe signed integer", value: int64(1 << 53)},
|
|
{name: "unsafe unsigned integer", value: uint64(1 << 53)},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if _, err := jsonvalue.CopyMap(map[string]any{"value": tc.value}); err == nil {
|
|
t.Fatal("expected validation error")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCopyMapValidatesJSONNumberSyntaxAndRange(t *testing.T) {
|
|
for _, number := range []json.Number{"0", "-1", "1.25", "-1.25e+2"} {
|
|
t.Run("valid "+number.String(), func(t *testing.T) {
|
|
got, err := jsonvalue.CopyMap(map[string]any{"value": number})
|
|
if err != nil {
|
|
t.Fatalf("copy valid JSON number: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(got["value"], number) {
|
|
t.Fatalf("JSON number changed: got %#v want %#v", got["value"], number)
|
|
}
|
|
})
|
|
}
|
|
|
|
for _, number := range []json.Number{"", "01", "+1", "1.", ".1", "1e9999", "not-a-number"} {
|
|
t.Run("invalid "+number.String(), func(t *testing.T) {
|
|
if _, err := jsonvalue.CopyMap(map[string]any{"value": number}); err == nil {
|
|
t.Fatal("expected invalid JSON number error")
|
|
}
|
|
})
|
|
}
|
|
}
|