mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-09 07:39:19 -05:00
AppendUnquote reports invalid UTF-8 after replacing it, which failed the whole copy although the decoder allows it. Key now holds U+FFFD in its place, as with encoding/json and ScanPackument, and the key is still written with its original bytes.
202 lines
5.3 KiB
Go
202 lines
5.3 KiB
Go
package jsonstream
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"errors"
|
||
"slices"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
func copyString(t *testing.T, in string, fn func(*Member) error) (string, error) {
|
||
t.Helper()
|
||
var out bytes.Buffer
|
||
err := CopyObject(strings.NewReader(in), &out, fn)
|
||
return out.String(), err
|
||
}
|
||
|
||
func TestCopyObjectKeepsValueBytes(t *testing.T) {
|
||
in := `{"a": 1.50, "b" : {"x": "<&>", "y":[1, 2]}, "c":null, "d":"é"}`
|
||
got, err := copyString(t, in, func(m *Member) error { return nil })
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if want := `{"a":1.50,"b":{"x": "<&>", "y":[1, 2]},"c":null,"d":"é"}`; got != want {
|
||
t.Errorf("got %s\nwant %s", got, want)
|
||
}
|
||
}
|
||
|
||
func TestCopyObjectFiltersNested(t *testing.T) {
|
||
in := `{"info":{"subdir":"noarch"},"packages":{"keep":{"n":1},"drop":{"n":2},"also":{"n":3}},"tail":[]}`
|
||
got, err := copyString(t, in, func(m *Member) error {
|
||
if m.Key != "packages" {
|
||
return m.Copy()
|
||
}
|
||
return m.Object(func(entry *Member) error {
|
||
raw, err := entry.Raw()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if entry.Key == "drop" {
|
||
return nil
|
||
}
|
||
return entry.Write(raw)
|
||
})
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if want := `{"info":{"subdir":"noarch"},"packages":{"keep":{"n":1},"also":{"n":3}},"tail":[]}`; got != want {
|
||
t.Errorf("got %s\nwant %s", got, want)
|
||
}
|
||
if !json.Valid([]byte(got)) {
|
||
t.Error("output is not valid JSON")
|
||
}
|
||
}
|
||
|
||
func TestCopyObjectSkipReplaceAndEmpty(t *testing.T) {
|
||
got, err := copyString(t, `{"first":1,"secret":"x","n":2,"empty":{}}`, func(m *Member) error {
|
||
switch m.Key {
|
||
case "first", "secret":
|
||
_, err := m.Raw()
|
||
return err
|
||
case "n":
|
||
if _, err := m.Raw(); err != nil {
|
||
return err
|
||
}
|
||
return m.Write([]byte(`3`))
|
||
case "empty":
|
||
return m.Object(func(*Member) error { return nil })
|
||
}
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if want := `{"n":3,"empty":{}}`; got != want {
|
||
t.Errorf("got %s, want %s", got, want)
|
||
}
|
||
}
|
||
|
||
func TestCopyObjectKeys(t *testing.T) {
|
||
got, err := copyString(t, `{"a<b":1,"quote\"d":2,"tab\t":3}`, func(m *Member) error { return nil })
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if want := `{"a<b":1,"quote\"d":2,"tab\t":3}`; got != want {
|
||
t.Errorf("got %s, want %s", got, want)
|
||
}
|
||
}
|
||
|
||
func TestMemberMisuse(t *testing.T) {
|
||
_, err := copyString(t, `{"a":1}`, func(m *Member) error { return m.Write([]byte("1")) })
|
||
if !errors.Is(err, errUnconsumed) {
|
||
t.Errorf("Write before Raw: err = %v", err)
|
||
}
|
||
_, err = copyString(t, `{"a":1}`, func(m *Member) error {
|
||
if _, err := m.Raw(); err != nil {
|
||
return err
|
||
}
|
||
_, err := m.Raw()
|
||
return err
|
||
})
|
||
if !errors.Is(err, errConsumed) {
|
||
t.Errorf("second Raw: err = %v", err)
|
||
}
|
||
}
|
||
|
||
func TestObjectNull(t *testing.T) {
|
||
got, err := copyString(t, `{"packages":null}`, func(m *Member) error {
|
||
return m.Object(func(*Member) error { return nil })
|
||
})
|
||
if err != nil || got != `{"packages":null}` {
|
||
t.Errorf("got %s, %v", got, err)
|
||
}
|
||
}
|
||
|
||
func TestCopyObjectErrors(t *testing.T) {
|
||
tests := map[string]string{
|
||
"not an object": `[1,2]`,
|
||
"truncated": `{"a":{"b":1}`,
|
||
"truncated value": `{"a":"abc`,
|
||
"trailing data": `{"a":1} {"b":2}`,
|
||
"nested not an object": `{"packages":[1]}`,
|
||
"empty": ``,
|
||
}
|
||
for name, in := range tests {
|
||
t.Run(name, func(t *testing.T) {
|
||
_, err := copyString(t, in, func(m *Member) error {
|
||
if m.Key == "packages" {
|
||
return m.Object(func(*Member) error { return nil })
|
||
}
|
||
return nil
|
||
})
|
||
if err == nil {
|
||
t.Error("expected an error")
|
||
}
|
||
})
|
||
}
|
||
if _, err := copyString(t, `[1]`, func(*Member) error { return nil }); !errors.Is(err, ErrNotObject) {
|
||
t.Errorf("err = %v, want ErrNotObject", err)
|
||
}
|
||
}
|
||
|
||
// TestCopyObjectReadsJSONAsEncodingJSON checks CopyObject accepts what
|
||
// encoding/json does: repeated names, each passed to fn, and invalid UTF-8.
|
||
// Keys keep their bytes, escapes included, while Key is unquoted.
|
||
func TestCopyObjectReadsJSONAsEncodingJSON(t *testing.T) {
|
||
var keys []string
|
||
in := "{\"a\":1,\"a\":2,\"caf\\u00e9\":\"bad \xff byte\",\"skip\":{\"x\":[1,2]}}"
|
||
got, err := copyString(t, in, func(m *Member) error {
|
||
keys = append(keys, m.Key)
|
||
if m.Key == "skip" {
|
||
return m.Skip()
|
||
}
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if want := "{\"a\":1,\"a\":2,\"caf\\u00e9\":\"bad \xff byte\"}"; got != want {
|
||
t.Errorf("got %q, want %q", got, want)
|
||
}
|
||
if want := []string{"a", "a", "café", "skip"}; !slices.Equal(keys, want) {
|
||
t.Errorf("keys %q, want %q", keys, want)
|
||
}
|
||
}
|
||
|
||
func TestSkipThenWriteReplaces(t *testing.T) {
|
||
got, err := copyString(t, `{"a":{"big":[1,2,3]},"b":2}`, func(m *Member) error {
|
||
if m.Key != "a" {
|
||
return nil
|
||
}
|
||
if err := m.Skip(); err != nil {
|
||
return err
|
||
}
|
||
return m.Write([]byte(`"small"`))
|
||
})
|
||
if err != nil || got != `{"a":"small","b":2}` {
|
||
t.Errorf("got %s, %v", got, err)
|
||
}
|
||
}
|
||
|
||
// TestCopyObjectInvalidUTF8Key checks a key with invalid UTF-8 is accepted:
|
||
// Key has U+FFFD in its place, as with encoding/json, and the key is written
|
||
// with its original bytes.
|
||
func TestCopyObjectInvalidUTF8Key(t *testing.T) {
|
||
var keys []string
|
||
got, err := copyString(t, "{\"k\xff\":1}", func(m *Member) error {
|
||
keys = append(keys, m.Key)
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if want := "{\"k\xff\":1}"; got != want {
|
||
t.Errorf("got %q, want %q", got, want)
|
||
}
|
||
if want := []string{"k<>"}; !slices.Equal(keys, want) {
|
||
t.Errorf("keys %q, want %q", keys, want)
|
||
}
|
||
}
|