// Copyright (C) 2023 Luke Shumaker // // SPDX-License-Identifier: GPL-2.0-or-later package json import ( "bytes" "reflect" "strings" "testing" "github.com/stretchr/testify/assert" ) func TestCompatHTMLEscape(t *testing.T) { t.Parallel() type testcase struct { In string Out string } testcases := map[string]testcase{ "invalid": {In: `x`, Out: `x`}, "hex-lower": {In: `"\uabcd"`, Out: `"\uabcd"`}, "hex-upper": {In: `"\uABCD"`, Out: `"\uABCD"`}, "hex-mixed": {In: `"\uAbCd"`, Out: `"\uAbCd"`}, } for tcName, tc := range testcases { tc := tc t.Run(tcName, func(t *testing.T) { t.Parallel() t.Logf("in=%q", tc.In) var dst bytes.Buffer HTMLEscape(&dst, []byte(tc.In)) assert.Equal(t, tc.Out, dst.String()) }) } } func TestCompatValid(t *testing.T) { t.Parallel() type testcase struct { In string Exp bool } testcases := map[string]testcase{ "empty": {In: ``, Exp: false}, "num": {In: `1`, Exp: true}, "trunc": {In: `{`, Exp: false}, "object": {In: `{}`, Exp: true}, "non-utf8": {In: "\"\x85\xcd\"", Exp: false}, // https://github.com/golang/go/issues/58517 "hex-lower": {In: `"\uabcd"`, Exp: true}, "hex-upper": {In: `"\uABCD"`, Exp: true}, "hex-mixed": {In: `"\uAbCd"`, Exp: true}, } for tcName, tc := range testcases { tc := tc t.Run(tcName, func(t *testing.T) { t.Parallel() t.Logf("in=%q", tc.In) act := Valid([]byte(tc.In)) assert.Equal(t, tc.Exp, act) }) } } func TestCompatCompact(t *testing.T) { t.Parallel() type testcase struct { In string Out string Err string } testcases := map[string]testcase{ "trunc": {In: `{`, Out: ``, Err: `unexpected end of JSON input`}, "object": {In: `{}`, Out: `{}`}, "non-utf8": {In: "\"\x85\xcd\"", Out: "\"\x85\xcd\""}, "float": {In: `1.200e003`, Out: `1.200e003`}, "hex-lower": {In: `"\uabcd"`, Out: `"\uabcd"`}, "hex-upper": {In: `"\uABCD"`, Out: `"\uABCD"`}, "hex-mixed": {In: `"\uAbCd"`, Out: `"\uAbCd"`}, "invalid-utf8": {In: "\x85", Err: `invalid character '\u0085' looking for beginning of value`}, } for tcName, tc := range testcases { tc := tc t.Run(tcName, func(t *testing.T) { t.Parallel() t.Logf("in=%q", tc.In) var out bytes.Buffer err := Compact(&out, []byte(tc.In)) assert.Equal(t, tc.Out, out.String()) if tc.Err == "" { assert.NoError(t, err) } else { assert.EqualError(t, err, tc.Err) } }) } } func TestCompatIndent(t *testing.T) { t.Parallel() type testcase struct { In string Out string Err string } testcases := map[string]testcase{ "trunc": {In: `{`, Out: ``, Err: `unexpected end of JSON input`}, "object": {In: `{}`, Out: `{}`}, "non-utf8": {In: "\"\x85\xcd\"", Out: "\"\x85\xcd\""}, "float": {In: `1.200e003`, Out: `1.200e003`}, "tailws0": {In: `0`, Out: `0`}, "tailws1": {In: `0 `, Out: `0 `}, "tailws2": {In: `0 `, Out: `0 `}, "tailws3": {In: "0\n", Out: "0\n"}, "headws1": {In: ` 0`, Out: `0`}, "objws1": {In: `{"a" : 1}`, Out: "{\n>.\"a\": 1\n>}"}, "objws2": {In: "{\"a\"\n:\n1}", Out: "{\n>.\"a\": 1\n>}"}, "hex-lower": {In: `"\uabcd"`, Out: `"\uabcd"`}, "hex-upper": {In: `"\uABCD"`, Out: `"\uABCD"`}, "hex-mixed": {In: `"\uAbCd"`, Out: `"\uAbCd"`}, "invalid-utf8": {In: "\x85", Err: `invalid character '\u0085' looking for beginning of value`}, } for tcName, tc := range testcases { tc := tc t.Run(tcName, func(t *testing.T) { t.Parallel() t.Logf("in=%q", tc.In) var out bytes.Buffer err := Indent(&out, []byte(tc.In), ">", ".") assert.Equal(t, tc.Out, out.String()) if tc.Err == "" { assert.NoError(t, err) } else { assert.EqualError(t, err, tc.Err) } }) } } func TestCompatMarshal(t *testing.T) { t.Parallel() type testcase struct { In any Out string Err string } testcases := map[string]testcase{ "non-utf8": {In: "\x85\xcd", Out: "\"\\ufffd\\ufffd\""}, "urc": {In: "\ufffd", Out: "\"\ufffd\""}, "float": {In: 1.2e3, Out: `1200`}, } for tcName, tc := range testcases { tc := tc t.Run(tcName, func(t *testing.T) { t.Parallel() out, err := Marshal(tc.In) assert.Equal(t, tc.Out, string(out)) if tc.Err == "" { assert.NoError(t, err) } else { assert.EqualError(t, err, tc.Err) } }) } } func TestCompatUnmarshal(t *testing.T) { t.Parallel() type testcase struct { In string InPtr any ExpOut any ExpErr string } testcases := map[string]testcase{ "empty-obj": {In: `{}`, ExpOut: map[string]any{}}, "partial-obj": {In: `{"foo":"bar",`, ExpOut: nil, ExpErr: `unexpected end of JSON input`}, "existing-obj": {In: `{"baz":"quz"}`, InPtr: &map[string]string{"foo": "bar"}, ExpOut: map[string]string{"foo": "bar", "baz": "quz"}}, "existing-obj-partial": {In: `{"baz":"quz"`, InPtr: &map[string]string{"foo": "bar"}, ExpOut: map[string]string{"foo": "bar"}, ExpErr: "unexpected end of JSON input"}, "empty-ary": {In: `[]`, ExpOut: []any{}}, "two-objs": {In: `{} {}`, ExpOut: nil, ExpErr: `invalid character '{' after top-level value`}, "two-numbers1": {In: `00`, ExpOut: nil, ExpErr: `invalid character '0' after top-level value`}, "two-numbers2": {In: `1 2`, ExpOut: nil, ExpErr: `invalid character '2' after top-level value`}, "invalid-utf8": {In: "\x85", ExpErr: `invalid character '\u0085' looking for beginning of value`}, // 2e308 is slightly more than math.MaxFloat64 (~1.79e308) "obj-overflow": {In: `{"foo":"bar", "baz":2e308, "qux": "orb"}`, ExpOut: map[string]any{"foo": "bar", "baz": nil, "qux": "orb"}, ExpErr: `json: cannot unmarshal number 2e308 into Go value of type float64`}, "ary-overflow": {In: `["foo",2e308,"bar",3e308]`, ExpOut: []any{"foo", nil, "bar", nil}, ExpErr: `json: cannot unmarshal number 2e308 into Go value of type float64`}, "existing-overflow": {In: `2e308`, InPtr: func() any { x := 4; return &x }(), ExpOut: 4, ExpErr: `json: cannot unmarshal number 2e308 into Go value of type int`}, } for tcName, tc := range testcases { tc := tc t.Run(tcName, func(t *testing.T) { t.Parallel() ptr := tc.InPtr if ptr == nil { var out any ptr = &out } err := Unmarshal([]byte(tc.In), ptr) assert.Equal(t, tc.ExpOut, reflect.ValueOf(ptr).Elem().Interface()) if tc.ExpErr == "" { assert.NoError(t, err) } else { assert.EqualError(t, err, tc.ExpErr) } }) } } func TestCompatDecode(t *testing.T) { t.Parallel() type testcase struct { In string InPtr any ExpOut any ExpErr string } testcases := map[string]testcase{ "empty-obj": {In: `{}`, ExpOut: map[string]any{}}, "partial-obj": {In: `{"foo":"bar",`, ExpOut: nil, ExpErr: `unexpected EOF`}, "existing-obj": {In: `{"baz":"quz"}`, InPtr: &map[string]string{"foo": "bar"}, ExpOut: map[string]string{"foo": "bar", "baz": "quz"}}, "existing-obj-partial": {In: `{"baz":"quz"`, InPtr: &map[string]string{"foo": "bar"}, ExpOut: map[string]string{"foo": "bar"}, ExpErr: "unexpected EOF"}, "empty-ary": {In: `[]`, ExpOut: []any{}}, "two-objs": {In: `{} {}`, ExpOut: map[string]any{}}, "two-numbers1": {In: `00`, ExpOut: float64(0)}, "two-numbers2": {In: `1 2`, ExpOut: float64(1)}, "invalid-utf8": {In: "\x85", ExpErr: `invalid character '\u0085' looking for beginning of value`}, // 2e308 is slightly more than math.MaxFloat64 (~1.79e308) "obj-overflow": {In: `{"foo":"bar", "baz":2e308, "qux": "orb"}`, ExpOut: map[string]any{"foo": "bar", "baz": nil, "qux": "orb"}, ExpErr: `json: cannot unmarshal number 2e308 into Go value of type float64`}, "ary-overflow": {In: `["foo",2e308,"bar",3e308]`, ExpOut: []any{"foo", nil, "bar", nil}, ExpErr: `json: cannot unmarshal number 2e308 into Go value of type float64`}, "existing-overflow": {In: `2e308`, InPtr: func() any { x := 4; return &x }(), ExpOut: 4, ExpErr: `json: cannot unmarshal number 2e308 into Go value of type int`}, } for tcName, tc := range testcases { tc := tc t.Run(tcName, func(t *testing.T) { t.Parallel() ptr := tc.InPtr if ptr == nil { var out any ptr = &out } err := NewDecoder(strings.NewReader(tc.In)).Decode(ptr) assert.Equal(t, tc.ExpOut, reflect.ValueOf(ptr).Elem().Interface()) if tc.ExpErr == "" { assert.NoError(t, err) } else { assert.EqualError(t, err, tc.ExpErr) } }) } }