From dd654b1fb6813eac6a8c011ea21cd07aa168f45b Mon Sep 17 00:00:00 2001 From: ubuntuvm Date: Mon, 6 Dec 2021 15:44:58 +0530 Subject: [PATCH] added a case to support remove patch when remove object is not present in config --- patch.go | 70 ++++++++++++++++++++ patch_test.go | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 244 insertions(+) diff --git a/patch.go b/patch.go index de3173f..90f4cb0 100644 --- a/patch.go +++ b/patch.go @@ -644,6 +644,27 @@ func (p Patch) remove(doc *container, op Operation) error { return nil } +func (p Patch) removeImpl(doc *container, op Operation, allowMissingPathOnRemove bool) error { + path, err := op.Path() + if err != nil { + return errors.Wrapf(ErrMissing, "remove operation failed to decode path") + } + + con, key := findObject(doc, path) + + if con == nil { + return errors.Wrapf(ErrMissing, "remove operation does not apply: doc is missing path: \"%s\"", path) + } + + err = con.remove(key) + // return nil if patch to be removed not found + if err != nil && !allowMissingPathOnRemove { + return errors.Wrapf(err, "error in remove for path: '%s'", path) + } + + return nil +} + func (p Patch) replace(doc *container, op Operation) error { path, err := op.Path() if err != nil { @@ -871,6 +892,55 @@ func (p Patch) ApplyIndent(doc []byte, indent string) ([]byte, error) { return json.Marshal(pd) } +// functionality is same as ApplyIndent in addition it doesn't return error if object is not present on path +func (p Patch) ApplyIndentAllowRemove(doc []byte, indent string, allowMissingPathOnRemove bool) ([]byte, error) { + var pd container + if doc[0] == '[' { + pd = &partialArray{} + } else { + pd = &partialDoc{} + } + + err := json.Unmarshal(doc, pd) + + if err != nil { + return nil, err + } + + err = nil + + var accumulatedCopySize int64 + + for _, op := range p { + switch op.Kind() { + case "add": + err = p.add(&pd, op) + case "remove": + err = p.removeImpl(&pd, op, allowMissingPathOnRemove) + case "replace": + err = p.replace(&pd, op) + case "move": + err = p.move(&pd, op) + case "test": + err = p.test(&pd, op) + case "copy": + err = p.copy(&pd, op, &accumulatedCopySize) + default: + err = fmt.Errorf("Unexpected kind: %s", op.Kind()) + } + + if err != nil { + return nil, err + } + } + + if indent != "" { + return json.MarshalIndent(pd, "", indent) + } + + return json.Marshal(pd) +} + // From http://tools.ietf.org/html/rfc6901#section-4 : // // Evaluation of each reference token begins by decoding any escaped diff --git a/patch_test.go b/patch_test.go index b87e732..e924d50 100644 --- a/patch_test.go +++ b/patch_test.go @@ -43,6 +43,38 @@ func applyPatch(doc, patch string) (string, error) { return string(out), nil } +func applyPatchImpl_allowRemove(doc, patch string) (string, error) { + obj, err := DecodePatch([]byte(patch)) + + if err != nil { + panic(err) + } + + out, err := obj.ApplyIndentAllowRemove([]byte(doc), "", true) + + if err != nil { + return "", err + } + + return string(out), nil +} + +func applyPatchImpl_dontAllowRemove(doc, patch string) (string, error) { + obj, err := DecodePatch([]byte(patch)) + + if err != nil { + panic(err) + } + + out, err := obj.ApplyIndentAllowRemove([]byte(doc), "", false) + + if err != nil { + return "", err + } + + return string(out), nil +} + type Case struct { doc, patch, result string } @@ -380,6 +412,9 @@ var Cases = []Case{ type BadCase struct { doc, patch string } +type BadCases_remove struct { + doc, patch string +} var MutationTestCases = []BadCase{ { @@ -480,6 +515,29 @@ var BadCases = []BadCase{ }, } +var BadCasesRemove = []BadCases_remove{ + { + `{ "a": { "b": { "d": 1 } } }`, + `[ { "op": "remove", "path": "/a/b/c" } ]`, + }, + { + `{ "a": { "b": [1] } }`, + `[ { "op": "remove", "path": "/a/b/1" } ]`, + }, + { + `{ "foo": []}`, + `[ {"op": "remove", "path": "/foo/-"}]`, + }, + { + `{ "foo": []}`, + `[ {"op": "remove", "path": "/foo/-1"}]`, + }, + { + `{ "foo": ["bar"]}`, + `[ {"op": "remove", "path": "/foo/-2"}]`, + }, +} + // This is not thread safe, so we cannot run patch tests in parallel. func configureGlobals(accumulatedCopySizeLimit int64) func() { oldAccumulatedCopySizeLimit := AccumulatedCopySizeLimit @@ -539,6 +597,106 @@ func TestAllCases(t *testing.T) { } } +func TestAllCasesImpl_allowRemove(t *testing.T) { + defer configureGlobals(int64(100))() + for _, c := range ExprCases { + out, err := applyPatchImpl_allowRemove(c.doc, c.patch) + + if err != nil { + t.Errorf("Unable to apply patch: %s", err) + } + + if !compareJSON(out, c.result) { + t.Errorf("Patch did not apply. Expected:\n%s\n\nActual:\n%s\nPatch:%s\n", + reformatJSON(c.result), reformatJSON(out), reformatJSON(c.patch)) + } + } + + for _, c := range Cases { + out, err := applyPatchImpl_allowRemove(c.doc, c.patch) + + if err != nil { + t.Errorf("Unable to apply patch: %s", err) + } + + if !compareJSON(out, c.result) { + t.Errorf("Patch did not apply. Expected:\n%s\n\nActual:\n%s\nPatch:%s\n", + reformatJSON(c.result), reformatJSON(out), reformatJSON(c.patch)) + } + } + + for _, c := range MutationTestCases { + out, err := applyPatchImpl_allowRemove(c.doc, c.patch) + + if err != nil { + t.Errorf("Unable to apply patch: %s", err) + } + + if compareJSON(out, c.doc) { + t.Errorf("Patch did not apply. Original:\n%s\n\nPatched:\n%s", + reformatJSON(c.doc), reformatJSON(out)) + } + } + + for _, c := range BadCasesRemove { + _, err := applyPatchImpl_allowRemove(c.doc, c.patch) + + if err != nil { + t.Errorf("Patch %q should have failed to apply but it did not", c.patch) + } + } +} + +func TestAllCasesImpl_dontAllow(t *testing.T) { + defer configureGlobals(int64(100))() + for _, c := range ExprCases { + out, err := applyPatchImpl_dontAllowRemove(c.doc, c.patch) + + if err != nil { + t.Errorf("Unable to apply patch: %s", err) + } + + if !compareJSON(out, c.result) { + t.Errorf("Patch did not apply. Expected:\n%s\n\nActual:\n%s\nPatch:%s\n", + reformatJSON(c.result), reformatJSON(out), reformatJSON(c.patch)) + } + } + + for _, c := range Cases { + out, err := applyPatchImpl_dontAllowRemove(c.doc, c.patch) + + if err != nil { + t.Errorf("Unable to apply patch: %s", err) + } + + if !compareJSON(out, c.result) { + t.Errorf("Patch did not apply. Expected:\n%s\n\nActual:\n%s\nPatch:%s\n", + reformatJSON(c.result), reformatJSON(out), reformatJSON(c.patch)) + } + } + + for _, c := range MutationTestCases { + out, err := applyPatchImpl_dontAllowRemove(c.doc, c.patch) + + if err != nil { + t.Errorf("Unable to apply patch: %s", err) + } + + if compareJSON(out, c.doc) { + t.Errorf("Patch did not apply. Original:\n%s\n\nPatched:\n%s", + reformatJSON(c.doc), reformatJSON(out)) + } + } + + for _, c := range BadCases { + _, err := applyPatchImpl_dontAllowRemove(c.doc, c.patch) + + if err == nil { + t.Errorf("Patch %q should have failed to apply but it did not", c.patch) + } + } +} + type TestCase struct { doc, patch string result bool @@ -636,3 +794,19 @@ func TestAllTest(t *testing.T) { } } } + +func TestAllTest1(t *testing.T) { + for _, c := range TestCases { + _, err := applyPatchImpl_allowRemove(c.doc, c.patch) + if c.result && err != nil { + t.Errorf("Testing failed when it should have passed: %s", err) + } else if !c.result && err == nil { + t.Errorf("Testing passed when it should have faild: %s", err) + } else if !c.result { + expected := fmt.Sprintf("testing value %s failed: test failed", c.failedPath) + if err.Error() != expected { + t.Errorf("Testing failed as expected but invalid message: expected [%s], got [%s]", expected, err) + } + } + } +}