1 package jsonpatch
2
3 import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "reflect"
8 "testing"
9 )
10
11 func reformatJSON(j string) string {
12 buf := new(bytes.Buffer)
13
14 json.Indent(buf, []byte(j), "", " ")
15
16 return buf.String()
17 }
18
19 func compareJSON(a, b string) bool {
20
21
22 var objA, objB map[string]interface{}
23 json.Unmarshal([]byte(a), &objA)
24 json.Unmarshal([]byte(b), &objB)
25
26
27 return reflect.DeepEqual(objA, objB)
28 }
29
30 func 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
46 type Case struct {
47 doc, patch, result string
48 }
49
50 func repeatedA(r int) string {
51 var s string
52 for i := 0; i < r; i++ {
53 s += "A"
54 }
55 return s
56 }
57
58 var Cases = []Case{
59 {
60 ``,
61 `[
62 { "op": "add", "path": "/baz", "value": "qux" }
63 ]`,
64 ``,
65 },
66 {
67 `{ "foo": "bar"}`,
68 `[
69 { "op": "add", "path": "/baz", "value": "qux" }
70 ]`,
71 `{
72 "baz": "qux",
73 "foo": "bar"
74 }`,
75 },
76 {
77 `{ "foo": [ "bar", "baz" ] }`,
78 `[
79 { "op": "add", "path": "/foo/1", "value": "qux" }
80 ]`,
81 `{ "foo": [ "bar", "qux", "baz" ] }`,
82 },
83 {
84 `{ "foo": [ "bar", "baz" ] }`,
85 `[
86 { "op": "add", "path": "/foo/-1", "value": "qux" }
87 ]`,
88 `{ "foo": [ "bar", "baz", "qux" ] }`,
89 },
90 {
91 `{ "baz": "qux", "foo": "bar" }`,
92 `[ { "op": "remove", "path": "/baz" } ]`,
93 `{ "foo": "bar" }`,
94 },
95 {
96 `{ "foo": [ "bar", "qux", "baz" ] }`,
97 `[ { "op": "remove", "path": "/foo/1" } ]`,
98 `{ "foo": [ "bar", "baz" ] }`,
99 },
100 {
101 `{ "baz": "qux", "foo": "bar" }`,
102 `[ { "op": "replace", "path": "/baz", "value": "boo" } ]`,
103 `{ "baz": "boo", "foo": "bar" }`,
104 },
105 {
106 `{
107 "foo": {
108 "bar": "baz",
109 "waldo": "fred"
110 },
111 "qux": {
112 "corge": "grault"
113 }
114 }`,
115 `[ { "op": "move", "from": "/foo/waldo", "path": "/qux/thud" } ]`,
116 `{
117 "foo": {
118 "bar": "baz"
119 },
120 "qux": {
121 "corge": "grault",
122 "thud": "fred"
123 }
124 }`,
125 },
126 {
127 `{ "foo": [ "all", "grass", "cows", "eat" ] }`,
128 `[ { "op": "move", "from": "/foo/1", "path": "/foo/3" } ]`,
129 `{ "foo": [ "all", "cows", "eat", "grass" ] }`,
130 },
131 {
132 `{ "foo": [ "all", "grass", "cows", "eat" ] }`,
133 `[ { "op": "move", "from": "/foo/1", "path": "/foo/2" } ]`,
134 `{ "foo": [ "all", "cows", "grass", "eat" ] }`,
135 },
136 {
137 `{ "foo": "bar" }`,
138 `[ { "op": "add", "path": "/child", "value": { "grandchild": { } } } ]`,
139 `{ "foo": "bar", "child": { "grandchild": { } } }`,
140 },
141 {
142 `{ "foo": ["bar"] }`,
143 `[ { "op": "add", "path": "/foo/-", "value": ["abc", "def"] } ]`,
144 `{ "foo": ["bar", ["abc", "def"]] }`,
145 },
146 {
147 `{ "foo": "bar", "qux": { "baz": 1, "bar": null } }`,
148 `[ { "op": "remove", "path": "/qux/bar" } ]`,
149 `{ "foo": "bar", "qux": { "baz": 1 } }`,
150 },
151 {
152 `{ "foo": "bar" }`,
153 `[ { "op": "add", "path": "/baz", "value": null } ]`,
154 `{ "baz": null, "foo": "bar" }`,
155 },
156 {
157 `{ "foo": ["bar"]}`,
158 `[ { "op": "replace", "path": "/foo/0", "value": "baz"}]`,
159 `{ "foo": ["baz"]}`,
160 },
161 {
162 `{ "foo": ["bar","baz"]}`,
163 `[ { "op": "replace", "path": "/foo/0", "value": "bum"}]`,
164 `{ "foo": ["bum","baz"]}`,
165 },
166 {
167 `{ "foo": ["bar","qux","baz"]}`,
168 `[ { "op": "replace", "path": "/foo/1", "value": "bum"}]`,
169 `{ "foo": ["bar", "bum","baz"]}`,
170 },
171 {
172 `[ {"foo": ["bar","qux","baz"]}]`,
173 `[ { "op": "replace", "path": "/0/foo/0", "value": "bum"}]`,
174 `[ {"foo": ["bum","qux","baz"]}]`,
175 },
176 {
177 `[ {"foo": ["bar","qux","baz"], "bar": ["qux","baz"]}]`,
178 `[ { "op": "copy", "from": "/0/foo/0", "path": "/0/bar/0"}]`,
179 `[ {"foo": ["bar","qux","baz"], "bar": ["bar", "baz"]}]`,
180 },
181 {
182 `[ {"foo": ["bar","qux","baz"], "bar": ["qux","baz"]}]`,
183 `[ { "op": "copy", "from": "/0/foo/0", "path": "/0/bar"}]`,
184 `[ {"foo": ["bar","qux","baz"], "bar": ["bar", "qux", "baz"]}]`,
185 },
186 {
187 `[ { "foo": {"bar": ["qux","baz"]}, "baz": {"qux": "bum"}}]`,
188 `[ { "op": "copy", "from": "/0/foo/bar", "path": "/0/baz/bar"}]`,
189 `[ { "baz": {"bar": ["qux","baz"], "qux":"bum"}, "foo": {"bar": ["qux","baz"]}}]`,
190 },
191 {
192 `{ "foo": ["bar"]}`,
193 `[{"op": "copy", "path": "/foo/0", "from": "/foo"}]`,
194 `{ "foo": [["bar"], "bar"]}`,
195 },
196 {
197 `{ "foo": ["bar","qux","baz"]}`,
198 `[ { "op": "remove", "path": "/foo/-2"}]`,
199 `{ "foo": ["bar", "baz"]}`,
200 },
201 {
202 `{ "foo": []}`,
203 `[ { "op": "add", "path": "/foo/-1", "value": "qux"}]`,
204 `{ "foo": ["qux"]}`,
205 },
206 {
207 `{ "bar": [{"baz": null}]}`,
208 `[ { "op": "replace", "path": "/bar/0/baz", "value": 1 } ]`,
209 `{ "bar": [{"baz": 1}]}`,
210 },
211 {
212 `{ "bar": [{"baz": 1}]}`,
213 `[ { "op": "replace", "path": "/bar/0/baz", "value": null } ]`,
214 `{ "bar": [{"baz": null}]}`,
215 },
216 {
217 `{ "bar": [null]}`,
218 `[ { "op": "replace", "path": "/bar/0", "value": 1 } ]`,
219 `{ "bar": [1]}`,
220 },
221 {
222 `{ "bar": [1]}`,
223 `[ { "op": "replace", "path": "/bar/0", "value": null } ]`,
224 `{ "bar": [null]}`,
225 },
226 {
227 fmt.Sprintf(`{ "foo": ["A", %q] }`, repeatedA(48)),
228
229
230 `[ { "op": "copy", "path": "/foo/-", "from": "/foo/1" },
231 { "op": "copy", "path": "/foo/-", "from": "/foo/1" }]`,
232 fmt.Sprintf(`{ "foo": ["A", %q, %q, %q] }`, repeatedA(48), repeatedA(48), repeatedA(48)),
233 },
234 {
235 `{
236 "id": "00000000-0000-0000-0000-000000000000",
237 "parentID": "00000000-0000-0000-0000-000000000000"
238 }`,
239 `[
240 {
241 "op": "test",
242 "path": "",
243 "value": {
244 "id": "00000000-0000-0000-0000-000000000000",
245 "parentID": "00000000-0000-0000-0000-000000000000"
246 }
247 },
248 {
249 "op": "replace",
250 "path": "",
251 "value": {
252 "id": "759981e8-ec68-4639-a83e-513225914ecb",
253 "originalID": "bar",
254 "parentID": "00000000-0000-0000-0000-000000000000"
255 }
256 }
257 ]`,
258 `{
259 "id" : "759981e8-ec68-4639-a83e-513225914ecb",
260 "originalID" : "bar",
261 "parentID" : "00000000-0000-0000-0000-000000000000"
262 }`,
263 },
264 }
265
266 type BadCase struct {
267 doc, patch string
268 }
269
270 var MutationTestCases = []BadCase{
271 {
272 `{ "foo": "bar", "qux": { "baz": 1, "bar": null } }`,
273 `[ { "op": "remove", "path": "/qux/bar" } ]`,
274 },
275 {
276 `{ "foo": "bar", "qux": { "baz": 1, "bar": null } }`,
277 `[ { "op": "replace", "path": "/qux/baz", "value": null } ]`,
278 },
279 }
280
281 var BadCases = []BadCase{
282 {
283 `{ "foo": "bar" }`,
284 `[ { "op": "add", "path": "/baz/bat", "value": "qux" } ]`,
285 },
286 {
287 `{ "a": { "b": { "d": 1 } } }`,
288 `[ { "op": "remove", "path": "/a/b/c" } ]`,
289 },
290 {
291 `{ "a": { "b": { "d": 1 } } }`,
292 `[ { "op": "move", "from": "/a/b/c", "path": "/a/b/e" } ]`,
293 },
294 {
295 `{ "a": { "b": [1] } }`,
296 `[ { "op": "remove", "path": "/a/b/1" } ]`,
297 },
298 {
299 `{ "a": { "b": [1] } }`,
300 `[ { "op": "move", "from": "/a/b/1", "path": "/a/b/2" } ]`,
301 },
302 {
303 `{ "foo": "bar" }`,
304 `[ { "op": "add", "pathz": "/baz", "value": "qux" } ]`,
305 },
306 {
307 `{ "foo": "bar" }`,
308 `[ { "op": "add", "path": "", "value": "qux" } ]`,
309 },
310 {
311 `{ "foo": ["bar","baz"]}`,
312 `[ { "op": "replace", "path": "/foo/2", "value": "bum"}]`,
313 },
314 {
315 `{ "foo": ["bar","baz"]}`,
316 `[ { "op": "add", "path": "/foo/-4", "value": "bum"}]`,
317 },
318 {
319 `{ "name":{ "foo": "bat", "qux": "bum"}}`,
320 `[ { "op": "replace", "path": "/foo/bar", "value":"baz"}]`,
321 },
322 {
323 `{ "foo": ["bar"]}`,
324 `[ {"op": "add", "path": "/foo/2", "value": "bum"}]`,
325 },
326 {
327 `{ "foo": []}`,
328 `[ {"op": "remove", "path": "/foo/-"}]`,
329 },
330 {
331 `{ "foo": []}`,
332 `[ {"op": "remove", "path": "/foo/-1"}]`,
333 },
334 {
335 `{ "foo": ["bar"]}`,
336 `[ {"op": "remove", "path": "/foo/-2"}]`,
337 },
338 {
339 `{}`,
340 `[ {"op":null,"path":""} ]`,
341 },
342 {
343 `{}`,
344 `[ {"op":"add","path":null} ]`,
345 },
346 {
347 `{}`,
348 `[ { "op": "copy", "from": null }]`,
349 },
350 {
351 `{ "foo": ["bar"]}`,
352 `[{"op": "copy", "path": "/foo/6666666666", "from": "/"}]`,
353 },
354
355 {
356 `{ "foo": ["bar"]}`,
357 `[{"op": "copy", "path": "/foo/2", "from": "/foo/0"}]`,
358 },
359
360 {
361 fmt.Sprintf(`{ "foo": ["A", %q] }`, repeatedA(49)),
362
363
364 `[ { "op": "copy", "path": "/foo/-", "from": "/foo/1" },
365 { "op": "copy", "path": "/foo/-", "from": "/foo/1" }]`,
366 },
367
368 {
369 `{ "foo": [ "all", "grass", "cows", "eat" ] }`,
370 `[ { "op": "move", "from": "/foo/1", "path": "/foo/4" } ]`,
371 },
372 }
373
374
375 func configureGlobals(accumulatedCopySizeLimit int64) func() {
376 oldAccumulatedCopySizeLimit := AccumulatedCopySizeLimit
377 AccumulatedCopySizeLimit = accumulatedCopySizeLimit
378 return func() {
379 AccumulatedCopySizeLimit = oldAccumulatedCopySizeLimit
380 }
381 }
382
383 func TestAllCases(t *testing.T) {
384 defer configureGlobals(int64(100))()
385 for _, c := range Cases {
386 out, err := applyPatch(c.doc, c.patch)
387
388 if err != nil {
389 t.Errorf("Unable to apply patch: %s", err)
390 }
391
392 if !compareJSON(out, c.result) {
393 t.Errorf("Patch did not apply. Expected:\n%s\n\nActual:\n%s",
394 reformatJSON(c.result), reformatJSON(out))
395 }
396 }
397
398 for _, c := range MutationTestCases {
399 out, err := applyPatch(c.doc, c.patch)
400
401 if err != nil {
402 t.Errorf("Unable to apply patch: %s", err)
403 }
404
405 if compareJSON(out, c.doc) {
406 t.Errorf("Patch did not apply. Original:\n%s\n\nPatched:\n%s",
407 reformatJSON(c.doc), reformatJSON(out))
408 }
409 }
410
411 for _, c := range BadCases {
412 _, err := applyPatch(c.doc, c.patch)
413
414 if err == nil {
415 t.Errorf("Patch %q should have failed to apply but it did not", c.patch)
416 }
417 }
418 }
419
420 type TestCase struct {
421 doc, patch string
422 result bool
423 failedPath string
424 }
425
426 var TestCases = []TestCase{
427 {
428 `{
429 "baz": "qux",
430 "foo": [ "a", 2, "c" ]
431 }`,
432 `[
433 { "op": "test", "path": "/baz", "value": "qux" },
434 { "op": "test", "path": "/foo/1", "value": 2 }
435 ]`,
436 true,
437 "",
438 },
439 {
440 `{ "baz": "qux" }`,
441 `[ { "op": "test", "path": "/baz", "value": "bar" } ]`,
442 false,
443 "/baz",
444 },
445 {
446 `{
447 "baz": "qux",
448 "foo": ["a", 2, "c"]
449 }`,
450 `[
451 { "op": "test", "path": "/baz", "value": "qux" },
452 { "op": "test", "path": "/foo/1", "value": "c" }
453 ]`,
454 false,
455 "/foo/1",
456 },
457 {
458 `{ "baz": "qux" }`,
459 `[ { "op": "test", "path": "/foo", "value": 42 } ]`,
460 false,
461 "/foo",
462 },
463 {
464 `{ "baz": "qux" }`,
465 `[ { "op": "test", "path": "/foo", "value": null } ]`,
466 true,
467 "",
468 },
469 {
470 `{ "foo": null }`,
471 `[ { "op": "test", "path": "/foo", "value": null } ]`,
472 true,
473 "",
474 },
475 {
476 `{ "foo": {} }`,
477 `[ { "op": "test", "path": "/foo", "value": null } ]`,
478 false,
479 "/foo",
480 },
481 {
482 `{ "foo": [] }`,
483 `[ { "op": "test", "path": "/foo", "value": null } ]`,
484 false,
485 "/foo",
486 },
487 {
488 `{ "baz/foo": "qux" }`,
489 `[ { "op": "test", "path": "/baz~1foo", "value": "qux"} ]`,
490 true,
491 "",
492 },
493 {
494 `{ "foo": [] }`,
495 `[ { "op": "test", "path": "/foo"} ]`,
496 false,
497 "/foo",
498 },
499 {
500 `{ "baz": [] }`,
501 `[ { "op": "test", "path": "/foo"} ]`,
502 true,
503 "/foo",
504 },
505 }
506
507 func TestAllTest(t *testing.T) {
508 for _, c := range TestCases {
509 _, err := applyPatch(c.doc, c.patch)
510
511 if c.result && err != nil {
512 t.Errorf("Testing failed when it should have passed: %s", err)
513 } else if !c.result && err == nil {
514 t.Errorf("Testing passed when it should have faild: %s", err)
515 } else if !c.result {
516 expected := fmt.Sprintf("testing value %s failed: test failed", c.failedPath)
517 if err.Error() != expected {
518 t.Errorf("Testing failed as expected but invalid message: expected [%s], got [%s]", expected, err)
519 }
520 }
521 }
522 }
523
524 func TestAdd(t *testing.T) {
525 testCases := []struct {
526 name string
527 key string
528 val lazyNode
529 arr partialArray
530 rejectNegativeIndicies bool
531 err string
532 }{
533 {
534 name: "should work",
535 key: "0",
536 val: lazyNode{},
537 arr: partialArray{},
538 },
539 {
540 name: "index too large",
541 key: "1",
542 val: lazyNode{},
543 arr: partialArray{},
544 err: "Unable to access invalid index: 1: invalid index referenced",
545 },
546 {
547 name: "negative should work",
548 key: "-1",
549 val: lazyNode{},
550 arr: partialArray{},
551 },
552 {
553 name: "negative too small",
554 key: "-2",
555 val: lazyNode{},
556 arr: partialArray{},
557 err: "Unable to access invalid index: -2: invalid index referenced",
558 },
559 {
560 name: "negative but negative disabled",
561 key: "-1",
562 val: lazyNode{},
563 arr: partialArray{},
564 err: "Unable to access invalid index: -1: invalid index referenced",
565
566 rejectNegativeIndicies: true,
567 },
568 }
569 for _, tc := range testCases {
570 t.Run(tc.name, func(t *testing.T) {
571 SupportNegativeIndices = !tc.rejectNegativeIndicies
572 key := tc.key
573 arr := &tc.arr
574 val := &tc.val
575 err := arr.add(key, val)
576 if err == nil && tc.err != "" {
577 t.Errorf("Expected error but got none! %v", tc.err)
578 } else if err != nil && tc.err == "" {
579 t.Errorf("Did not expect error but go: %v", err)
580 } else if err != nil && err.Error() != tc.err {
581 t.Errorf("Expected error %v but got error %v", tc.err, err)
582 }
583 })
584 }
585 }
586
587 type EqualityCase struct {
588 name string
589 a, b string
590 equal bool
591 }
592
593 var EqualityCases = []EqualityCase{
594 {
595 "ExtraKeyFalse",
596 `{"foo": "bar"}`,
597 `{"foo": "bar", "baz": "qux"}`,
598 false,
599 },
600 {
601 "StripWhitespaceTrue",
602 `{
603 "foo": "bar",
604 "baz": "qux"
605 }`,
606 `{"foo": "bar", "baz": "qux"}`,
607 true,
608 },
609 {
610 "KeysOutOfOrderTrue",
611 `{
612 "baz": "qux",
613 "foo": "bar"
614 }`,
615 `{"foo": "bar", "baz": "qux"}`,
616 true,
617 },
618 {
619 "ComparingNullFalse",
620 `{"foo": null}`,
621 `{"foo": "bar"}`,
622 false,
623 },
624 {
625 "ComparingNullTrue",
626 `{"foo": null}`,
627 `{"foo": null}`,
628 true,
629 },
630 {
631 "ArrayOutOfOrderFalse",
632 `["foo", "bar", "baz"]`,
633 `["bar", "baz", "foo"]`,
634 false,
635 },
636 {
637 "ArrayTrue",
638 `["foo", "bar", "baz"]`,
639 `["foo", "bar", "baz"]`,
640 true,
641 },
642 {
643 "NonStringTypesTrue",
644 `{"int": 6, "bool": true, "float": 7.0, "string": "the_string", "null": null}`,
645 `{"int": 6, "bool": true, "float": 7.0, "string": "the_string", "null": null}`,
646 true,
647 },
648 {
649 "NestedNullFalse",
650 `{"foo": ["an", "array"], "bar": {"an": "object"}}`,
651 `{"foo": null, "bar": null}`,
652 false,
653 },
654 {
655 "NullCompareStringFalse",
656 `"foo"`,
657 `null`,
658 false,
659 },
660 {
661 "NullCompareIntFalse",
662 `6`,
663 `null`,
664 false,
665 },
666 {
667 "NullCompareFloatFalse",
668 `6.01`,
669 `null`,
670 false,
671 },
672 {
673 "NullCompareBoolFalse",
674 `false`,
675 `null`,
676 false,
677 },
678 }
679
680 func TestEquality(t *testing.T) {
681 for _, tc := range EqualityCases {
682 t.Run(tc.name, func(t *testing.T) {
683 got := Equal([]byte(tc.a), []byte(tc.b))
684 if got != tc.equal {
685 t.Errorf("Expected Equal(%s, %s) to return %t, but got %t", tc.a, tc.b, tc.equal, got)
686 }
687
688 got = Equal([]byte(tc.b), []byte(tc.a))
689 if got != tc.equal {
690 t.Errorf("Expected Equal(%s, %s) to return %t, but got %t", tc.b, tc.a, tc.equal, got)
691 }
692 })
693 }
694 }
695
View as plain text