blob: e31ee06dd871e7e343e06ede5fff3de253099b5e [file]
/*
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)
})
}
}