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