File: json_test.go

package info (click to toggle)
golang-github-containers-image 5.28.0-4
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 5,104 kB
  • sloc: sh: 194; makefile: 73
file content (138 lines) | stat: -rw-r--r-- 4,477 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package internal

import (
	"encoding/json"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type mSA map[string]any // To minimize typing the long name

// implementsUnmarshalJSON is a minimalistic type used to detect that
// paranoidUnmarshalJSONObject uses the json.Unmarshaler interface of resolved
// pointers.
type implementsUnmarshalJSON bool

// Compile-time check that Policy implements json.Unmarshaler.
var _ json.Unmarshaler = (*implementsUnmarshalJSON)(nil)

func (dest *implementsUnmarshalJSON) UnmarshalJSON(data []byte) error {
	_ = data     // We don't care, not really.
	*dest = true // Mark handler as called
	return nil
}

func TestParanoidUnmarshalJSONObject(t *testing.T) {
	type testStruct struct {
		A string
		B int
	}
	ts := testStruct{}
	var unmarshalJSONCalled implementsUnmarshalJSON
	tsResolver := func(key string) any {
		switch key {
		case "a":
			return &ts.A
		case "b":
			return &ts.B
		case "implementsUnmarshalJSON":
			return &unmarshalJSONCalled
		default:
			return nil
		}
	}

	// Empty object
	ts = testStruct{}
	err := ParanoidUnmarshalJSONObject([]byte(`{}`), tsResolver)
	require.NoError(t, err)
	assert.Equal(t, testStruct{}, ts)

	// Success
	ts = testStruct{}
	err = ParanoidUnmarshalJSONObject([]byte(`{"a":"x", "b":2}`), tsResolver)
	require.NoError(t, err)
	assert.Equal(t, testStruct{A: "x", B: 2}, ts)

	// json.Unmarshaler is used for decoding values
	ts = testStruct{}
	unmarshalJSONCalled = implementsUnmarshalJSON(false)
	err = ParanoidUnmarshalJSONObject([]byte(`{"implementsUnmarshalJSON":true}`), tsResolver)
	require.NoError(t, err)
	assert.Equal(t, unmarshalJSONCalled, implementsUnmarshalJSON(true))

	// Various kinds of invalid input
	for _, input := range []string{
		``,                       // Empty input
		`&`,                      // Entirely invalid JSON
		`1`,                      // Not an object
		`{&}`,                    // Invalid key JSON
		`{1:1}`,                  // Key not a string
		`{"b":1, "b":1}`,         // Duplicate key
		`{"thisdoesnotexist":1}`, // Key rejected by resolver
		`{"a":&}`,                // Invalid value JSON
		`{"a":1}`,                // Type mismatch
		`{"a":"value"}{}`,        // Extra data after object
	} {
		ts = testStruct{}
		err := ParanoidUnmarshalJSONObject([]byte(input), tsResolver)
		assert.Error(t, err, input)
	}
}

func TestParanoidUnmarshalJSONObjectExactFields(t *testing.T) {
	var stringValue string
	var float64Value float64
	var rawValue json.RawMessage
	var unmarshallCalled implementsUnmarshalJSON
	exactFields := map[string]any{
		"string":       &stringValue,
		"float64":      &float64Value,
		"raw":          &rawValue,
		"unmarshaller": &unmarshallCalled,
	}

	// Empty object
	err := ParanoidUnmarshalJSONObjectExactFields([]byte(`{}`), map[string]any{})
	require.NoError(t, err)

	// Success
	err = ParanoidUnmarshalJSONObjectExactFields([]byte(`{"string": "a", "float64": 3.5, "raw": {"a":"b"}, "unmarshaller": true}`), exactFields)
	require.NoError(t, err)
	assert.Equal(t, "a", stringValue)
	assert.Equal(t, 3.5, float64Value)
	assert.Equal(t, json.RawMessage(`{"a":"b"}`), rawValue)
	assert.Equal(t, implementsUnmarshalJSON(true), unmarshallCalled)

	// Various kinds of invalid input
	for _, input := range []string{
		``,      // Empty input
		`&`,     // Entirely invalid JSON
		`1`,     // Not an object
		`{&}`,   // Invalid key JSON
		`{1:1}`, // Key not a string
		`{"string": "a", "string": "a", "float64": 3.5, "raw": {"a":"b"}, "unmarshaller": true}`,      // Duplicate key
		`{"string": "a", "float64": 3.5, "raw": {"a":"b"}, "unmarshaller": true, "thisisunknown", 1}`, // Unknown key
		`{"string": &, "float64": 3.5, "raw": {"a":"b"}, "unmarshaller": true}`,                       // Invalid value JSON
		`{"string": 1, "float64": 3.5, "raw": {"a":"b"}, "unmarshaller": true}`,                       // Type mismatch
		`{"string": "a", "float64": 3.5, "raw": {"a":"b"}, "unmarshaller": true}{}`,                   // Extra data after object
	} {
		err := ParanoidUnmarshalJSONObjectExactFields([]byte(input), exactFields)
		assert.Error(t, err, input)
	}
}

// Return the result of modifying validJSON with fn
func modifiedJSON(t *testing.T, validJSON []byte, modifyFn func(mSA)) []byte {
	var tmp mSA
	err := json.Unmarshal(validJSON, &tmp)
	require.NoError(t, err)

	modifyFn(tmp)

	modifiedJSON, err := json.Marshal(tmp)
	require.NoError(t, err)
	return modifiedJSON
}