1/*
2Copyright 2018 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package typed_test
18
19import (
20	"fmt"
21	"testing"
22
23	"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
24	"sigs.k8s.io/structured-merge-diff/v3/typed"
25	"sigs.k8s.io/structured-merge-diff/v3/value"
26)
27
28type objSetPair struct {
29	object typed.YAMLObject
30	set    *fieldpath.Set
31}
32
33type fieldsetTestCase struct {
34	name         string
35	rootTypeName string
36	schema       typed.YAMLObject
37	pairs        []objSetPair
38}
39
40var (
41	// Short names for readable test cases.
42	_NS  = fieldpath.NewSet
43	_P   = fieldpath.MakePathOrDie
44	_KBF = fieldpath.KeyByFields
45	_V   = value.NewValueInterface
46)
47
48var fieldsetCases = []fieldsetTestCase{{
49	name:         "simple pair",
50	rootTypeName: "stringPair",
51	schema: `types:
52- name: stringPair
53  map:
54    fields:
55    - name: key
56      type:
57        scalar: string
58    - name: value
59      type:
60        namedType: __untyped_atomic_
61- name: __untyped_atomic_
62  scalar: untyped
63  list:
64    elementType:
65      namedType: __untyped_atomic_
66    elementRelationship: atomic
67  map:
68    elementType:
69      namedType: __untyped_atomic_
70    elementRelationship: atomic
71`,
72	pairs: []objSetPair{
73		{`{"key":"foo","value":1}`, _NS(_P("key"), _P("value"))},
74		{`{"key":"foo","value":{"a": "b"}}`, _NS(_P("key"), _P("value"))},
75		{`{"key":"foo","value":null}`, _NS(_P("key"), _P("value"))},
76		{`{"key":"foo"}`, _NS(_P("key"))},
77		{`{"key":"foo","value":true}`, _NS(_P("key"), _P("value"))},
78	},
79}, {
80	name:         "struct grab bag",
81	rootTypeName: "myStruct",
82	schema: `types:
83- name: myStruct
84  map:
85    fields:
86    - name: numeric
87      type:
88        scalar: numeric
89    - name: string
90      type:
91        scalar: string
92    - name: bool
93      type:
94        scalar: boolean
95    - name: setStr
96      type:
97        list:
98          elementType:
99            scalar: string
100          elementRelationship: associative
101    - name: setBool
102      type:
103        list:
104          elementType:
105            scalar: boolean
106          elementRelationship: associative
107    - name: setNumeric
108      type:
109        list:
110          elementType:
111            scalar: numeric
112          elementRelationship: associative
113    - name: color
114      type:
115        map:
116          fields:
117          - name: R
118            type:
119              scalar: numeric
120          - name: G
121            type:
122              scalar: numeric
123          - name: B
124            type:
125              scalar: numeric
126          elementRelationship: atomic
127    - name: arbitraryWavelengthColor
128      type:
129        map:
130          elementType:
131            scalar: numeric
132          elementRelationship: atomic
133    - name: args
134      type:
135        list:
136          elementType:
137            map:
138              fields:
139              - name: key
140                type:
141                  scalar: string
142              - name: value
143                type:
144                  scalar: string
145          elementRelationship: atomic
146`,
147	pairs: []objSetPair{
148		{`{"numeric":1}`, _NS(_P("numeric"))},
149		{`{"numeric":3.14159}`, _NS(_P("numeric"))},
150		{`{"string":"aoeu"}`, _NS(_P("string"))},
151		{`{"bool":true}`, _NS(_P("bool"))},
152		{`{"bool":false}`, _NS(_P("bool"))},
153		{`{"setStr":["a","b","c"]}`, _NS(
154			_P("setStr", _V("a")),
155			_P("setStr", _V("b")),
156			_P("setStr", _V("c")),
157		)},
158		{`{"setBool":[true,false]}`, _NS(
159			_P("setBool", _V(true)),
160			_P("setBool", _V(false)),
161		)},
162		{`{"setNumeric":[1,2,3,3.14159]}`, _NS(
163			_P("setNumeric", _V(1)),
164			_P("setNumeric", _V(2)),
165			_P("setNumeric", _V(3)),
166			_P("setNumeric", _V(3.14159)),
167		)},
168		{`{"color":{}}`, _NS(_P("color"))},
169		{`{"color":null}`, _NS(_P("color"))},
170		{`{"color":{"R":255,"G":0,"B":0}}`, _NS(_P("color"))},
171		{`{"arbitraryWavelengthColor":{}}`, _NS(_P("arbitraryWavelengthColor"))},
172		{`{"arbitraryWavelengthColor":null}`, _NS(_P("arbitraryWavelengthColor"))},
173		{`{"arbitraryWavelengthColor":{"IR":255}}`, _NS(_P("arbitraryWavelengthColor"))},
174		{`{"args":[]}`, _NS(_P("args"))},
175		{`{"args":null}`, _NS(_P("args"))},
176		{`{"args":[null]}`, _NS(_P("args"))},
177		{`{"args":[{"key":"a","value":"b"},{"key":"c","value":"d"}]}`, _NS(_P("args"))},
178	},
179}, {
180	name:         "associative list",
181	rootTypeName: "myRoot",
182	schema: `types:
183- name: myRoot
184  map:
185    fields:
186    - name: list
187      type:
188        namedType: myList
189    - name: atomicList
190      type:
191        namedType: mySequence
192- name: myList
193  list:
194    elementType:
195      namedType: myElement
196    elementRelationship: associative
197    keys:
198    - key
199    - id
200- name: mySequence
201  list:
202    elementType:
203      scalar: string
204    elementRelationship: atomic
205- name: myElement
206  map:
207    fields:
208    - name: key
209      type:
210        scalar: string
211    - name: id
212      type:
213        scalar: numeric
214    - name: value
215      type:
216        namedType: myValue
217    - name: bv
218      type:
219        scalar: boolean
220    - name: nv
221      type:
222        scalar: numeric
223- name: myValue
224  map:
225    elementType:
226      scalar: string
227`,
228	pairs: []objSetPair{
229		{`{"list":[]}`, _NS()},
230		{`{"list":[{"key":"a","id":1,"value":{"a":"a"}}]}`, _NS(
231			_P("list", _KBF("key", "a", "id", 1)),
232			_P("list", _KBF("key", "a", "id", 1), "key"),
233			_P("list", _KBF("key", "a", "id", 1), "id"),
234			_P("list", _KBF("key", "a", "id", 1), "value", "a"),
235		)},
236		{`{"list":[{"key":"a","id":1},{"key":"a","id":2},{"key":"b","id":1}]}`, _NS(
237			_P("list", _KBF("key", "a", "id", 1)),
238			_P("list", _KBF("key", "a", "id", 2)),
239			_P("list", _KBF("key", "b", "id", 1)),
240			_P("list", _KBF("key", "a", "id", 1), "key"),
241			_P("list", _KBF("key", "a", "id", 1), "id"),
242			_P("list", _KBF("key", "a", "id", 2), "key"),
243			_P("list", _KBF("key", "a", "id", 2), "id"),
244			_P("list", _KBF("key", "b", "id", 1), "key"),
245			_P("list", _KBF("key", "b", "id", 1), "id"),
246		)},
247		{`{"atomicList":["a","a","a"]}`, _NS(_P("atomicList"))},
248	},
249}}
250
251func (tt fieldsetTestCase) test(t *testing.T) {
252	parser, err := typed.NewParser(tt.schema)
253	if err != nil {
254		t.Fatalf("failed to create schema: %v", err)
255	}
256	for i, v := range tt.pairs {
257		v := v
258		t.Run(fmt.Sprintf("%v-%v", tt.name, i), func(t *testing.T) {
259			t.Parallel()
260			tv, err := parser.Type(tt.rootTypeName).FromYAML(v.object)
261			if err != nil {
262				t.Errorf("failed to parse object: %v", err)
263			}
264			fs, err := tv.ToFieldSet()
265			if err != nil {
266				t.Fatalf("got validation errors: %v", err)
267			}
268			if !fs.Equals(v.set) {
269				t.Errorf("wanted\n%s\ngot\n%s\n", v.set, fs)
270			}
271		})
272	}
273}
274
275func TestToFieldSet(t *testing.T) {
276	for _, tt := range fieldsetCases {
277		tt := tt
278		t.Run(tt.name, func(t *testing.T) {
279			t.Parallel()
280			tt.test(t)
281		})
282	}
283}
284