1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package extension_test
16
17 import (
18 "testing"
19
20 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/extension"
21 "github.com/nasa9084/go-openapi"
22 )
23
24 func TestGetReferenceFieldName(t *testing.T) {
25 tests := []struct {
26 name string
27 path []string
28 convertedFieldName string
29 schema *openapi.Schema
30 hasError bool
31 }{
32 {
33 name: "append Ref to the original field name",
34 path: []string{"foo"},
35 convertedFieldName: "fooRef",
36 schema: &openapi.Schema{
37 Type: "string",
38 Extension: map[string]interface{}{
39 "x-dcl-references": []interface{}{
40 map[interface{}]interface{}{
41 "resource": "SomeService/SomeKind",
42 "field": "selfLink",
43 },
44 },
45 },
46 },
47 },
48 {
49 name: "append Ref to the original nested field name",
50 path: []string{"object", "foo"},
51 convertedFieldName: "fooRef",
52 schema: &openapi.Schema{
53 Type: "string",
54 Extension: map[string]interface{}{
55 "x-dcl-references": []interface{}{
56 map[interface{}]interface{}{
57 "resource": "SomeService/SomeKind",
58 "field": "selfLink",
59 },
60 },
61 },
62 },
63 },
64 {
65 name: "serviceAccountEmail is converted to serviceAccountRef",
66 path: []string{"serviceAccountEmail"},
67 convertedFieldName: "serviceAccountRef",
68 schema: &openapi.Schema{
69 Type: "string",
70 Extension: map[string]interface{}{
71 "x-dcl-references": []interface{}{
72 map[interface{}]interface{}{
73 "resource": "SomeService/SomeKind",
74 "field": "selfLink",
75 },
76 },
77 },
78 },
79 },
80 {
81 name: "nested serviceAccountEmail is converted to serviceAccountRef",
82 path: []string{"object", "serviceAccountEmail"},
83 convertedFieldName: "serviceAccountRef",
84 schema: &openapi.Schema{
85 Type: "string",
86 Extension: map[string]interface{}{
87 "x-dcl-references": []interface{}{
88 map[interface{}]interface{}{
89 "resource": "SomeService/SomeKind",
90 "field": "selfLink",
91 },
92 },
93 },
94 },
95 },
96 {
97 name: "kmsKeyName is converted to kmsKeyRef",
98 path: []string{"kmsKeyName"},
99 convertedFieldName: "kmsKeyRef",
100 schema: &openapi.Schema{
101 Type: "string",
102 Extension: map[string]interface{}{
103 "x-dcl-references": []interface{}{
104 map[interface{}]interface{}{
105 "resource": "SomeService/SomeKind",
106 "field": "selfLink",
107 },
108 },
109 },
110 },
111 },
112 {
113 name: "nested kmsKeyName is converted to kmsKeyRef",
114 path: []string{"object", "kmsKeyName"},
115 convertedFieldName: "kmsKeyRef",
116 schema: &openapi.Schema{
117 Type: "string",
118 Extension: map[string]interface{}{
119 "x-dcl-references": []interface{}{
120 map[interface{}]interface{}{
121 "resource": "SomeService/SomeKind",
122 "field": "selfLink",
123 },
124 },
125 },
126 },
127 },
128 {
129 name: "groupId is converted to groupRef",
130 path: []string{"groupId"},
131 convertedFieldName: "groupRef",
132 schema: &openapi.Schema{
133 Type: "string",
134 Extension: map[string]interface{}{
135 "x-dcl-references": []interface{}{
136 map[interface{}]interface{}{
137 "resource": "SomeService/SomeKind",
138 "field": "selfLink",
139 },
140 },
141 },
142 },
143 },
144 {
145 name: "nested groupId is converted to groupRef",
146 path: []string{"object", "groupId"},
147 convertedFieldName: "groupRef",
148 schema: &openapi.Schema{
149 Type: "string",
150 Extension: map[string]interface{}{
151 "x-dcl-references": []interface{}{
152 map[interface{}]interface{}{
153 "resource": "SomeService/SomeKind",
154 "field": "selfLink",
155 },
156 },
157 },
158 },
159 },
160 {
161 name: "resourceLink is converted to resourceRef",
162 path: []string{"resourceLink"},
163 convertedFieldName: "resourceRef",
164 schema: &openapi.Schema{
165 Type: "string",
166 Extension: map[string]interface{}{
167 "x-dcl-references": []interface{}{
168 map[interface{}]interface{}{
169 "resource": "SomeService/SomeKind",
170 "field": "selfLink",
171 },
172 },
173 },
174 },
175 },
176 {
177 name: "nested resourceLink is converted to resourceRef",
178 path: []string{"object", "resourceLink"},
179 convertedFieldName: "resourceRef",
180 schema: &openapi.Schema{
181 Type: "string",
182 Extension: map[string]interface{}{
183 "x-dcl-references": []interface{}{
184 map[interface{}]interface{}{
185 "resource": "SomeService/SomeKind",
186 "field": "selfLink",
187 },
188 },
189 },
190 },
191 },
192 {
193 name: "field name is preserved if the field is an array of references",
194 path: []string{"networks"},
195 convertedFieldName: "networks",
196 schema: &openapi.Schema{
197 Type: "array",
198 Items: &openapi.Schema{
199 Type: "string",
200 Extension: map[string]interface{}{
201 "x-dcl-references": []interface{}{
202 map[interface{}]interface{}{
203 "resource": "SomeService/SomeKind",
204 "field": "selfLink",
205 },
206 },
207 },
208 },
209 },
210 },
211 {
212 name: "nested field name is preserved if the field is an array of references",
213 path: []string{"object", "networks"},
214 convertedFieldName: "networks",
215 schema: &openapi.Schema{
216 Type: "array",
217 Items: &openapi.Schema{
218 Type: "string",
219 Extension: map[string]interface{}{
220 "x-dcl-references": []interface{}{
221 map[interface{}]interface{}{
222 "resource": "SomeService/SomeKind",
223 "field": "selfLink",
224 },
225 },
226 },
227 },
228 },
229 },
230 {
231 name: "field name is converted if the field can take the reference of different resource kinds",
232 path: []string{"group"},
233 convertedFieldName: "groupRef",
234 schema: &openapi.Schema{
235 Type: "string",
236 Extension: map[string]interface{}{
237 "x-dcl-references": []interface{}{
238 map[interface{}]interface{}{
239 "resource": "SomeService/SomeKind",
240 "field": "selfLink",
241 },
242 map[interface{}]interface{}{
243 "resource": "SomeOtherService/SomeOtherKind",
244 "field": "selfLink",
245 },
246 },
247 },
248 },
249 },
250 {
251 name: "nested field name is converted if the field can take the reference of different resource kinds",
252 path: []string{"object", "group"},
253 convertedFieldName: "groupRef",
254 schema: &openapi.Schema{
255 Type: "string",
256 Extension: map[string]interface{}{
257 "x-dcl-references": []interface{}{
258 map[interface{}]interface{}{
259 "resource": "SomeService/SomeKind",
260 "field": "selfLink",
261 },
262 map[interface{}]interface{}{
263 "resource": "SomeOtherService/SomeOtherKind",
264 "field": "selfLink",
265 },
266 },
267 },
268 },
269 },
270 {
271 name: "cannot get reference field name for top-level parent field",
272 path: []string{"parent"},
273 hasError: true,
274 schema: &openapi.Schema{
275 Type: "string",
276 Extension: map[string]interface{}{
277 "x-dcl-references": []interface{}{
278 map[interface{}]interface{}{
279 "resource": "Cloudresourcemanager/Project",
280 "field": "name",
281 "parent": true,
282 },
283 map[interface{}]interface{}{
284 "resource": "Cloudresourcemanager/Folder",
285 "field": "name",
286 "parent": true,
287 },
288 map[interface{}]interface{}{
289 "resource": "Cloudresourcemanager/Organization",
290 "field": "name",
291 "parent": true,
292 },
293 },
294 },
295 },
296 },
297 {
298 name: "field name is updated for nested parent field that can take the reference of different resource kinds",
299 path: []string{"object", "parent"},
300 convertedFieldName: "parentRef",
301 schema: &openapi.Schema{
302 Type: "string",
303 Extension: map[string]interface{}{
304 "x-dcl-references": []interface{}{
305 map[interface{}]interface{}{
306 "resource": "Cloudresourcemanager/Project",
307 "field": "name",
308 "parent": true,
309 },
310 map[interface{}]interface{}{
311 "resource": "Cloudresourcemanager/Folder",
312 "field": "name",
313 "parent": true,
314 },
315 map[interface{}]interface{}{
316 "resource": "Cloudresourcemanager/Organization",
317 "field": "name",
318 "parent": true,
319 },
320 },
321 },
322 },
323 },
324 {
325 name: "append Ref to nested parent field that can take the reference of only one kind",
326 path: []string{"object", "parent"},
327 convertedFieldName: "parentRef",
328 schema: &openapi.Schema{
329 Type: "string",
330 Extension: map[string]interface{}{
331 "x-dcl-references": []interface{}{
332 map[interface{}]interface{}{
333 "resource": "Cloudresourcemanager/Project",
334 "field": "name",
335 "parent": true,
336 },
337 },
338 },
339 },
340 },
341 }
342 for _, tc := range tests {
343 tc := tc
344 t.Run(tc.name, func(t *testing.T) {
345 t.Parallel()
346 if !extension.IsReferenceField(tc.schema) {
347 t.Fatalf("expect the field to be a reference field")
348 }
349 actual, err := extension.GetReferenceFieldName(tc.path, tc.schema)
350 if tc.hasError {
351 if err == nil {
352 t.Fatalf("got no error, wanted one")
353 }
354 return
355 }
356 if err != nil {
357 t.Fatalf("unexpected error: %v", err)
358 }
359 if actual != tc.convertedFieldName {
360 t.Fatalf("got the converted reference field name as %v, want %v", actual, tc.convertedFieldName)
361 }
362 })
363 }
364 }
365
366 func TestIsResourceIDFieldServerGenerated(t *testing.T) {
367 tests := []struct {
368 name string
369 schema *openapi.Schema
370 expectedResult bool
371 }{
372 {
373 name: "missing 'x-dcl-server-generated-parameter' extension is treated as user-specified name",
374 schema: &openapi.Schema{
375 Type: "string",
376 },
377 expectedResult: false,
378 },
379 {
380 name: "server-generated id",
381 schema: &openapi.Schema{
382 Type: "string",
383 Extension: map[string]interface{}{
384 "x-dcl-server-generated-parameter": true,
385 },
386 },
387 expectedResult: true,
388 },
389 {
390 name: "the name field is specifically marked as non-server-generated",
391 schema: &openapi.Schema{
392 Type: "string",
393 Extension: map[string]interface{}{
394 "x-dcl-server-generated-parameter": false,
395 },
396 },
397 expectedResult: false,
398 },
399 }
400
401 for _, tc := range tests {
402 tc := tc
403 t.Run(tc.name, func(t *testing.T) {
404 t.Parallel()
405 actual, err := extension.IsResourceIDFieldServerGenerated(tc.schema)
406 if err != nil {
407 t.Fatalf("unexpected error: %v", err)
408 }
409 if actual != tc.expectedResult {
410 t.Fatalf("got the result as %v, want %v", actual, tc.expectedResult)
411 }
412 })
413 }
414 }
415
416 func TestHasSensitiveFields(t *testing.T) {
417 tests := []struct {
418 name string
419 schema *openapi.Schema
420 expectedResult bool
421 hasError bool
422 }{
423 {
424 name: "has sensitive string field",
425 schema: &openapi.Schema{
426 Type: "string",
427 Extension: map[string]interface{}{
428 "x-dcl-sensitive": true,
429 },
430 },
431 expectedResult: true,
432 },
433 {
434 name: "has sensitive string field in string array",
435 schema: &openapi.Schema{
436 Type: "array",
437 Items: &openapi.Schema{
438 Type: "string",
439 Extension: map[string]interface{}{
440 "x-dcl-sensitive": true,
441 },
442 },
443 },
444 expectedResult: true,
445 },
446 {
447 name: "has sensitive string field in object array",
448 schema: &openapi.Schema{
449 Type: "array",
450 Items: &openapi.Schema{
451 Type: "object",
452 Properties: map[string]*openapi.Schema{
453 "foo": {
454 Type: "string",
455 Extension: map[string]interface{}{
456 "x-dcl-sensitive": true,
457 },
458 },
459 },
460 },
461 },
462 expectedResult: true,
463 },
464 {
465 name: "has sensitive string field in object",
466 schema: &openapi.Schema{
467 Type: "object",
468 Properties: map[string]*openapi.Schema{
469 "foo": {
470 Type: "string",
471 Extension: map[string]interface{}{
472 "x-dcl-sensitive": true,
473 },
474 },
475 },
476 },
477 expectedResult: true,
478 },
479 {
480 name: "has sensitive string field in string map",
481 schema: &openapi.Schema{
482 Type: "object",
483 AdditionalProperties: &openapi.Schema{
484 Type: "string",
485 Extension: map[string]interface{}{
486 "x-dcl-sensitive": true,
487 },
488 },
489 },
490 expectedResult: true,
491 },
492 {
493 name: "has sensitive string field in object map",
494 schema: &openapi.Schema{
495 Type: "object",
496 AdditionalProperties: &openapi.Schema{
497 Type: "object",
498 Properties: map[string]*openapi.Schema{
499 "foo": {
500 Type: "string",
501 Extension: map[string]interface{}{
502 "x-dcl-sensitive": true,
503 },
504 },
505 },
506 },
507 },
508 expectedResult: true,
509 },
510 {
511 name: "has sensitive number field",
512 schema: &openapi.Schema{
513 Type: "number",
514 Extension: map[string]interface{}{
515 "x-dcl-sensitive": true,
516 },
517 },
518 hasError: true,
519 },
520 {
521 name: "has sensitive array field",
522 schema: &openapi.Schema{
523 Type: "array",
524 Items: &openapi.Schema{
525 Type: "string",
526 },
527 Extension: map[string]interface{}{
528 "x-dcl-sensitive": true,
529 },
530 },
531 hasError: true,
532 },
533 {
534 name: "has sensitive object field",
535 schema: &openapi.Schema{
536 Type: "object",
537 Properties: map[string]*openapi.Schema{
538 "foo": {
539 Type: "string",
540 },
541 },
542 Extension: map[string]interface{}{
543 "x-dcl-sensitive": true,
544 },
545 },
546 hasError: true,
547 },
548 {
549 name: "has no sensitive field",
550 schema: &openapi.Schema{
551 Type: "string",
552 },
553 expectedResult: false,
554 },
555 }
556
557 for _, tc := range tests {
558 tc := tc
559 t.Run(tc.name, func(t *testing.T) {
560 t.Parallel()
561 result, err := extension.HasSensitiveFields(tc.schema)
562 if tc.hasError {
563 if err == nil {
564 t.Fatalf("got no error, but want an error")
565 }
566 return
567 }
568 if err != nil {
569 t.Fatalf("unexpected error: %v", err)
570 }
571 if got, want := result, tc.expectedResult; got != want {
572 t.Fatalf("got %v, want %v", got, want)
573 }
574 })
575 }
576 }
577
578 func TestHasStateHint(t *testing.T) {
579 tests := []struct {
580 name string
581 schema *openapi.Schema
582 expectedResult bool
583 hasError bool
584 }{
585 {
586 name: "has state hint",
587 schema: &openapi.Schema{
588 Type: "object",
589 Extension: map[string]interface{}{
590 "x-dcl-uses-state-hint": true,
591 },
592 },
593 expectedResult: true,
594 },
595 {
596 name: "has no state hint",
597 schema: &openapi.Schema{
598 Type: "object",
599 },
600 expectedResult: false,
601 },
602 {
603 name: "wrong type for x-dcl-uses-state-hint extension",
604 schema: &openapi.Schema{
605 Type: "object",
606 Extension: map[string]interface{}{
607 "x-dcl-uses-state-hint": "true",
608 },
609 },
610 hasError: true,
611 },
612 }
613
614 for _, tc := range tests {
615 tc := tc
616 t.Run(tc.name, func(t *testing.T) {
617 t.Parallel()
618 result, err := extension.HasStateHint(tc.schema)
619 if tc.hasError {
620 if err == nil {
621 t.Fatal("got no error, but want an error")
622 }
623 return
624 }
625 if err != nil {
626 t.Fatalf("unexpected error: %v", err)
627 }
628 if got, want := result, tc.expectedResult; got != want {
629 t.Fatalf("got %v, want %v", got, want)
630 }
631 })
632 }
633 }
634
635 func TestIsMutableButUnreadableField(t *testing.T) {
636 tests := []struct {
637 name string
638 schema *openapi.Schema
639 expectedResult bool
640 hasError bool
641 }{
642 {
643 name: "mutable-but-unreadable field",
644 schema: &openapi.Schema{
645 Type: "string",
646 Extension: map[string]interface{}{
647 "x-dcl-mutable-unreadable": true,
648 },
649 },
650 expectedResult: true,
651 },
652 {
653 name: "mutable and readable field",
654 schema: &openapi.Schema{
655 Type: "string",
656 },
657 expectedResult: false,
658 },
659 {
660 name: "immutable and unreadable field",
661 schema: &openapi.Schema{
662 Type: "string",
663 Extension: map[string]interface{}{
664 "x-dcl-mutable-unreadable": true,
665 "x-kubernetes-immutable": true,
666 },
667 },
668 expectedResult: false,
669 },
670 {
671 name: "wrong type for x-dcl-mutable-unreadable extension",
672 schema: &openapi.Schema{
673 Type: "string",
674 Extension: map[string]interface{}{
675 "x-dcl-mutable-unreadable": "true",
676 },
677 },
678 hasError: true,
679 },
680 }
681
682 for _, tc := range tests {
683 tc := tc
684 t.Run(tc.name, func(t *testing.T) {
685 t.Parallel()
686 result, err := extension.IsMutableButUnreadableField(tc.schema)
687 if tc.hasError {
688 if err == nil {
689 t.Fatal("got no error, but want an error")
690 }
691 return
692 }
693 if err != nil {
694 t.Fatalf("unexpected error: %v", err)
695 }
696 if got, want := result, tc.expectedResult; got != want {
697 t.Fatalf("got %v, want %v", got, want)
698 }
699 })
700 }
701 }
702
703 func TestHasIam(t *testing.T) {
704 tests := []struct {
705 name string
706 schema *openapi.Schema
707 expectedResult bool
708 hasError bool
709 }{
710 {
711 name: "has iam policy",
712 schema: &openapi.Schema{
713 Extension: map[string]interface{}{
714 "x-dcl-has-iam": true,
715 },
716 },
717 expectedResult: true,
718 },
719 {
720 name: "has no iam policy",
721 schema: &openapi.Schema{
722 Extension: map[string]interface{}{
723 "x-dcl-has-iam": false,
724 },
725 },
726 expectedResult: false,
727 },
728 {
729 name: "wrong type for x-dcl-has-iam extension",
730 schema: &openapi.Schema{
731 Extension: map[string]interface{}{
732 "x-dcl-has-iam": "true",
733 },
734 },
735 expectedResult: false,
736 hasError: true,
737 },
738 }
739
740 for _, tc := range tests {
741 tc := tc
742 t.Run(tc.name, func(t *testing.T) {
743 t.Parallel()
744 result, err := extension.HasIam(tc.schema)
745 if tc.hasError {
746 if err == nil {
747 t.Fatal("got no error, but want an error")
748 }
749 return
750 }
751 if err != nil {
752 t.Fatalf("unexpected error: %v", err)
753 }
754 if got, want := result, tc.expectedResult; got != want {
755 t.Fatalf("got %v, want %v", got, want)
756 }
757 })
758 }
759 }
760
View as plain text