1
16
17 package cel
18
19 import (
20 "reflect"
21 "testing"
22
23 "github.com/google/cel-go/common/types"
24 "github.com/google/cel-go/common/types/ref"
25 "github.com/google/cel-go/common/types/traits"
26
27 "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
28 )
29
30 var (
31 listTypeSet = "set"
32 listTypeMap = "map"
33 stringSchema = schema.Structural{
34 Generic: schema.Generic{
35 Type: "string",
36 },
37 }
38 intSchema = schema.Structural{
39 Generic: schema.Generic{
40 Type: "integer",
41 },
42 ValueValidation: &schema.ValueValidation{
43 Format: "int64",
44 },
45 }
46 mapListElementSchema = schema.Structural{
47 Generic: schema.Generic{
48 Type: "object",
49 },
50 Properties: map[string]schema.Structural{
51 "key": stringSchema,
52 "val": intSchema,
53 },
54 }
55 mapListSchema = schema.Structural{
56 Extensions: schema.Extensions{XListType: &listTypeMap, XListMapKeys: []string{"key"}},
57 Generic: schema.Generic{
58 Type: "array",
59 },
60 Items: &mapListElementSchema,
61 }
62 multiKeyMapListSchema = schema.Structural{
63 Extensions: schema.Extensions{XListType: &listTypeMap, XListMapKeys: []string{"key1", "key2"}},
64 Generic: schema.Generic{
65 Type: "array",
66 },
67 Items: &schema.Structural{
68 Generic: schema.Generic{
69 Type: "object",
70 },
71 Properties: map[string]schema.Structural{
72 "key1": stringSchema,
73 "key2": stringSchema,
74 "val": intSchema,
75 },
76 },
77 }
78 setListSchema = schema.Structural{
79 Extensions: schema.Extensions{XListType: &listTypeSet},
80 Generic: schema.Generic{
81 Type: "array",
82 },
83 Items: &stringSchema,
84 }
85 atomicListSchema = schema.Structural{
86 Generic: schema.Generic{
87 Type: "array",
88 },
89 Items: &stringSchema,
90 }
91 objectSchema = schema.Structural{
92 Generic: schema.Generic{
93 Type: "object",
94 },
95 Properties: map[string]schema.Structural{
96 "field1": stringSchema,
97 "field2": stringSchema,
98 },
99 }
100 mapSchema = schema.Structural{
101 Generic: schema.Generic{
102 Type: "object",
103 AdditionalProperties: &schema.StructuralOrBool{
104 Bool: true,
105 Structural: &stringSchema,
106 },
107 },
108 }
109 )
110
111 func TestEquality(t *testing.T) {
112 cases := []struct {
113 name string
114 lhs ref.Val
115 rhs ref.Val
116 equal bool
117 }{
118 {
119 name: "map lists are equal regardless of order",
120 lhs: UnstructuredToVal([]interface{}{
121 map[string]interface{}{
122 "key": "a",
123 "val": 1,
124 },
125 map[string]interface{}{
126 "key": "b",
127 "val": 2,
128 },
129 }, &mapListSchema),
130 rhs: UnstructuredToVal([]interface{}{
131 map[string]interface{}{
132 "key": "b",
133 "val": 2,
134 },
135 map[string]interface{}{
136 "key": "a",
137 "val": 1,
138 },
139 }, &mapListSchema),
140 equal: true,
141 },
142 {
143 name: "map lists are not equal if contents differs",
144 lhs: UnstructuredToVal([]interface{}{
145 map[string]interface{}{
146 "key": "a",
147 "val": 1,
148 },
149 map[string]interface{}{
150 "key": "b",
151 "val": 2,
152 },
153 }, &mapListSchema),
154 rhs: UnstructuredToVal([]interface{}{
155 map[string]interface{}{
156 "key": "a",
157 "val": 1,
158 },
159 map[string]interface{}{
160 "key": "b",
161 "val": 3,
162 },
163 }, &mapListSchema),
164 equal: false,
165 },
166 {
167 name: "map lists are not equal if length differs",
168 lhs: UnstructuredToVal([]interface{}{
169 map[string]interface{}{
170 "key": "a",
171 "val": 1,
172 },
173 map[string]interface{}{
174 "key": "b",
175 "val": 2,
176 },
177 }, &mapListSchema),
178 rhs: UnstructuredToVal([]interface{}{
179 map[string]interface{}{
180 "key": "a",
181 "val": 1,
182 },
183 map[string]interface{}{
184 "key": "b",
185 "val": 2,
186 },
187 map[string]interface{}{
188 "key": "c",
189 "val": 3,
190 },
191 }, &mapListSchema),
192 equal: false,
193 },
194 {
195 name: "multi-key map lists are equal regardless of order",
196 lhs: UnstructuredToVal([]interface{}{
197 map[string]interface{}{
198 "key1": "a1",
199 "key2": "a2",
200 "val": 1,
201 },
202 map[string]interface{}{
203 "key1": "b1",
204 "key2": "b2",
205 "val": 2,
206 },
207 }, &multiKeyMapListSchema),
208 rhs: UnstructuredToVal([]interface{}{
209 map[string]interface{}{
210 "key1": "b1",
211 "key2": "b2",
212 "val": 2,
213 },
214 map[string]interface{}{
215 "key1": "a1",
216 "key2": "a2",
217 "val": 1,
218 },
219 }, &multiKeyMapListSchema),
220 equal: true,
221 },
222 {
223 name: "multi-key map lists with different contents are not equal",
224 lhs: UnstructuredToVal([]interface{}{
225 map[string]interface{}{
226 "key1": "a1",
227 "key2": "a2",
228 "val": 1,
229 },
230 map[string]interface{}{
231 "key1": "b1",
232 "key2": "b2",
233 "val": 2,
234 },
235 }, &multiKeyMapListSchema),
236 rhs: UnstructuredToVal([]interface{}{
237 map[string]interface{}{
238 "key1": "a1",
239 "key2": "a2",
240 "val": 1,
241 },
242 map[string]interface{}{
243 "key1": "b1",
244 "key2": "b2",
245 "val": 3,
246 },
247 }, &multiKeyMapListSchema),
248 equal: false,
249 },
250 {
251 name: "multi-key map lists with different keys are not equal",
252 lhs: UnstructuredToVal([]interface{}{
253 map[string]interface{}{
254 "key1": "a1",
255 "key2": "a2",
256 "val": 1,
257 },
258 map[string]interface{}{
259 "key1": "b1",
260 "key2": "b2",
261 "val": 2,
262 },
263 }, &multiKeyMapListSchema),
264 rhs: UnstructuredToVal([]interface{}{
265 map[string]interface{}{
266 "key1": "a1",
267 "key2": "a2",
268 "val": 1,
269 },
270 map[string]interface{}{
271 "key1": "c1",
272 "key2": "c2",
273 "val": 3,
274 },
275 }, &multiKeyMapListSchema),
276 equal: false,
277 },
278 {
279 name: "multi-key map lists with different lengths are not equal",
280 lhs: UnstructuredToVal([]interface{}{
281 map[string]interface{}{
282 "key1": "a1",
283 "key2": "a2",
284 "val": 1,
285 },
286 }, &multiKeyMapListSchema),
287 rhs: UnstructuredToVal([]interface{}{
288 map[string]interface{}{
289 "key1": "a1",
290 "key2": "a2",
291 "val": 1,
292 },
293 map[string]interface{}{
294 "key1": "b1",
295 "key2": "b2",
296 "val": 3,
297 },
298 }, &multiKeyMapListSchema),
299 equal: false,
300 },
301 {
302 name: "set lists are equal regardless of order",
303 lhs: UnstructuredToVal([]interface{}{"a", "b"}, &setListSchema),
304 rhs: UnstructuredToVal([]interface{}{"b", "a"}, &setListSchema),
305 equal: true,
306 },
307 {
308 name: "set lists are not equal if contents differ",
309 lhs: UnstructuredToVal([]interface{}{"a", "b"}, &setListSchema),
310 rhs: UnstructuredToVal([]interface{}{"a", "c"}, &setListSchema),
311 equal: false,
312 },
313 {
314 name: "set lists are not equal if lengths differ",
315 lhs: UnstructuredToVal([]interface{}{"a", "b"}, &setListSchema),
316 rhs: UnstructuredToVal([]interface{}{"a", "b", "c"}, &setListSchema),
317 equal: false,
318 },
319 {
320 name: "identical atomic lists are equal",
321 lhs: UnstructuredToVal([]interface{}{"a", "b"}, &atomicListSchema),
322 rhs: UnstructuredToVal([]interface{}{"a", "b"}, &atomicListSchema),
323 equal: true,
324 },
325 {
326 name: "atomic lists are not equal if order differs",
327 lhs: UnstructuredToVal([]interface{}{"a", "b"}, &atomicListSchema),
328 rhs: UnstructuredToVal([]interface{}{"b", "a"}, &atomicListSchema),
329 equal: false,
330 },
331 {
332 name: "atomic lists are not equal if contents differ",
333 lhs: UnstructuredToVal([]interface{}{"a", "b"}, &atomicListSchema),
334 rhs: UnstructuredToVal([]interface{}{"a", "c"}, &atomicListSchema),
335 equal: false,
336 },
337 {
338 name: "atomic lists are not equal if lengths differ",
339 lhs: UnstructuredToVal([]interface{}{"a", "b"}, &atomicListSchema),
340 rhs: UnstructuredToVal([]interface{}{"a", "b", "c"}, &atomicListSchema),
341 equal: false,
342 },
343 {
344 name: "identical objects are equal",
345 lhs: UnstructuredToVal(map[string]interface{}{"field1": "a", "field2": "b"}, &objectSchema),
346 rhs: UnstructuredToVal(map[string]interface{}{"field1": "a", "field2": "b"}, &objectSchema),
347 equal: true,
348 },
349 {
350 name: "objects are equal regardless of field order",
351 lhs: UnstructuredToVal(map[string]interface{}{"field1": "a", "field2": "b"}, &objectSchema),
352 rhs: UnstructuredToVal(map[string]interface{}{"field2": "b", "field1": "a"}, &objectSchema),
353 equal: true,
354 },
355 {
356 name: "objects are not equal if contents differs",
357 lhs: UnstructuredToVal(map[string]interface{}{"field1": "a", "field2": "b"}, &objectSchema),
358 rhs: UnstructuredToVal(map[string]interface{}{"field1": "a", "field2": "c"}, &objectSchema),
359 equal: false,
360 },
361 {
362 name: "objects are not equal if length differs",
363 lhs: UnstructuredToVal(map[string]interface{}{"field1": "a", "field2": "b"}, &objectSchema),
364 rhs: UnstructuredToVal(map[string]interface{}{"field1": "a"}, &objectSchema),
365 equal: false,
366 },
367 {
368 name: "identical maps are equal",
369 lhs: UnstructuredToVal(map[string]interface{}{"key1": "a", "key2": "b"}, &mapSchema),
370 rhs: UnstructuredToVal(map[string]interface{}{"key1": "a", "key2": "b"}, &mapSchema),
371 equal: true,
372 },
373 {
374 name: "maps are equal regardless of field order",
375 lhs: UnstructuredToVal(map[string]interface{}{"key1": "a", "key2": "b"}, &mapSchema),
376 rhs: UnstructuredToVal(map[string]interface{}{"key2": "b", "key1": "a"}, &mapSchema),
377 equal: true,
378 },
379 {
380 name: "maps are not equal if contents differs",
381 lhs: UnstructuredToVal(map[string]interface{}{"key1": "a", "key2": "b"}, &mapSchema),
382 rhs: UnstructuredToVal(map[string]interface{}{"key1": "a", "key2": "c"}, &mapSchema),
383 equal: false,
384 },
385 {
386 name: "maps are not equal if length differs",
387 lhs: UnstructuredToVal(map[string]interface{}{"key1": "a", "key2": "b"}, &mapSchema),
388 rhs: UnstructuredToVal(map[string]interface{}{"key1": "a", "key2": "b", "key3": "c"}, &mapSchema),
389 equal: false,
390 },
391 }
392
393 for _, tc := range cases {
394 t.Run(tc.name, func(t *testing.T) {
395
396 if tc.lhs.Equal(tc.rhs) != types.Bool(tc.equal) {
397 t.Errorf("expected Equals to return %v", tc.equal)
398 }
399 if tc.rhs.Equal(tc.lhs) != types.Bool(tc.equal) {
400 t.Errorf("expected Equals to return %v", tc.equal)
401 }
402
403
404
405 if tc.lhs.Equal(types.DefaultTypeAdapter.NativeToValue(tc.rhs.Value())) != types.Bool(tc.equal) {
406 t.Errorf("expected unstructuredVal.Equals(<native type>) to return %v", tc.equal)
407 }
408 if tc.rhs.Equal(types.DefaultTypeAdapter.NativeToValue(tc.lhs.Value())) != types.Bool(tc.equal) {
409 t.Errorf("expected unstructuredVal.Equals(<native type>) to return %v", tc.equal)
410 }
411 })
412 }
413 }
414
415 func TestLister(t *testing.T) {
416 cases := []struct {
417 name string
418 unstructured []interface{}
419 schema *schema.Structural
420 itemSchema *schema.Structural
421 size int64
422 notContains []ref.Val
423 addition []interface{}
424 expectAdded []interface{}
425 }{
426 {
427 name: "map list",
428 unstructured: []interface{}{
429 map[string]interface{}{
430 "key": "a",
431 "val": 1,
432 },
433 map[string]interface{}{
434 "key": "b",
435 "val": 2,
436 },
437 },
438 schema: &mapListSchema,
439 itemSchema: &mapListElementSchema,
440 size: 2,
441 notContains: []ref.Val{
442 UnstructuredToVal(map[string]interface{}{
443 "key": "a",
444 "val": 2,
445 }, &mapListElementSchema),
446 UnstructuredToVal(map[string]interface{}{
447 "key": "c",
448 "val": 1,
449 }, &mapListElementSchema),
450 },
451 addition: []interface{}{
452 map[string]interface{}{
453 "key": "b",
454 "val": 3,
455 },
456 map[string]interface{}{
457 "key": "c",
458 "val": 4,
459 },
460 },
461 expectAdded: []interface{}{
462 map[string]interface{}{
463 "key": "a",
464 "val": 1,
465 },
466 map[string]interface{}{
467 "key": "b",
468 "val": 3,
469 },
470 map[string]interface{}{
471 "key": "c",
472 "val": 4,
473 },
474 },
475 },
476 {
477 name: "set list",
478 unstructured: []interface{}{"a", "b"},
479 schema: &setListSchema,
480 itemSchema: &stringSchema,
481 size: 2,
482 notContains: []ref.Val{UnstructuredToVal("c", &stringSchema)},
483 addition: []interface{}{"b", "c"},
484 expectAdded: []interface{}{"a", "b", "c"},
485 },
486 {
487 name: "atomic list",
488 unstructured: []interface{}{"a", "b"},
489 schema: &atomicListSchema,
490 itemSchema: &stringSchema,
491 size: 2,
492 notContains: []ref.Val{UnstructuredToVal("c", &stringSchema)},
493 addition: []interface{}{"b", "c"},
494 expectAdded: []interface{}{"a", "b", "b", "c"},
495 },
496 }
497
498 for _, tc := range cases {
499 t.Run(tc.name, func(t *testing.T) {
500 lister := UnstructuredToVal(tc.unstructured, tc.schema).(traits.Lister)
501 if lister.Size().Value() != tc.size {
502 t.Errorf("Expected Size to return %d but got %d", tc.size, lister.Size().Value())
503 }
504 iter := lister.Iterator()
505 for i := 0; i < int(tc.size); i++ {
506 get := lister.Get(types.Int(i)).Value()
507 if !reflect.DeepEqual(get, tc.unstructured[i]) {
508 t.Errorf("Expected Get to return %v for index %d but got %v", tc.unstructured[i], i, get)
509 }
510 if iter.HasNext() != types.True {
511 t.Error("Expected HasNext to return true")
512 }
513 next := iter.Next().Value()
514 if !reflect.DeepEqual(next, tc.unstructured[i]) {
515 t.Errorf("Expected Next to return %v for index %d but got %v", tc.unstructured[i], i, next)
516 }
517 }
518 if iter.HasNext() != types.False {
519 t.Error("Expected HasNext to return false")
520 }
521 for _, contains := range tc.unstructured {
522 if lister.Contains(UnstructuredToVal(contains, tc.itemSchema)) != types.True {
523 t.Errorf("Expected Contains to return true for %v", contains)
524 }
525 }
526 for _, notContains := range tc.notContains {
527 if lister.Contains(notContains) != types.False {
528 t.Errorf("Expected Contains to return false for %v", notContains)
529 }
530 }
531
532 addition := UnstructuredToVal(tc.addition, tc.schema).(traits.Lister)
533 added := lister.Add(addition).Value()
534 if !reflect.DeepEqual(added, tc.expectAdded) {
535 t.Errorf("Expected Add to return %v but got %v", tc.expectAdded, added)
536 }
537 })
538 }
539 }
540
541 func TestMapper(t *testing.T) {
542 cases := []struct {
543 name string
544 unstructured map[string]interface{}
545 schema *schema.Structural
546 propertySchema func(key string) (*schema.Structural, bool)
547 size int64
548 notContains []ref.Val
549 }{
550 {
551 name: "object",
552 unstructured: map[string]interface{}{
553 "field1": "a",
554 "field2": "b",
555 },
556 schema: &objectSchema,
557 propertySchema: func(key string) (*schema.Structural, bool) {
558 if s, ok := objectSchema.Properties[key]; ok {
559 return &s, true
560 }
561 return nil, false
562 },
563 size: 2,
564 notContains: []ref.Val{
565 UnstructuredToVal("field3", &stringSchema),
566 },
567 },
568 {
569 name: "map",
570 unstructured: map[string]interface{}{
571 "key1": "a",
572 "key2": "b",
573 },
574 schema: &mapSchema,
575 propertySchema: func(key string) (*schema.Structural, bool) { return mapSchema.AdditionalProperties.Structural, true },
576 size: 2,
577 notContains: []ref.Val{
578 UnstructuredToVal("key3", &stringSchema),
579 },
580 },
581 }
582
583 for _, tc := range cases {
584 t.Run(tc.name, func(t *testing.T) {
585 mapper := UnstructuredToVal(tc.unstructured, tc.schema).(traits.Mapper)
586 if mapper.Size().Value() != tc.size {
587 t.Errorf("Expected Size to return %d but got %d", tc.size, mapper.Size().Value())
588 }
589 iter := mapper.Iterator()
590 iterResults := map[interface{}]struct{}{}
591 keys := map[interface{}]struct{}{}
592 for k := range tc.unstructured {
593 keys[k] = struct{}{}
594 get := mapper.Get(types.String(k)).Value()
595 if !reflect.DeepEqual(get, tc.unstructured[k]) {
596 t.Errorf("Expected Get to return %v for key %s but got %v", tc.unstructured[k], k, get)
597 }
598 if iter.HasNext() != types.True {
599 t.Error("Expected HasNext to return true")
600 }
601 iterResults[iter.Next().Value()] = struct{}{}
602 }
603 if !reflect.DeepEqual(iterResults, keys) {
604 t.Errorf("Expected accumulation of iterator.Next calls to be %v but got %v", keys, iterResults)
605 }
606 if iter.HasNext() != types.False {
607 t.Error("Expected HasNext to return false")
608 }
609 for contains := range tc.unstructured {
610 if mapper.Contains(UnstructuredToVal(contains, &stringSchema)) != types.True {
611 t.Errorf("Expected Contains to return true for %v", contains)
612 }
613 }
614 for _, notContains := range tc.notContains {
615 if mapper.Contains(notContains) != types.False {
616 t.Errorf("Expected Contains to return false for %v", notContains)
617 }
618 }
619 })
620 }
621 }
622
623 func BenchmarkUnstructuredToVal(b *testing.B) {
624 u := []interface{}{
625 map[string]interface{}{
626 "key": "a",
627 "val": 1,
628 },
629 map[string]interface{}{
630 "key": "b",
631 "val": 2,
632 },
633 map[string]interface{}{
634 "key": "@b",
635 "val": 2,
636 },
637 }
638
639 b.ReportAllocs()
640 b.ResetTimer()
641
642 for n := 0; n < b.N; n++ {
643 if val := UnstructuredToVal(u, &mapListSchema); val == nil {
644 b.Fatal(val)
645 }
646 }
647 }
648
649 func BenchmarkUnstructuredToValWithEscape(b *testing.B) {
650 u := []interface{}{
651 map[string]interface{}{
652 "key": "a.1",
653 "val": "__i.1",
654 },
655 map[string]interface{}{
656 "key": "b.1",
657 "val": 2,
658 },
659 }
660
661 b.ReportAllocs()
662 b.ResetTimer()
663
664 for n := 0; n < b.N; n++ {
665 if val := UnstructuredToVal(u, &mapListSchema); val == nil {
666 b.Fatal(val)
667 }
668 }
669 }
670
View as plain text