...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/extension/extension_test.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/extension

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    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