...

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

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

     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 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