| /* |
| Copyright 2018 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package typed_test |
| |
| import ( |
| "fmt" |
| "testing" |
| |
| "sigs.k8s.io/structured-merge-diff/v4/fieldpath" |
| "sigs.k8s.io/structured-merge-diff/v4/typed" |
| "sigs.k8s.io/structured-merge-diff/v4/value" |
| ) |
| |
| type removeTestCase struct { |
| name string |
| rootTypeName string |
| schema typed.YAMLObject |
| quadruplets []removeQuadruplet |
| } |
| |
| type removeQuadruplet struct { |
| object typed.YAMLObject |
| set *fieldpath.Set |
| removeOutput typed.YAMLObject |
| extractOutput typed.YAMLObject |
| } |
| |
| var simplePairSchema = `types: |
| - name: stringPair |
| map: |
| fields: |
| - name: key |
| type: |
| scalar: string |
| - name: value |
| type: |
| namedType: __untyped_atomic_ |
| - name: __untyped_atomic_ |
| scalar: untyped |
| list: |
| elementType: |
| namedType: __untyped_atomic_ |
| elementRelationship: atomic |
| map: |
| elementType: |
| namedType: __untyped_atomic_ |
| elementRelationship: atomic |
| ` |
| |
| var structGrabBagSchema = `types: |
| - name: myStruct |
| map: |
| fields: |
| - name: numeric |
| type: |
| scalar: numeric |
| - name: string |
| type: |
| scalar: string |
| - name: bool |
| type: |
| scalar: boolean |
| - name: setStr |
| type: |
| list: |
| elementType: |
| scalar: string |
| elementRelationship: associative |
| - name: setBool |
| type: |
| list: |
| elementType: |
| scalar: boolean |
| elementRelationship: associative |
| - name: setNumeric |
| type: |
| list: |
| elementType: |
| scalar: numeric |
| elementRelationship: associative |
| ` |
| |
| var associativeAndAtomicSchema = `types: |
| - name: myRoot |
| map: |
| fields: |
| - name: list |
| type: |
| namedType: myList |
| - name: atomicList |
| type: |
| namedType: mySequence |
| - name: atomicMap |
| type: |
| namedType: myAtomicMap |
| - name: myList |
| list: |
| elementType: |
| namedType: myElement |
| elementRelationship: associative |
| keys: |
| - key |
| - id |
| - name: myAtomicMap |
| map: |
| elementType: |
| scalar: string |
| elementRelationship: atomic |
| - name: mySequence |
| list: |
| elementType: |
| scalar: string |
| elementRelationship: atomic |
| - name: myElement |
| map: |
| fields: |
| - name: key |
| type: |
| scalar: string |
| - name: id |
| type: |
| scalar: numeric |
| - name: value |
| type: |
| namedType: myValue |
| - name: bv |
| type: |
| scalar: boolean |
| - name: nv |
| type: |
| scalar: numeric |
| - name: myValue |
| map: |
| elementType: |
| scalar: string |
| ` |
| var atomicTypesSchema = `types: |
| - name: myRoot |
| map: |
| fields: |
| - name: atomicMap |
| type: |
| namedType: myAtomicMap |
| - name: atomicList |
| type: |
| namedType: mySequence |
| - name: myAtomicMap |
| map: |
| elementType: |
| scalar: string |
| elementRelationship: atomic |
| - name: mySequence |
| list: |
| elementType: |
| scalar: string |
| elementRelationship: atomic |
| ` |
| |
| var nestedTypesSchema = `types: |
| - name: type |
| map: |
| fields: |
| - name: listOfLists |
| type: |
| namedType: listOfLists |
| - name: listOfMaps |
| type: |
| namedType: listOfMaps |
| - name: mapOfLists |
| type: |
| namedType: mapOfLists |
| - name: mapOfMaps |
| type: |
| namedType: mapOfMaps |
| - name: mapOfMapsRecursive |
| type: |
| namedType: mapOfMapsRecursive |
| - name: struct |
| type: |
| namedType: struct |
| - name: struct |
| map: |
| fields: |
| - name: name |
| type: |
| scalar: string |
| - name: value |
| type: |
| scalar: number |
| - name: listOfLists |
| list: |
| elementType: |
| map: |
| fields: |
| - name: name |
| type: |
| scalar: string |
| - name: value |
| type: |
| namedType: list |
| elementRelationship: associative |
| keys: |
| - name |
| - name: list |
| list: |
| elementType: |
| scalar: string |
| elementRelationship: associative |
| - name: listOfMaps |
| list: |
| elementType: |
| map: |
| fields: |
| - name: name |
| type: |
| scalar: string |
| - name: value |
| type: |
| namedType: map |
| elementRelationship: associative |
| keys: |
| - name |
| - name: map |
| map: |
| elementType: |
| scalar: string |
| elementRelationship: associative |
| - name: mapOfLists |
| map: |
| elementType: |
| namedType: list |
| elementRelationship: associative |
| - name: mapOfMaps |
| map: |
| elementType: |
| namedType: map |
| elementRelationship: associative |
| - name: mapOfMapsRecursive |
| map: |
| elementType: |
| namedType: mapOfMapsRecursive |
| elementRelationship: associative |
| ` |
| |
| var removeCases = []removeTestCase{{ |
| name: "simple pair", |
| rootTypeName: "stringPair", |
| schema: typed.YAMLObject(simplePairSchema), |
| quadruplets: []removeQuadruplet{{ |
| `{"key":"foo"}`, |
| _NS(_P("key")), |
| ``, |
| `{"key":"foo"}`, |
| }, { |
| `{"key":"foo"}`, |
| _NS(), |
| `{"key":"foo"}`, |
| ``, |
| }, { |
| `{"key":"foo","value":true}`, |
| _NS(_P("key")), |
| `{"value":true}`, |
| `{"key":"foo"}`, |
| }, { |
| `{"key":"foo","value":{"a": "b"}}`, |
| _NS(_P("value")), |
| `{"key":"foo"}`, |
| `{"value":{"a": "b"}}`, |
| }}, |
| }, { |
| name: "struct grab bag", |
| rootTypeName: "myStruct", |
| schema: typed.YAMLObject(structGrabBagSchema), |
| quadruplets: []removeQuadruplet{{ |
| `{"setBool":[false]}`, |
| _NS(_P("setBool", _V(false))), |
| `{"setBool":null}`, |
| `{"setBool":[false]}`, |
| }, { |
| `{"setBool":[false]}`, |
| _NS(_P("setBool", _V(true))), |
| `{"setBool":[false]}`, |
| `{"setBool":null}`, |
| }, { |
| `{"setBool":[true,false]}`, |
| _NS(_P("setBool", _V(true))), |
| `{"setBool":[false]}`, |
| `{"setBool":[true]}`, |
| }, { |
| `{"setBool":[true,false]}`, |
| _NS(_P("setBool")), |
| ``, |
| `{"setBool":null}`, |
| }, { |
| `{"setNumeric":[1,2,3,4.5]}`, |
| _NS(_P("setNumeric", _V(1)), _P("setNumeric", _V(4.5))), |
| `{"setNumeric":[2,3]}`, |
| `{"setNumeric":[1,4.5]}`, |
| }, { |
| `{"setStr":["a","b","c"]}`, |
| _NS(_P("setStr", _V("a"))), |
| `{"setStr":["b","c"]}`, |
| `{"setStr":["a"]}`, |
| }}, |
| }, { |
| name: "associative and atomic", |
| rootTypeName: "myRoot", |
| schema: typed.YAMLObject(associativeAndAtomicSchema), |
| quadruplets: []removeQuadruplet{{ |
| // extract a struct from an associative list |
| `{"list":[{"key":"a","id":1},{"key":"a","id":2},{"key":"b","id":1}]}`, |
| _NS( |
| _P("list", _KBF("key", "a", "id", 1), "key"), |
| _P("list", _KBF("key", "a", "id", 1), "id"), |
| ), |
| `unparseable`, |
| `{"list":[{"key":"a","id":1}]}`, |
| }, { |
| // remove structs from an associative list |
| `{"list":[{"key":"a","id":1},{"key":"a","id":2},{"key":"b","id":1}]}`, |
| _NS( |
| _P("list", _KBF("key", "a", "id", 1)), |
| ), |
| `{"list":[{"key":"a","id":2},{"key":"b","id":1}]}`, |
| `unparseable`, |
| }, { |
| `{"atomicList":["a", "a", "a"]}`, |
| _NS(_P("atomicList")), |
| ``, |
| // atomic lists should still return everything in the list |
| `{"atomicList":["a", "a", "a"]}`, |
| }, { |
| `{"atomicMap":{"a": "c", "b": "d"}}`, |
| _NS(_P("atomicMap")), |
| ``, |
| // atomic maps should still return everything in the map |
| `{"atomicMap":{"a": "c", "b": "d"}}`, |
| }}, |
| }, { |
| name: "nested types", |
| rootTypeName: "type", |
| schema: typed.YAMLObject(nestedTypesSchema), |
| quadruplets: []removeQuadruplet{{ |
| // extract everything |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| _NS( |
| _P("listOfLists", _KBF("name", "a"), "name"), |
| _P("listOfLists", _KBF("name", "a"), "value", _V("b")), |
| _P("listOfLists", _KBF("name", "a"), "value", _V("c")), |
| _P("listOfLists", _KBF("name", "d"), "name"), |
| ), |
| `unparseable`, |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| }, { |
| // path to root type |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| _NS( |
| _P("listOfLists"), |
| ), |
| ``, |
| `{"listOfLists": null}`, |
| }, { |
| // path to a top-level element (extract) |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| _NS(_P("listOfLists", _KBF("name", "d"), "name")), |
| //`{"listOfLists": [{"name": "a", "value": ["b", "c"]}, null]}`, |
| `unparseable`, |
| `{"listOfLists": [{"name": "d"}]}`, |
| }, { |
| // path to a top-level element (remove) |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| _NS(_P("listOfLists", _KBF("name", "d"))), |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}]}`, |
| `unparseable`, |
| }, { |
| // same as previous with the other top-level element containing nested elements. (extract) |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| _NS( |
| _P("listOfLists", _KBF("name", "a"), "name"), |
| ), |
| `unparseable`, |
| `{"listOfLists": [{"name": "a"}]}`, |
| }, { |
| // same as previous with the other top-level element containing nested elements. (remove) |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| _NS( |
| _P("listOfLists", _KBF("name", "a")), |
| ), |
| `{"listOfLists": [{"name": "d"}]}`, |
| `unparseable`, |
| }, { |
| // just one path to leaf element |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| _NS( |
| _P("listOfLists", _KBF("name", "a"), "value", _V("b")), |
| ), |
| `{"listOfLists": [{"name":"a", "value": ["c"]}, {"name": "d"}]}`, |
| `unparseable`, // cannot extract leaf element without path to top-level element as well |
| }, { |
| // paths to leaf and top level element |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| _NS( |
| _P("listOfLists", _KBF("name", "a"), "name"), |
| _P("listOfLists", _KBF("name", "a"), "value", _V("b")), |
| ), |
| `unparseable`, // cannot remove a top-level list and a single element from the list within |
| `{"listOfLists": [{"name": "a", "value": ["b"]}]}`, |
| }, { |
| // path to non-existant top-level element |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| _NS( |
| _P("listOfLists", _KBF("name", "x")), |
| ), |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, // doesn't remove anything |
| `{"listOfLists":null}`, // extract only the root type |
| }, { |
| // path with existant top-level but non-existant leaf element |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| _NS( |
| _P("listOfLists", _KBF("name", "a"), "value", _V("x")), |
| ), |
| `{"listOfLists": [{"name":"a", "value": ["b","c"]}, {"name": "d"}]}`, // nothing removed since the path doesn't exist. |
| `unparseable`, //`{"listOfLists":[{"value":null}]}`, // unparseable because name cannot be missing |
| }, { |
| // paths with existant top-level but non-existant leaf element |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| _NS( |
| _P("listOfLists", _KBF("name", "a"), "name"), |
| _P("listOfLists", _KBF("name", "a"), "value", _V("x")), |
| ), |
| `unparseable`, // unparseable because remove cannot operate on a top-level element and a leaf within |
| `unparseable`, //`{"listOfLists":[{"name: "a","value": null}]}`, // unparseable because value (list type) cannot be null |
| }, { |
| // invalid path to just a leaf |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| _NS( |
| _P(_V("b")), |
| ), |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| ``, |
| }, { |
| // extract everything |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, |
| _NS( |
| _P("listOfMaps", _KBF("name", "a"), "name"), |
| _P("listOfMaps", _KBF("name", "a"), "value", "b"), |
| _P("listOfMaps", _KBF("name", "a"), "value", "c"), |
| _P("listOfMaps", _KBF("name", "d"), "name"), |
| _P("listOfMaps", _KBF("name", "d"), "value", "e"), |
| ), |
| `unparseable`, |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, |
| }, { |
| // path to root type |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, |
| _NS( |
| _P("listOfMaps"), |
| ), |
| ``, |
| `{"listOfMaps"}`, |
| }, { |
| // path to a top-level element (extract) |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, |
| _NS( |
| _P("listOfMaps", _KBF("name", "a"), "name"), |
| ), |
| `unparseable`, |
| `{"listOfMaps": [{"name": "a"}]}`, |
| }, { |
| // path to a top-level element (remove) |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, |
| _NS( |
| _P("listOfMaps", _KBF("name", "a")), |
| ), |
| `{"listOfMaps": [{"name": "d", "value": {"e":"z"}}]}`, |
| `unparseable`, |
| }, { |
| // just one path to leaf element |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, |
| _NS( |
| _P("listOfMaps", _KBF("name", "a"), "value", "b"), |
| ), |
| `{"listOfMaps": [{"name": "a", "value": {"c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, |
| `unparseable`, // cannot extract leaf element without path to top-level element as well |
| }, { |
| // paths to leaf and top level element |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, |
| _NS( |
| _P("listOfMaps", _KBF("name", "a"), "name"), |
| _P("listOfMaps", _KBF("name", "a"), "value", "b"), |
| ), |
| `unparseable`, // cannot remove a top-lvel list and a single element from the list within |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x"}}]}`, |
| }, { |
| // path to non-existant top-level element |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, |
| _NS( |
| _P("listOfMaps", _KBF("name", "q"), "name"), |
| ), |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, // doesn't remove anything |
| `{"listOfMaps":null}`, // extract only the root type |
| }, { |
| // path with existant top-level but non-existant leaf element. |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, |
| _NS( |
| _P("listOfMaps", _KBF("name", "a"), "value", "q"), |
| ), |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, // doesn't remove anything |
| `unparseable`, //`{"listOfMaps": [{"value": null}]}`, // unparseable because name cannot be missing |
| }, { |
| // paths with existant top-level but non-existant leaf element |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, |
| _NS( |
| _P("listOfMaps", _KBF("name", "a"), "name"), |
| _P("listOfMaps", _KBF("name", "a"), "value", "q"), |
| ), |
| `unparseable`, // unparseable because remove cannot operate on a top-level element and a leaf within |
| `{"listOfMaps": [{"name":"a", "value": null}]}`, // parseable because value (map type) CAN be null |
| }, { |
| // extract everything |
| `{"mapOfLists": {"b":["a","c"], "d":["e", "f"]}}`, |
| _NS( |
| _P("mapOfLists", "b", _V("a")), |
| _P("mapOfLists", "b", _V("c")), |
| _P("mapOfLists", "d", _V("e")), |
| _P("mapOfLists", "d", _V("f")), |
| ), |
| `unparseable`, |
| `{"mapOfLists": {"b":["a","c"], "d":["e", "f"]}}`, |
| }, { |
| // path to root type |
| `{"mapOfLists": {"b":["a","c"], "d":["e", "f"]}}`, |
| _NS( |
| _P("mapOfLists"), |
| ), |
| ``, |
| `{"mapOfLists":null}`, |
| }, { |
| // path to a top-level element |
| `{"mapOfLists": {"b":["a","c"], "d":["e", "f"]}}`, |
| _NS( |
| _P("mapOfLists", "b"), |
| ), |
| `{"mapOfLists": {"d":["e", "f"]}}`, |
| `{"mapOfLists": {"b":null}}`, |
| }, { |
| // just one path to leaf element |
| `{"mapOfLists": {"b":["a","c"], "d":["e", "f"]}}`, |
| _NS( |
| _P("mapOfLists", "b", _V("a")), |
| ), |
| `{"mapOfLists":{"b":["c"],"d":["e", "f"]}}`, |
| `{"mapOfLists":{"b":["a"]}}`, |
| }, { |
| // path to non-existant top-level element |
| `{"mapOfLists": {"b":["a","c"], "d":["e", "f"]}}`, |
| _NS( |
| _P("mapOfLists", "q"), |
| ), |
| `{"mapOfLists": {"b":["a","c"], "d":["e", "f"]}}`, |
| `{"mapOfLists":null}`, |
| }, { |
| // path with existant top-level but non-existant leaf element |
| `{"mapOfLists": {"b":["a","c"], "d":["e", "f"]}}`, |
| _NS( |
| _P("mapOfLists", "b", _V("q")), |
| ), |
| `{"mapOfLists": {"b":["a","c"], "d":["e", "f"]}}`, |
| `{"mapOfLists":{"b":null}}`, |
| }, { |
| // path with existant top-level but non-existant leaf element |
| `{"mapOfLists": {"b":null, "d":["e", "f"]}}`, |
| _NS( |
| _P("mapOfLists", "b"), |
| ), |
| `{"mapOfLists": {"d":["e", "f"]}}`, |
| `{"mapOfLists":{"b":null}}`, // same output as previous case, but can be differentiated by input fieldpath.Set |
| }, { |
| // invalid path to just a leaf |
| `{"mapOfLists": {"b":["a","c"], "d":["e", "f"]}}`, |
| _NS( |
| _P(_V("a")), |
| ), |
| `{"mapOfLists": {"b":["a","c"], "d":["e", "f"]}}`, |
| ``, |
| }, { |
| // extract everything |
| `{"mapOfMaps": {"b":{"a":"x","c":"z"}, "d":{"e":"y", "f":"w"}}}`, |
| _NS( |
| _P("mapOfMaps", "b", "a"), |
| _P("mapOfMaps", "b", "c"), |
| _P("mapOfMaps", "d", "e"), |
| _P("mapOfMaps", "d", "f"), |
| ), |
| `unparseable`, |
| `{"mapOfMaps": {"b":{"a":"x","c":"z"}, "d":{"e":"y", "f":"w"}}}`, |
| }, { |
| // path to root type |
| `{"mapOfMaps": {"b":{"a":"x","c":"z"}, "d":{"e":"y", "f":"w"}}}`, |
| _NS( |
| _P("mapOfMaps"), |
| ), |
| ``, |
| `{"mapOfMaps":null}`, |
| }, { |
| // path to a top-level element |
| `{"mapOfMaps": {"b":{"a":"x","c":"z"}, "d":{"e":"y", "f":"w"}}}`, |
| _NS( |
| _P("mapOfMaps", "b"), |
| ), |
| `{"mapOfMaps": {"d":{"e":"y", "f":"w"}}}`, |
| `{"mapOfMaps": {"b":null}}`, |
| }, { |
| // just one path to leaf element |
| `{"mapOfMaps": {"b":{"a":"x","c":"z"}, "d":{"e":"y", "f":"w"}}}`, |
| _NS( |
| _P("mapOfMaps", "b", "a"), |
| ), |
| `{"mapOfMaps": {"b":{"c":"z"},"d":{"e":"y", "f":"w"}}}`, |
| `{"mapOfMaps": {"b":{"a":"x"}}}`, |
| }, { |
| // path to non-existant top-level element |
| `{"mapOfMaps": {"b":{"a":"x","c":"z"}, "d":{"e":"y", "f":"w"}}}`, |
| _NS( |
| _P("mapOfMaps", "q"), |
| ), |
| `{"mapOfMaps": {"b":{"a":"x","c":"z"}, "d":{"e":"y", "f":"w"}}}`, |
| `{"mapOfMaps": null}`, |
| }, { |
| // path with existant top-level but non-existant leaf element |
| `{"mapOfMaps": {"b":{"a":"x","c":"z"}, "d":{"e":"y", "f":"w"}}}`, |
| _NS( |
| _P("mapOfMaps", "b", "q"), |
| ), |
| `{"mapOfMaps": {"b":{"a":"x","c":"z"}, "d":{"e":"y", "f":"w"}}}`, |
| `{"mapOfMaps": {"b":null}}`, |
| }, { |
| // top-level element with null leaf elements |
| `{"mapOfMaps": {"b":null, "d":{"e":"y", "f":"w"}}}`, |
| _NS( |
| _P("mapOfMaps", "b"), |
| ), |
| `{"mapOfMaps": {"d":{"e":"y", "f":"w"}}}`, |
| `{"mapOfMaps": {"b":null}}`, // same output as previous case, but can be differentiated by input fieldpath.Set |
| }, { |
| // invalid path to just a leaf |
| `{"mapOfMaps": {"b":{"a":"x","c":"z"}, "d":{"e":"y", "f":"w"}}}`, |
| _NS( |
| _P("a"), |
| ), |
| `{"mapOfMaps": {"b":{"a":"x","c":"z"}, "d":{"e":"y", "f":"w"}}}`, |
| ``, |
| }, { |
| // root element |
| `{"mapOfMapsRecursive": {"a":{"b":{"c":null}}}}`, |
| _NS( |
| _P("mapOfMapsRecursive"), |
| ), |
| ``, |
| `{"mapOfMapsRecursive":null}`, |
| }, { |
| // top-level map |
| `{"mapOfMapsRecursive": {"a":{"b":{"c":null}}}}`, |
| _NS( |
| _P("mapOfMapsRecursive", "a"), |
| ), |
| `{"mapOfMapsRecursive"}`, |
| `{"mapOfMapsRecursive": {"a":null}}`, |
| }, { |
| // second-level map |
| `{"mapOfMapsRecursive": {"a":{"b":{"c":null}}}}`, |
| _NS( |
| _P("mapOfMapsRecursive", "a", "b"), |
| ), |
| `{"mapOfMapsRecursive":{"a":null}}`, |
| `{"mapOfMapsRecursive": {"a":{"b":null}}}`, |
| }, { |
| // third-level map |
| `{"mapOfMapsRecursive": {"a":{"b":{"c":null}}}}`, |
| _NS( |
| _P("mapOfMapsRecursive", "a", "b", "c"), |
| ), |
| `{"mapOfMapsRecursive":{"a":{"b":null}}}`, |
| `{"mapOfMapsRecursive": {"a":{"b":{"c":null}}}}`, |
| }}, |
| }} |
| |
| func (tt removeTestCase) test(t *testing.T) { |
| parser, err := typed.NewParser(tt.schema) |
| if err != nil { |
| t.Fatalf("failed to create schema: %v", err) |
| } |
| pt := parser.Type(tt.rootTypeName) |
| |
| for i, quadruplet := range tt.quadruplets { |
| quadruplet := quadruplet |
| t.Run(fmt.Sprintf("%v-valid-%v", tt.name, i), func(t *testing.T) { |
| t.Parallel() |
| |
| tv, err := pt.FromYAML(quadruplet.object) |
| if err != nil { |
| t.Fatalf("unable to parser/validate object yaml: %v\n%v", err, quadruplet.object) |
| } |
| |
| // test RemoveItems |
| if quadruplet.removeOutput != "unparseable" { |
| rmOut, err := pt.FromYAML(quadruplet.removeOutput) |
| if err != nil { |
| t.Fatalf("unable to parser/validate removeOutput yaml: %v\n%v", err, quadruplet.removeOutput) |
| } |
| |
| rmGot := tv.RemoveItems(quadruplet.set) |
| if !value.Equals(rmGot.AsValue(), rmOut.AsValue()) { |
| t.Errorf("RemoveItems expected\n%v\nbut got\n%v\n", |
| value.ToString(rmOut.AsValue()), value.ToString(rmGot.AsValue()), |
| ) |
| } |
| } |
| |
| // test ExtractItems |
| if quadruplet.extractOutput != "unparseable" { |
| exOut, err := pt.FromYAML(quadruplet.extractOutput) |
| if err != nil { |
| t.Fatalf("unable to parser/validate extractOutput yaml: %v\n%v", err, quadruplet.extractOutput) |
| } |
| exGot := tv.ExtractItems(quadruplet.set) |
| if !value.Equals(exGot.AsValue(), exOut.AsValue()) { |
| t.Errorf("ExtractItems expected\n%v\nbut got\n%v\n", |
| value.ToString(exOut.AsValue()), value.ToString(exGot.AsValue()), |
| ) |
| } |
| |
| } |
| }) |
| } |
| } |
| |
| func TestRemove(t *testing.T) { |
| for _, tt := range removeCases { |
| tt := tt |
| t.Run(tt.name, func(t *testing.T) { |
| t.Parallel() |
| tt.test(t) |
| }) |
| } |
| } |
| |
| type reversibleExtractTestCase struct { |
| name string |
| rootTypeName string |
| schema typed.YAMLObject |
| pairs []reversibleExtractPair |
| } |
| |
| type reversibleExtractPair struct { |
| object typed.YAMLObject |
| pso typed.YAMLObject |
| } |
| |
| var reversibleExtractCases = []reversibleExtractTestCase{{ |
| name: "nested types", |
| rootTypeName: "type", |
| schema: typed.YAMLObject(nestedTypesSchema), |
| pairs: []reversibleExtractPair{{ |
| // add to top level element |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| `{"listOfLists": [{"name": "f", "value": ["j", "k"]},]}`, |
| }, { |
| // add to leaf element |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| `{"listOfLists": [{"name": "a", "value": ["j", "k"]},]}`, |
| }, { |
| // apply empty structure |
| `{"listOfLists": [{"name": "a", "value": ["b", "c"]}, {"name": "d"}]}`, |
| `{"listOfLists": [{"name": "a", "value": null},]}`, |
| }, { |
| // add to top level element |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, |
| `{"listOfMaps": [{"name": "f", "value": {"q":"p"}}]}`, |
| }, { |
| // add to leaf element |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, |
| `{"listOfMaps": [{"name": "a", "value": {"f":"p"}}]}`, |
| }, { |
| // replace leaf element |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, |
| `{"listOfMaps": [{"name": "a", "value": {"b":"p"}}]}`, |
| }, { |
| // apply empty structure |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, |
| `{"listOfMaps": [{"name": "a", "value": null}]}`, |
| }, { |
| // add to top level element |
| `{"mapOfLists": {"b":["a","c"], "d":["e", "f"]}}`, |
| `{"mapOfLists": {"x":["y","z"]}}`, |
| }, { |
| // add to leaf element |
| `{"mapOfLists": {"b":["a","c"], "d":["e", "f"]}}`, |
| `{"mapOfLists": {"b":["y","z"]}}`, |
| }, { |
| // apply empty structure |
| `{"mapOfLists": {"b":["a","c"], "d":["e", "f"]}}`, |
| `{"mapOfLists": {"b":null}}`, |
| }, { |
| // add to top level element |
| `{"mapOfMaps": {"b":{"a":"x","c":"z"}, "d":{"e":"y", "f":"w"}}}`, |
| `{"mapOfMaps": {"i":{"j":"k"}}}`, |
| }, { |
| // add to leaf element |
| `{"mapOfMaps": {"b":{"a":"x","c":"z"}, "d":{"e":"y", "f":"w"}}}`, |
| `{"mapOfMaps": {"b":{"j":"k"}}}`, |
| }, { |
| // replace leaf element |
| `{"mapOfMaps": {"b":{"a":"x","c":"z"}, "d":{"e":"y", "f":"w"}}}`, |
| `{"mapOfMaps": {"b":{"a":"k"}}}`, |
| }, { |
| // apply empty structure |
| `{"mapOfMaps": {"b":{"a":"x","c":"z"}, "d":{"e":"y", "f":"w"}}}`, |
| `{"mapOfMaps": {"b": null}}`, |
| }, { |
| // misc: add another root type |
| `{"listOfMaps": [{"name": "a", "value": {"b":"x", "c":"y"}}, {"name": "d", "value": {"e":"z"}}]}`, |
| `{"mapOfLists": {"b":["y","z"]}}`, |
| }, { |
| // misc: recursive deeply nested leaves |
| `{"mapOfMapsRecursive": {"a":{"b":{"c":null}, "d":{"e":{"f":null}, "g":null}}}}`, |
| `{"mapOfMapsRecursive": {"a":{"d":{"e":{"f":{"q":null}, "p":null}}}}}`, |
| }, { |
| // misc: recursive deeply nested empty structure |
| `{"mapOfMapsRecursive": {"a":{"b":{"c":{"d":{"e":{"f":null}}, "g":{"h":null}, "i":null}}}}}`, |
| `{"mapOfMapsRecursive": {"a":{"b":{"c":null}}}}`, |
| }}, |
| }} |
| |
| func (tt reversibleExtractTestCase) test(t *testing.T) { |
| parser, err := typed.NewParser(tt.schema) |
| if err != nil { |
| t.Fatalf("failed to create schema: %v", err) |
| } |
| pt := parser.Type(tt.rootTypeName) |
| |
| for i, pair := range tt.pairs { |
| pair := pair |
| t.Run(fmt.Sprintf("%v-valid-%v", tt.name, i), func(t *testing.T) { |
| t.Parallel() |
| // Generate initial typed obj |
| initialObj, err := pt.FromYAML(pair.object) |
| if err != nil { |
| t.Fatalf("unable to parser/validate initial object yaml: %v\n%v", err, pair.object) |
| } |
| // Generate PSO |
| pso, err := pt.FromYAML(pair.pso) |
| if err != nil { |
| t.Fatalf("unable to parser/validate PSO yaml: %v\n%v", err, pair.pso) |
| } |
| // Merge PSO with base object |
| mergedObj, err := initialObj.Merge(pso) |
| if err != nil { |
| t.Fatalf("unable to merge PSO into initial object: %v\n", err) |
| } |
| // convert PSO to fieldset |
| fieldSet, err := pso.ToFieldSet() |
| if err != nil { |
| t.Fatalf("unable to convert pso to fieldset: %v\n%v", err, pso) |
| } |
| // trying to extract the fieldSet directly will return everything |
| // under the first path in the set, so we must filter out all |
| // the non-leaf nodes from the fieldSet |
| extractSet := fieldSet.Leaves() |
| // extract PSO fieldset from result object |
| extracted := mergedObj.ExtractItems(extractSet) |
| // confirm extract object is initial PSO |
| if !value.Equals(pso.AsValue(), extracted.AsValue()) { |
| t.Errorf("ExtractItems not reversible expected\n%v\nbut got\n%v\n", |
| value.ToString(pso.AsValue()), value.ToString(extracted.AsValue()), |
| ) |
| } |
| }) |
| } |
| } |
| |
| // TestReversibleExtract ensures that when you apply a |
| // partially specified object (PSO) to an existing object |
| // and then Extract the fieldset from the resulting object |
| // you receive back the initial partially specified object. |
| func TestReversibleExtract(t *testing.T) { |
| for _, tt := range reversibleExtractCases { |
| tt := tt |
| t.Run(tt.name, func(t *testing.T) { |
| t.Parallel() |
| tt.test(t) |
| }) |
| } |
| } |