1
16
17 package typed_test
18
19 import (
20 "fmt"
21 "testing"
22
23 "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
24 "sigs.k8s.io/structured-merge-diff/v4/typed"
25 )
26
27 type reconcileTestCase struct {
28 name string
29 rootTypeName string
30 oldSchema typed.YAMLObject
31 newSchema typed.YAMLObject
32 liveObject typed.YAMLObject
33 oldFields *fieldpath.Set
34 fixedFields *fieldpath.Set
35 }
36
37 func granularSchema(version string) typed.YAMLObject {
38 return typed.YAMLObject(fmt.Sprintf(`types:
39 - name: %s
40 map:
41 fields:
42 - name: struct
43 type:
44 namedType: struct
45 - name: list
46 type:
47 namedType: list
48 - name: objectList
49 type:
50 namedType: objectList
51 - name: stringMap
52 type:
53 namedType: stringMap
54 - name: unchanged
55 type:
56 namedType: unchanged
57 - name: struct
58 map:
59 fields:
60 - name: numeric
61 type:
62 scalar: numeric
63 - name: string
64 type:
65 scalar: string
66 - name: list
67 list:
68 elementType:
69 scalar: string
70 elementRelationship: associative
71 - name: objectList
72 list:
73 elementType:
74 namedType: listItem
75 elementRelationship: associative
76 keys:
77 - keyA
78 - keyB
79 - name: listItem
80 map:
81 fields:
82 - name: keyA
83 type:
84 scalar: string
85 - name: keyB
86 type:
87 scalar: string
88 - name: value
89 type:
90 scalar: string
91 - name: stringMap
92 map:
93 elementType:
94 scalar: string
95 - name: unchanged
96 map:
97 fields:
98 - name: numeric
99 type:
100 scalar: numeric
101 - name: empty
102 map:
103 elementType:
104 scalar: untyped
105 list:
106 elementType:
107 namedType: __untyped_atomic_
108 elementRelationship: atomic
109 map:
110 elementType:
111 namedType: __untyped_deduced_
112 elementRelationship: separable
113 - name: emptyWithPreserveUnknown
114 map:
115 fields:
116 - name: preserveField
117 type:
118 map:
119 elementType:
120 scalar: untyped
121 list:
122 elementType:
123 namedType: __untyped_atomic_
124 elementRelationship: atomic
125 map:
126 elementType:
127 namedType: __untyped_deduced_
128 elementRelationship: separable
129 - name: populatedWithPreserveUnknown
130 map:
131 fields:
132 - name: preserveField
133 type:
134 map:
135 fields:
136 - name: list
137 type:
138 namedType: list
139 elementType:
140 namedType: __untyped_deduced_
141 - name: __untyped_atomic_
142 scalar: untyped
143 list:
144 elementType:
145 namedType: __untyped_atomic_
146 elementRelationship: atomic
147 map:
148 elementType:
149 namedType: __untyped_atomic_
150 elementRelationship: atomic
151 - name: __untyped_deduced_
152 scalar: untyped
153 list:
154 elementType:
155 namedType: __untyped_atomic_
156 elementRelationship: atomic
157 map:
158 elementType:
159 namedType: __untyped_deduced_
160 elementRelationship: separable
161 `, version))
162 }
163
164 func atomicSchema(version string) typed.YAMLObject {
165 return typed.YAMLObject(fmt.Sprintf(`types:
166 - name: %s
167 map:
168 fields:
169 - name: struct
170 type:
171 namedType: struct
172 - name: list
173 type:
174 namedType: list
175 - name: objectList
176 type:
177 namedType: objectList
178 - name: stringMap
179 type:
180 namedType: stringMap
181 - name: unchanged
182 type:
183 namedType: unchanged
184 - name: struct
185 map:
186 fields:
187 - name: numeric
188 type:
189 scalar: numeric
190 - name: string
191 type:
192 scalar: string
193 elementRelationship: atomic
194 - name: list
195 list:
196 elementType:
197 scalar: string
198 elementRelationship: atomic
199 - name: objectList
200 list:
201 elementType:
202 namedType: listItem
203 elementRelationship: atomic
204 - name: listItem
205 map:
206 fields:
207 - name: keyA
208 type:
209 scalar: string
210 - name: keyB
211 type:
212 scalar: string
213 - name: value
214 type:
215 scalar: string
216 - name: stringMap
217 map:
218 elementType:
219 scalar: string
220 elementRelationship: atomic
221 - name: unchanged
222 map:
223 fields:
224 - name: numeric
225 type:
226 scalar: numeric
227 - name: empty
228 map:
229 elementType:
230 scalar: untyped
231 list:
232 elementType:
233 namedType: __untyped_atomic_
234 elementRelationship: atomic
235 map:
236 elementType:
237 namedType: __untyped_deduced_
238 elementRelationship: separable
239 - name: emptyWithPreserveUnknown
240 map:
241 fields:
242 - name: preserveField
243 type:
244 map:
245 elementType:
246 scalar: untyped
247 list:
248 elementType:
249 namedType: __untyped_atomic_
250 elementRelationship: atomic
251 map:
252 elementType:
253 namedType: __untyped_deduced_
254 elementRelationship: separable
255 - name: populatedWithPreserveUnknown
256 map:
257 fields:
258 - name: preserveField
259 type:
260 map:
261 fields:
262 - name: list
263 type:
264 namedType: list
265 elementType:
266 namedType: __untyped_deduced_
267 - name: __untyped_atomic_
268 scalar: untyped
269 list:
270 elementType:
271 namedType: __untyped_atomic_
272 elementRelationship: atomic
273 map:
274 elementType:
275 namedType: __untyped_atomic_
276 elementRelationship: atomic
277 - name: __untyped_deduced_
278 scalar: untyped
279 list:
280 elementType:
281 namedType: __untyped_atomic_
282 elementRelationship: atomic
283 map:
284 elementType:
285 namedType: __untyped_deduced_
286 elementRelationship: separable
287 `, version))
288 }
289
290 const basicLiveObject = typed.YAMLObject(`
291 struct:
292 numeric: 1
293 string: "two"
294 list:
295 - one
296 - two
297 objectList:
298 - keyA: a1
299 keyB: b1
300 value: v1
301 - keyA: a2
302 keyB: b2
303 value: v2
304 stringMap:
305 key1: value1
306 unchanged:
307 numeric: 10
308 `)
309
310 var reconcileCases = []reconcileTestCase{{
311 name: "granular-to-atomic",
312 rootTypeName: "v1",
313 oldSchema: granularSchema("v1"),
314 newSchema: atomicSchema("v1"),
315 liveObject: basicLiveObject,
316 oldFields: _NS(
317 _P("struct", "numeric"),
318 _P("list", _V("one")),
319 _P("stringMap", "key1"),
320 _P("objectList", _KBF("keyA", "a1", "keyB", "b1"), "value"),
321 _P("unchanged", "numeric"),
322 ),
323 fixedFields: _NS(
324 _P("struct"),
325 _P("list"),
326 _P("objectList"),
327 _P("stringMap"),
328 _P("unchanged", "numeric"),
329 ),
330 }, {
331 name: "no-change-granular",
332 rootTypeName: "v1",
333 oldSchema: granularSchema("v1"),
334 newSchema: granularSchema("v1"),
335 liveObject: basicLiveObject,
336 oldFields: _NS(
337 _P("struct", "numeric"),
338 _P("list", _V("one")),
339 _P("objectList", _KBF("keyA", "a1", "keyB", "b1"), "value"),
340 _P("stringMap", "key1"),
341 _P("unchanged", "numeric"),
342 ),
343 fixedFields: nil,
344 }, {
345 name: "no-change-atomic",
346 rootTypeName: "v1",
347 oldSchema: atomicSchema("v1"),
348 newSchema: atomicSchema("v1"),
349 liveObject: basicLiveObject,
350 oldFields: _NS(
351 _P("struct"),
352 _P("list"),
353 _P("objectList"),
354 _P("unchanged", "numeric"),
355 ),
356 fixedFields: nil,
357 }, {
358 name: "no-change-empty-granular",
359 rootTypeName: "v1",
360 oldSchema: granularSchema("v1"),
361 newSchema: granularSchema("v1"),
362 liveObject: typed.YAMLObject(`
363 struct: {}
364 list: []
365 objectList:
366 - keyA: a1
367 keyB: b1
368 stringMap: {}
369 unchanged: {}
370 `),
371 oldFields: _NS(
372 _P("struct"),
373 _P("list"),
374 _P("objectList"),
375 _P("objectList", _KBF("keyA", "a1", "keyB", "b1")),
376 _P("objectList", _KBF("keyA", "a1", "keyB", "b1"), "value"),
377 _P("unchanged"),
378 ),
379 fixedFields: nil,
380 }, {
381 name: "deduced",
382 rootTypeName: "empty",
383 oldSchema: atomicSchema("v1"),
384 newSchema: granularSchema("v1"),
385 liveObject: basicLiveObject,
386 oldFields: _NS(
387 _P("struct"),
388 _P("list"),
389 _P("objectList"),
390 _P("unchanged", "numeric"),
391 ),
392 fixedFields: nil,
393 }, {
394 name: "empty-preserve-unknown",
395 rootTypeName: "emptyWithPreserveUnknown",
396 oldSchema: atomicSchema("v1"),
397 newSchema: granularSchema("v1"),
398 liveObject: typed.YAMLObject(`
399 preserveField:
400 arbitrary: abc
401 `),
402 oldFields: _NS(
403 _P("preserveField"),
404 _P("preserveField", "arbitrary"),
405 ),
406 fixedFields: nil,
407 }, {
408 name: "populated-preserve-unknown",
409 rootTypeName: "populatedWithPreserveUnknown",
410 oldSchema: granularSchema("v1"),
411 newSchema: atomicSchema("v1"),
412 liveObject: typed.YAMLObject(`
413 preserveField:
414 arbitrary: abc
415 list:
416 - one
417 `),
418 oldFields: _NS(
419 _P("preserveField"),
420 _P("preserveField", "arbitrary"),
421 _P("preserveField", "list"),
422 _P("preserveField", "list", _V("one")),
423 ),
424 fixedFields: _NS(
425 _P("preserveField"),
426 _P("preserveField", "arbitrary"),
427 _P("preserveField", "list"),
428 ),
429 }}
430
431 func TestReconcileFieldSetWithSchema(t *testing.T) {
432 for _, tt := range reconcileCases {
433 tt := tt
434 t.Run(tt.name, func(t *testing.T) {
435 t.Parallel()
436 tt.testReconcileCase(t)
437 })
438 }
439 }
440
441 func (tt reconcileTestCase) testReconcileCase(t *testing.T) {
442 parser, err := typed.NewParser(tt.newSchema)
443 if err != nil {
444 t.Fatalf("failed to create schema: %v", err)
445 }
446 pt := parser.Type(tt.rootTypeName)
447 liveObject, err := pt.FromYAML(tt.liveObject)
448 if err != nil {
449 t.Fatalf("failed to parse/validate yaml: %v\n%v", err, tt.liveObject)
450 }
451
452 fixed, err := typed.ReconcileFieldSetWithSchema(tt.oldFields, liveObject)
453 if err != nil {
454 t.Fatalf("fixup errors: %v", err)
455 }
456 if tt.fixedFields == nil {
457 if fixed != nil {
458 t.Fatalf("expected fieldset to be null but got\n:%s", fixed.String())
459 }
460 return
461 }
462
463 if fixed == nil {
464 t.Fatalf("expected fieldset to be\n:%s\n:but got null", tt.fixedFields.String())
465 }
466
467 if !fixed.Equals(tt.fixedFields) {
468 t.Errorf("expected fieldset:\n%s\n:but got\n:%s", tt.fixedFields.String(), fixed.String())
469 }
470 }
471
View as plain text