1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package dcl
16
17 import (
18 "reflect"
19 "sort"
20 "testing"
21
22 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util/pathslice"
25
26 "github.com/google/go-cmp/cmp"
27 "github.com/nasa9084/go-openapi"
28 )
29
30 var mutableButUnreadableFieldAnnotationTests = []struct {
31 name string
32 resource *Resource
33 expectedMutableButUnreadableFieldsState map[string]interface{}
34 }{
35 {
36 name: "top-level fields",
37 resource: &Resource{
38 Resource: k8s.Resource{
39 Spec: map[string]interface{}{
40 "fieldA": "val1",
41 "fieldB": int64(2),
42 "fieldC": map[string]interface{}{
43 "field1": "val1",
44 "field2": "val2",
45 },
46 "fieldD": []interface{}{
47 "val1",
48 "val2",
49 },
50 },
51 },
52 Schema: &openapi.Schema{
53 Type: "object",
54 Properties: map[string]*openapi.Schema{
55 "fieldA": {
56 Type: "string",
57 Extension: map[string]interface{}{
58 "x-dcl-mutable-unreadable": true,
59 },
60 },
61 "fieldB": {
62 Type: "integer",
63 },
64 "fieldC": {
65 Type: "object",
66 Properties: map[string]*openapi.Schema{
67 "field1": {
68 Type: "string",
69 },
70 "field2": {
71 Type: "string",
72 },
73 },
74 Extension: map[string]interface{}{
75 "x-dcl-mutable-unreadable": true,
76 },
77 },
78 "fieldD": {
79 Type: "array",
80 Items: &openapi.Schema{
81 Type: "string",
82 },
83 Extension: map[string]interface{}{
84 "x-dcl-mutable-unreadable": true,
85 },
86 },
87 },
88 },
89 },
90 expectedMutableButUnreadableFieldsState: map[string]interface{}{
91 "spec": map[string]interface{}{
92 "fieldA": "val1",
93 "fieldC": map[string]interface{}{
94 "field1": "val1",
95 "field2": "val2",
96 },
97 "fieldD": []interface{}{
98 "val1",
99 "val2",
100 },
101 },
102 },
103 },
104 {
105 name: "nested fields",
106 resource: &Resource{
107 Resource: k8s.Resource{
108 Spec: map[string]interface{}{
109 "parentField": map[string]interface{}{
110 "fieldA": "val1",
111 "fieldB": int64(2),
112 "fieldC": map[string]interface{}{
113 "field1": "val1",
114 "field2": "val2",
115 },
116 "fieldD": []interface{}{
117 "val1",
118 "val2",
119 },
120 },
121 },
122 },
123 Schema: &openapi.Schema{
124 Type: "object",
125 Properties: map[string]*openapi.Schema{
126 "parentField": {
127 Type: "object",
128 Properties: map[string]*openapi.Schema{
129 "fieldA": {
130 Type: "string",
131 Extension: map[string]interface{}{
132 "x-dcl-mutable-unreadable": true,
133 },
134 },
135 "fieldB": {
136 Type: "integer",
137 },
138 "fieldC": {
139 Type: "object",
140 Properties: map[string]*openapi.Schema{
141 "field1": {
142 Type: "string",
143 },
144 "field2": {
145 Type: "string",
146 },
147 },
148 Extension: map[string]interface{}{
149 "x-dcl-mutable-unreadable": true,
150 },
151 },
152 "fieldD": {
153 Type: "array",
154 Items: &openapi.Schema{
155 Type: "string",
156 },
157 Extension: map[string]interface{}{
158 "x-dcl-mutable-unreadable": true,
159 },
160 },
161 },
162 },
163 },
164 },
165 },
166 expectedMutableButUnreadableFieldsState: map[string]interface{}{
167 "spec": map[string]interface{}{
168 "parentField": map[string]interface{}{
169 "fieldA": "val1",
170 "fieldC": map[string]interface{}{
171 "field1": "val1",
172 "field2": "val2",
173 },
174 "fieldD": []interface{}{
175 "val1",
176 "val2",
177 },
178 },
179 },
180 },
181 },
182 {
183 name: "sensitive field",
184 resource: &Resource{
185 Resource: k8s.Resource{
186 Spec: map[string]interface{}{
187 "secretValueField": map[string]interface{}{
188 "value": "test-1",
189 },
190 },
191 },
192 Schema: &openapi.Schema{
193 Type: "object",
194 Properties: map[string]*openapi.Schema{
195 "secretValueField": {
196 Type: "string",
197 Extension: map[string]interface{}{
198 "x-dcl-mutable-unreadable": true,
199 "x-dcl-sensitive": true,
200 },
201 },
202 },
203 },
204 },
205 expectedMutableButUnreadableFieldsState: map[string]interface{}{
206 "spec": map[string]interface{}{
207 "secretValueField": map[string]interface{}{
208 "value": "test-1",
209 },
210 },
211 },
212 },
213 {
214 name: "no mutable-but-unreadable fields set in spec",
215 resource: &Resource{
216 Resource: k8s.Resource{
217 Spec: map[string]interface{}{
218 "fieldB": int64(2),
219 "parentField": map[string]interface{}{
220 "fieldB": int64(2),
221 },
222 },
223 },
224 Schema: &openapi.Schema{
225 Type: "object",
226 Properties: map[string]*openapi.Schema{
227 "fieldA": {
228 Type: "string",
229 Extension: map[string]interface{}{
230 "x-dcl-mutable-unreadable": true,
231 },
232 },
233 "fieldB": {
234 Type: "integer",
235 },
236 "parentField": {
237 Type: "object",
238 Properties: map[string]*openapi.Schema{
239 "fieldA": {
240 Type: "string",
241 Extension: map[string]interface{}{
242 "x-dcl-mutable-unreadable": true,
243 },
244 },
245 "fieldB": {
246 Type: "integer",
247 },
248 },
249 },
250 },
251 },
252 },
253 expectedMutableButUnreadableFieldsState: map[string]interface{}{},
254 },
255 {
256 name: "no fields marked mutable-but-unreadable",
257 resource: &Resource{
258 Resource: k8s.Resource{
259 Spec: map[string]interface{}{
260 "fieldA": "val1",
261 },
262 },
263 Schema: &openapi.Schema{
264 Type: "object",
265 Properties: map[string]*openapi.Schema{
266 "fieldA": {
267 Type: "string",
268 },
269 "fieldB": {
270 Type: "integer",
271 },
272 },
273 },
274 },
275 expectedMutableButUnreadableFieldsState: map[string]interface{}{},
276 },
277 {
278 name: "no spec",
279 resource: &Resource{
280 Schema: &openapi.Schema{
281 Type: "object",
282 Properties: map[string]*openapi.Schema{
283 "fieldA": {
284 Type: "string",
285 Extension: map[string]interface{}{
286 "x-dcl-mutable-unreadable": true,
287 },
288 },
289 "fieldB": {
290 Type: "integer",
291 },
292 "parentField": {
293 Type: "object",
294 Properties: map[string]*openapi.Schema{
295 "fieldA": {
296 Type: "string",
297 Extension: map[string]interface{}{
298 "x-dcl-mutable-unreadable": true,
299 },
300 },
301 "fieldB": {
302 Type: "integer",
303 },
304 },
305 },
306 },
307 },
308 },
309 expectedMutableButUnreadableFieldsState: map[string]interface{}{},
310 },
311 }
312
313 func TestMutableButUnreadableFieldsAnnotationFor(t *testing.T) {
314 for _, tc := range mutableButUnreadableFieldAnnotationTests {
315 tc := tc
316 t.Run(tc.name, func(t *testing.T) {
317 t.Parallel()
318 annotationInString, err := MutableButUnreadableFieldsAnnotationFor(tc.resource)
319 if err != nil {
320 t.Fatal(err)
321 }
322
323 expectedStateInString, err := util.MarshalToJSONString(tc.expectedMutableButUnreadableFieldsState)
324 if err != nil {
325 t.Fatalf("error marshaling the expected state to string: %v", err)
326 }
327 if got, want := annotationInString, expectedStateInString; got != want {
328 t.Fatalf("got %v, want %v", got, want)
329 }
330 })
331 }
332 }
333
334 func TestGetMutableButUnreadableFieldsFromAnnotations(t *testing.T) {
335 for _, tc := range mutableButUnreadableFieldAnnotationTests {
336 tc := tc
337 t.Run(tc.name, func(t *testing.T) {
338 t.Parallel()
339 mutableButUnreadableFields, err := GetMutableButUnreadableFieldsFromAnnotations(tc.resource)
340 if err != nil {
341 t.Fatal(err)
342 }
343 if got, want := mutableButUnreadableFields, tc.expectedMutableButUnreadableFieldsState; !reflect.DeepEqual(got, want) {
344 t.Fatalf("unexpected mutable-but-unreadable fields diff (-want +got): \n%v", cmp.Diff(want, got))
345 }
346 })
347 }
348 }
349
350 func TestGetMutableButUnreadableDCLPathsInObject(t *testing.T) {
351 tests := []struct {
352 name string
353 schema *openapi.Schema
354 expectedResults []string
355 hasError bool
356 }{
357 {
358 name: "mutable-but-unreadable primitive fields",
359 schema: &openapi.Schema{
360 Type: "object",
361 Properties: map[string]*openapi.Schema{
362 "foo": {
363 Type: "string",
364 Extension: map[string]interface{}{
365 "x-dcl-mutable-unreadable": true,
366 },
367 },
368 "bar": {
369 Type: "integer",
370 Extension: map[string]interface{}{
371 "x-dcl-mutable-unreadable": true,
372 },
373 },
374 "baz": {
375 Type: "boolean",
376 Extension: map[string]interface{}{
377 "x-dcl-mutable-unreadable": true,
378 },
379 },
380 "quz": {
381 Type: "number",
382 Extension: map[string]interface{}{
383 "x-dcl-mutable-unreadable": true,
384 },
385 },
386 },
387 },
388 expectedResults: []string{"foo", "bar", "baz", "quz"},
389 },
390 {
391 name: "mutable-but-unreadable object field",
392 schema: &openapi.Schema{
393 Type: "object",
394 Properties: map[string]*openapi.Schema{
395 "fooObj": {
396 Type: "object",
397 Properties: map[string]*openapi.Schema{
398 "nestedField1": {
399 Type: "boolean",
400 },
401 "nestedField2": {
402 Type: "string",
403 },
404 },
405 Extension: map[string]interface{}{
406 "x-dcl-mutable-unreadable": true,
407 },
408 },
409 },
410 },
411 expectedResults: []string{"fooObj"},
412 },
413 {
414 name: "mutable-but-unreadable map field",
415 schema: &openapi.Schema{
416 Type: "object",
417 Properties: map[string]*openapi.Schema{
418 "fooMap": {
419 Type: "object",
420 AdditionalProperties: &openapi.Schema{
421 Type: "string",
422 },
423 Extension: map[string]interface{}{
424 "x-dcl-mutable-unreadable": true,
425 },
426 },
427 },
428 },
429 expectedResults: []string{"fooMap"},
430 },
431 {
432 name: "mutable-but-unreadable primitive array field",
433 schema: &openapi.Schema{
434 Type: "object",
435 Properties: map[string]*openapi.Schema{
436 "fooArray": {
437 Type: "array",
438 Items: &openapi.Schema{
439 Type: "string",
440 },
441 Extension: map[string]interface{}{
442 "x-dcl-mutable-unreadable": true,
443 },
444 },
445 },
446 },
447 expectedResults: []string{"fooArray"},
448 },
449 {
450 name: "nested mutable-but-unreadable primitive fields",
451 schema: &openapi.Schema{
452 Type: "object",
453 Properties: map[string]*openapi.Schema{
454 "fooObj": {
455 Type: "object",
456 Properties: map[string]*openapi.Schema{
457 "nestedField1": {
458 Type: "boolean",
459 },
460 "nestedField2": {
461 Type: "string",
462 Extension: map[string]interface{}{
463 "x-dcl-mutable-unreadable": true,
464 },
465 },
466 "nestedField3": {
467 Type: "object",
468 Properties: map[string]*openapi.Schema{
469 "bar": {
470 Type: "integer",
471 Extension: map[string]interface{}{
472 "x-dcl-mutable-unreadable": true,
473 },
474 },
475 },
476 },
477 },
478 },
479 },
480 },
481 expectedResults: []string{"fooObj.nestedField2", "fooObj.nestedField3.bar"},
482 },
483 {
484 name: "nested mutable-but-unreadable object fields",
485 schema: &openapi.Schema{
486 Type: "object",
487 Properties: map[string]*openapi.Schema{
488 "fooObj": {
489 Type: "object",
490 Properties: map[string]*openapi.Schema{
491 "nestedField": {
492 Type: "string",
493 },
494 "nestedObj": {
495 Type: "object",
496 Properties: map[string]*openapi.Schema{
497 "bar": {
498 Type: "integer",
499 },
500 "baz": {
501 Type: "boolean",
502 },
503 },
504 Extension: map[string]interface{}{
505 "x-dcl-mutable-unreadable": true,
506 },
507 },
508 },
509 },
510 },
511 },
512 expectedResults: []string{"fooObj.nestedObj"},
513 },
514 {
515 name: "nested mutable-but-unreadable map fields",
516 schema: &openapi.Schema{
517 Type: "object",
518 Properties: map[string]*openapi.Schema{
519 "fooObj": {
520 Type: "object",
521 Properties: map[string]*openapi.Schema{
522 "nestedMap1": {
523 Type: "object",
524 AdditionalProperties: &openapi.Schema{
525 Type: "string",
526 },
527 Extension: map[string]interface{}{
528 "x-dcl-mutable-unreadable": true,
529 },
530 },
531 "nestedObj": {
532 Type: "object",
533 Properties: map[string]*openapi.Schema{
534 "bar": {
535 Type: "integer",
536 },
537 "nestedMap2": {
538 Type: "object",
539 AdditionalProperties: &openapi.Schema{
540 Type: "string",
541 },
542 Extension: map[string]interface{}{
543 "x-dcl-mutable-unreadable": true,
544 },
545 },
546 },
547 },
548 },
549 },
550 },
551 },
552 expectedResults: []string{"fooObj.nestedMap1", "fooObj.nestedObj.nestedMap2"},
553 },
554 {
555 name: "nested mutable-but-unreadable primitive array fields",
556 schema: &openapi.Schema{
557 Type: "object",
558 Properties: map[string]*openapi.Schema{
559 "fooObj": {
560 Type: "object",
561 Properties: map[string]*openapi.Schema{
562 "nestedArray": {
563 Type: "array",
564 Items: &openapi.Schema{
565 Type: "string",
566 },
567 Extension: map[string]interface{}{
568 "x-dcl-mutable-unreadable": true,
569 },
570 },
571 },
572 },
573 },
574 },
575 expectedResults: []string{"fooObj.nestedArray"},
576 },
577 {
578 name: "mutable-but-unreadable subfield under read-only field should be ignored",
579 schema: &openapi.Schema{
580 Type: "object",
581 Properties: map[string]*openapi.Schema{
582 "fooObj": {
583 Type: "object",
584 Properties: map[string]*openapi.Schema{
585 "nestedField1": {
586 Type: "boolean",
587 },
588 "nestedField2": {
589 Type: "string",
590 Extension: map[string]interface{}{
591 "x-dcl-mutable-unreadable": true,
592 },
593 },
594 },
595 ReadOnly: true,
596 },
597 },
598 },
599 expectedResults: []string{},
600 },
601 {
602 name: "mutable-but-unreadable subfield in map should be ignored",
603 schema: &openapi.Schema{
604 Type: "object",
605 Properties: map[string]*openapi.Schema{
606 "fooMap": {
607 Type: "object",
608 AdditionalProperties: &openapi.Schema{
609 Type: "string",
610 Extension: map[string]interface{}{
611 "x-dcl-mutable-unreadable": true,
612 },
613 },
614 },
615 },
616 },
617 expectedResults: []string{},
618 },
619 {
620 name: "entry level mutable-but-unreadable field should be ignored",
621 schema: &openapi.Schema{
622 Type: "object",
623 Properties: map[string]*openapi.Schema{
624 "foo": {
625 Type: "object",
626 Properties: map[string]*openapi.Schema{
627 "nestedField1": {
628 Type: "boolean",
629 },
630 "nestedField2": {
631 Type: "string",
632 },
633 },
634 },
635 },
636 Extension: map[string]interface{}{
637 "x-dcl-mutable-unreadable": true,
638 },
639 },
640 expectedResults: []string{},
641 },
642 {
643 name: "entry level non-object schema should cause an error",
644 schema: &openapi.Schema{
645 Type: "string",
646 Extension: map[string]interface{}{
647 "x-dcl-mutable-unreadable": true,
648 },
649 },
650 hasError: true,
651 },
652 }
653
654 for _, tc := range tests {
655 tc := tc
656 t.Run(tc.name, func(t *testing.T) {
657 t.Parallel()
658 paths, err := getMutableButUnreadableDCLPathsInObject([]string{}, tc.schema)
659 if tc.hasError {
660 if err == nil {
661 t.Fatalf("got nil, but want an error getting mutable-but-unreadable fields")
662 }
663 return
664 }
665 if err != nil {
666 t.Fatalf("error getting mutable-but-unreadable fields: %v", err)
667 }
668
669 results := make([]string, 0)
670 for _, path := range paths {
671 results = append(results, pathslice.ToString(path))
672 }
673 sort.Strings(results)
674 sort.Strings(tc.expectedResults)
675 if got, want := results, tc.expectedResults; !reflect.DeepEqual(got, want) {
676 t.Fatalf("mutable-but-unreadable fields mismatch: got '%v', want '%v'", got, want)
677 }
678 })
679 }
680 }
681
View as plain text