1package jsonpatch
2
3import (
4	"bytes"
5	"encoding/json"
6	"fmt"
7	"reflect"
8	"testing"
9)
10
11func reformatJSON(j string) string {
12	buf := new(bytes.Buffer)
13
14	json.Indent(buf, []byte(j), "", "  ")
15
16	return buf.String()
17}
18
19func compareJSON(a, b string) bool {
20	// return Equal([]byte(a), []byte(b))
21
22	var objA, objB map[string]interface{}
23	json.Unmarshal([]byte(a), &objA)
24	json.Unmarshal([]byte(b), &objB)
25
26	// fmt.Printf("Comparing %#v\nagainst %#v\n", objA, objB)
27	return reflect.DeepEqual(objA, objB)
28}
29
30func applyPatch(doc, patch string) (string, error) {
31	obj, err := DecodePatch([]byte(patch))
32
33	if err != nil {
34		panic(err)
35	}
36
37	out, err := obj.Apply([]byte(doc))
38
39	if err != nil {
40		return "", err
41	}
42
43	return string(out), nil
44}
45
46type Case struct {
47	doc, patch, result string
48}
49
50func repeatedA(r int) string {
51	var s string
52	for i := 0; i < r; i++ {
53		s += "A"
54	}
55	return s
56}
57
58var Cases = []Case{
59	{
60		`{ "foo": "bar"}`,
61		`[
62         { "op": "add", "path": "/baz", "value": "qux" }
63     ]`,
64		`{
65       "baz": "qux",
66       "foo": "bar"
67     }`,
68	},
69	{
70		`{ "foo": [ "bar", "baz" ] }`,
71		`[
72     { "op": "add", "path": "/foo/1", "value": "qux" }
73    ]`,
74		`{ "foo": [ "bar", "qux", "baz" ] }`,
75	},
76	{
77		`{ "foo": [ "bar", "baz" ] }`,
78		`[
79     { "op": "add", "path": "/foo/-1", "value": "qux" }
80    ]`,
81		`{ "foo": [ "bar", "baz", "qux" ] }`,
82	},
83	{
84		`{ "baz": "qux", "foo": "bar" }`,
85		`[ { "op": "remove", "path": "/baz" } ]`,
86		`{ "foo": "bar" }`,
87	},
88	{
89		`{ "foo": [ "bar", "qux", "baz" ] }`,
90		`[ { "op": "remove", "path": "/foo/1" } ]`,
91		`{ "foo": [ "bar", "baz" ] }`,
92	},
93	{
94		`{ "baz": "qux", "foo": "bar" }`,
95		`[ { "op": "replace", "path": "/baz", "value": "boo" } ]`,
96		`{ "baz": "boo", "foo": "bar" }`,
97	},
98	{
99		`{
100     "foo": {
101       "bar": "baz",
102       "waldo": "fred"
103     },
104     "qux": {
105       "corge": "grault"
106     }
107   }`,
108		`[ { "op": "move", "from": "/foo/waldo", "path": "/qux/thud" } ]`,
109		`{
110     "foo": {
111       "bar": "baz"
112     },
113     "qux": {
114       "corge": "grault",
115       "thud": "fred"
116     }
117   }`,
118	},
119	{
120		`{ "foo": [ "all", "grass", "cows", "eat" ] }`,
121		`[ { "op": "move", "from": "/foo/1", "path": "/foo/3" } ]`,
122		`{ "foo": [ "all", "cows", "eat", "grass" ] }`,
123	},
124	{
125		`{ "foo": [ "all", "grass", "cows", "eat" ] }`,
126		`[ { "op": "move", "from": "/foo/1", "path": "/foo/2" } ]`,
127		`{ "foo": [ "all", "cows", "grass", "eat" ] }`,
128	},
129	{
130		`{ "foo": "bar" }`,
131		`[ { "op": "add", "path": "/child", "value": { "grandchild": { } } } ]`,
132		`{ "foo": "bar", "child": { "grandchild": { } } }`,
133	},
134	{
135		`{ "foo": ["bar"] }`,
136		`[ { "op": "add", "path": "/foo/-", "value": ["abc", "def"] } ]`,
137		`{ "foo": ["bar", ["abc", "def"]] }`,
138	},
139	{
140		`{ "foo": "bar", "qux": { "baz": 1, "bar": null } }`,
141		`[ { "op": "remove", "path": "/qux/bar" } ]`,
142		`{ "foo": "bar", "qux": { "baz": 1 } }`,
143	},
144	{
145		`{ "foo": "bar" }`,
146		`[ { "op": "add", "path": "/baz", "value": null } ]`,
147		`{ "baz": null, "foo": "bar" }`,
148	},
149	{
150		`{ "foo": ["bar"]}`,
151		`[ { "op": "replace", "path": "/foo/0", "value": "baz"}]`,
152		`{ "foo": ["baz"]}`,
153	},
154	{
155		`{ "foo": ["bar","baz"]}`,
156		`[ { "op": "replace", "path": "/foo/0", "value": "bum"}]`,
157		`{ "foo": ["bum","baz"]}`,
158	},
159	{
160		`{ "foo": ["bar","qux","baz"]}`,
161		`[ { "op": "replace", "path": "/foo/1", "value": "bum"}]`,
162		`{ "foo": ["bar", "bum","baz"]}`,
163	},
164	{
165		`[ {"foo": ["bar","qux","baz"]}]`,
166		`[ { "op": "replace", "path": "/0/foo/0", "value": "bum"}]`,
167		`[ {"foo": ["bum","qux","baz"]}]`,
168	},
169	{
170		`[ {"foo": ["bar","qux","baz"], "bar": ["qux","baz"]}]`,
171		`[ { "op": "copy", "from": "/0/foo/0", "path": "/0/bar/0"}]`,
172		`[ {"foo": ["bar","qux","baz"], "bar": ["bar", "baz"]}]`,
173	},
174	{
175		`[ {"foo": ["bar","qux","baz"], "bar": ["qux","baz"]}]`,
176		`[ { "op": "copy", "from": "/0/foo/0", "path": "/0/bar"}]`,
177		`[ {"foo": ["bar","qux","baz"], "bar": ["bar", "qux", "baz"]}]`,
178	},
179	{
180		`[ { "foo": {"bar": ["qux","baz"]}, "baz": {"qux": "bum"}}]`,
181		`[ { "op": "copy", "from": "/0/foo/bar", "path": "/0/baz/bar"}]`,
182		`[ { "baz": {"bar": ["qux","baz"], "qux":"bum"}, "foo": {"bar": ["qux","baz"]}}]`,
183	},
184	{
185		`{ "foo": ["bar"]}`,
186		`[{"op": "copy", "path": "/foo/0", "from": "/foo"}]`,
187		`{ "foo": [["bar"], "bar"]}`,
188	},
189	{
190		`{ "foo": ["bar","qux","baz"]}`,
191		`[ { "op": "remove", "path": "/foo/-2"}]`,
192		`{ "foo": ["bar", "baz"]}`,
193	},
194	{
195		`{ "foo": []}`,
196		`[ { "op": "add", "path": "/foo/-1", "value": "qux"}]`,
197		`{ "foo": ["qux"]}`,
198	},
199	{
200		`{ "bar": [{"baz": null}]}`,
201		`[ { "op": "replace", "path": "/bar/0/baz", "value": 1 } ]`,
202		`{ "bar": [{"baz": 1}]}`,
203	},
204	{
205		`{ "bar": [{"baz": 1}]}`,
206		`[ { "op": "replace", "path": "/bar/0/baz", "value": null } ]`,
207		`{ "bar": [{"baz": null}]}`,
208	},
209	{
210		`{ "bar": [null]}`,
211		`[ { "op": "replace", "path": "/bar/0", "value": 1 } ]`,
212		`{ "bar": [1]}`,
213	},
214	{
215		`{ "bar": [1]}`,
216		`[ { "op": "replace", "path": "/bar/0", "value": null } ]`,
217		`{ "bar": [null]}`,
218	},
219	{
220		fmt.Sprintf(`{ "foo": ["A", %q] }`, repeatedA(48)),
221		// The wrapping quotes around 'A's are included in the copy
222		// size, so each copy operation increases the size by 50 bytes.
223		`[ { "op": "copy", "path": "/foo/-", "from": "/foo/1" },
224		   { "op": "copy", "path": "/foo/-", "from": "/foo/1" }]`,
225		fmt.Sprintf(`{ "foo": ["A", %q, %q, %q] }`, repeatedA(48), repeatedA(48), repeatedA(48)),
226	},
227}
228
229type BadCase struct {
230	doc, patch string
231}
232
233var MutationTestCases = []BadCase{
234	{
235		`{ "foo": "bar", "qux": { "baz": 1, "bar": null } }`,
236		`[ { "op": "remove", "path": "/qux/bar" } ]`,
237	},
238	{
239		`{ "foo": "bar", "qux": { "baz": 1, "bar": null } }`,
240		`[ { "op": "replace", "path": "/qux/baz", "value": null } ]`,
241	},
242}
243
244var BadCases = []BadCase{
245	{
246		`{ "foo": "bar" }`,
247		`[ { "op": "add", "path": "/baz/bat", "value": "qux" } ]`,
248	},
249	{
250		`{ "a": { "b": { "d": 1 } } }`,
251		`[ { "op": "remove", "path": "/a/b/c" } ]`,
252	},
253	{
254		`{ "a": { "b": { "d": 1 } } }`,
255		`[ { "op": "move", "from": "/a/b/c", "path": "/a/b/e" } ]`,
256	},
257	{
258		`{ "a": { "b": [1] } }`,
259		`[ { "op": "remove", "path": "/a/b/1" } ]`,
260	},
261	{
262		`{ "a": { "b": [1] } }`,
263		`[ { "op": "move", "from": "/a/b/1", "path": "/a/b/2" } ]`,
264	},
265	{
266		`{ "foo": "bar" }`,
267		`[ { "op": "add", "pathz": "/baz", "value": "qux" } ]`,
268	},
269	{
270		`{ "foo": "bar" }`,
271		`[ { "op": "add", "path": "", "value": "qux" } ]`,
272	},
273	{
274		`{ "foo": ["bar","baz"]}`,
275		`[ { "op": "replace", "path": "/foo/2", "value": "bum"}]`,
276	},
277	{
278		`{ "foo": ["bar","baz"]}`,
279		`[ { "op": "add", "path": "/foo/-4", "value": "bum"}]`,
280	},
281	{
282		`{ "name":{ "foo": "bat", "qux": "bum"}}`,
283		`[ { "op": "replace", "path": "/foo/bar", "value":"baz"}]`,
284	},
285	{
286		`{ "foo": ["bar"]}`,
287		`[ {"op": "add", "path": "/foo/2", "value": "bum"}]`,
288	},
289	{
290		`{ "foo": []}`,
291		`[ {"op": "remove", "path": "/foo/-"}]`,
292	},
293	{
294		`{ "foo": []}`,
295		`[ {"op": "remove", "path": "/foo/-1"}]`,
296	},
297	{
298		`{ "foo": ["bar"]}`,
299		`[ {"op": "remove", "path": "/foo/-2"}]`,
300	},
301	{
302		`{}`,
303		`[ {"op":null,"path":""} ]`,
304	},
305	{
306		`{}`,
307		`[ {"op":"add","path":null} ]`,
308	},
309	{
310		`{}`,
311		`[ { "op": "copy", "from": null }]`,
312	},
313	{
314		`{ "foo": ["bar"]}`,
315		`[{"op": "copy", "path": "/foo/6666666666", "from": "/"}]`,
316	},
317	// Can't copy into an index greater than the size of the array
318	{
319		`{ "foo": ["bar"]}`,
320		`[{"op": "copy", "path": "/foo/2", "from": "/foo/0"}]`,
321	},
322	// Accumulated copy size cannot exceed AccumulatedCopySizeLimit.
323	{
324		fmt.Sprintf(`{ "foo": ["A", %q] }`, repeatedA(49)),
325		// The wrapping quotes around 'A's are included in the copy
326		// size, so each copy operation increases the size by 51 bytes.
327		`[ { "op": "copy", "path": "/foo/-", "from": "/foo/1" },
328		   { "op": "copy", "path": "/foo/-", "from": "/foo/1" }]`,
329	},
330	// Can't move into an index greater than or equal to the size of the array
331	{
332		`{ "foo": [ "all", "grass", "cows", "eat" ] }`,
333		`[ { "op": "move", "from": "/foo/1", "path": "/foo/4" } ]`,
334	},
335}
336
337// This is not thread safe, so we cannot run patch tests in parallel.
338func configureGlobals(accumulatedCopySizeLimit int64) func() {
339	oldAccumulatedCopySizeLimit := AccumulatedCopySizeLimit
340	AccumulatedCopySizeLimit = accumulatedCopySizeLimit
341	return func() {
342		AccumulatedCopySizeLimit = oldAccumulatedCopySizeLimit
343	}
344}
345
346func TestAllCases(t *testing.T) {
347	defer configureGlobals(int64(100))()
348	for _, c := range Cases {
349		out, err := applyPatch(c.doc, c.patch)
350
351		if err != nil {
352			t.Errorf("Unable to apply patch: %s", err)
353		}
354
355		if !compareJSON(out, c.result) {
356			t.Errorf("Patch did not apply. Expected:\n%s\n\nActual:\n%s",
357				reformatJSON(c.result), reformatJSON(out))
358		}
359	}
360
361	for _, c := range MutationTestCases {
362		out, err := applyPatch(c.doc, c.patch)
363
364		if err != nil {
365			t.Errorf("Unable to apply patch: %s", err)
366		}
367
368		if compareJSON(out, c.doc) {
369			t.Errorf("Patch did not apply. Original:\n%s\n\nPatched:\n%s",
370				reformatJSON(c.doc), reformatJSON(out))
371		}
372	}
373
374	for _, c := range BadCases {
375		_, err := applyPatch(c.doc, c.patch)
376
377		if err == nil {
378			t.Errorf("Patch %q should have failed to apply but it did not", c.patch)
379		}
380	}
381}
382
383type TestCase struct {
384	doc, patch string
385	result     bool
386	failedPath string
387}
388
389var TestCases = []TestCase{
390	{
391		`{
392      "baz": "qux",
393      "foo": [ "a", 2, "c" ]
394    }`,
395		`[
396      { "op": "test", "path": "/baz", "value": "qux" },
397      { "op": "test", "path": "/foo/1", "value": 2 }
398    ]`,
399		true,
400		"",
401	},
402	{
403		`{ "baz": "qux" }`,
404		`[ { "op": "test", "path": "/baz", "value": "bar" } ]`,
405		false,
406		"/baz",
407	},
408	{
409		`{
410      "baz": "qux",
411      "foo": ["a", 2, "c"]
412    }`,
413		`[
414      { "op": "test", "path": "/baz", "value": "qux" },
415      { "op": "test", "path": "/foo/1", "value": "c" }
416    ]`,
417		false,
418		"/foo/1",
419	},
420	{
421		`{ "baz": "qux" }`,
422		`[ { "op": "test", "path": "/foo", "value": 42 } ]`,
423		false,
424		"/foo",
425	},
426	{
427		`{ "baz": "qux" }`,
428		`[ { "op": "test", "path": "/foo", "value": null } ]`,
429		true,
430		"",
431	},
432	{
433		`{ "foo": null }`,
434		`[ { "op": "test", "path": "/foo", "value": null } ]`,
435		true,
436		"",
437	},
438	{
439		`{ "foo": {} }`,
440		`[ { "op": "test", "path": "/foo", "value": null } ]`,
441		false,
442		"/foo",
443	},
444	{
445		`{ "foo": [] }`,
446		`[ { "op": "test", "path": "/foo", "value": null } ]`,
447		false,
448		"/foo",
449	},
450	{
451		`{ "baz/foo": "qux" }`,
452		`[ { "op": "test", "path": "/baz~1foo", "value": "qux"} ]`,
453		true,
454		"",
455	},
456	{
457		`{ "foo": [] }`,
458		`[ { "op": "test", "path": "/foo"} ]`,
459		false,
460		"/foo",
461	},
462}
463
464func TestAllTest(t *testing.T) {
465	for _, c := range TestCases {
466		_, err := applyPatch(c.doc, c.patch)
467
468		if c.result && err != nil {
469			t.Errorf("Testing failed when it should have passed: %s", err)
470		} else if !c.result && err == nil {
471			t.Errorf("Testing passed when it should have faild: %s", err)
472		} else if !c.result {
473			expected := fmt.Sprintf("Testing value %s failed", c.failedPath)
474			if err.Error() != expected {
475				t.Errorf("Testing failed as expected but invalid message: expected [%s], got [%s]", expected, err)
476			}
477		}
478	}
479}
480