...

Source file src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/algorithm_test.go

Documentation: k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package objectmeta
    18  
    19  import (
    20  	"bytes"
    21  	"reflect"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  
    27  	"k8s.io/apimachinery/pkg/util/json"
    28  
    29  	structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
    30  )
    31  
    32  func TestCoerce(t *testing.T) {
    33  	tests := []struct {
    34  		name                  string
    35  		json                  string
    36  		includeRoot           bool
    37  		dropInvalidFields     bool
    38  		schema                *structuralschema.Structural
    39  		expected              string
    40  		expectedError         bool
    41  		expectedUnknownFields []string
    42  	}{
    43  		{name: "empty", json: "null", schema: nil, expected: "null"},
    44  		{name: "scalar", json: "4", schema: &structuralschema.Structural{}, expected: "4"},
    45  		{name: "scalar array", json: "[1,2]", schema: &structuralschema.Structural{
    46  			Items: &structuralschema.Structural{},
    47  		}, expected: "[1,2]"},
    48  		{name: "x-kubernetes-embedded-resource", json: `
    49  {
    50    "apiVersion": "foo/v1",
    51    "kind": "Foo",
    52    "metadata": {
    53      "name": "instance",
    54      "unspecified": "bar"
    55    },
    56    "unspecified":"bar",
    57    "pruned": {
    58      "apiVersion": "foo/v1",
    59      "kind": "Foo",
    60      "unspecified": "bar",
    61      "metadata": {
    62        "name": "instance",
    63        "unspecified": "bar"
    64      },
    65      "spec": {
    66        "unspecified": "bar"
    67      }
    68    },
    69    "preserving": {
    70      "apiVersion": "foo/v1",
    71      "kind": "Foo",
    72      "unspecified": "bar",
    73      "metadata": {
    74        "name": "instance",
    75        "unspecified": "bar"
    76      },
    77      "spec": {
    78        "unspecified": "bar"
    79      }
    80    },
    81    "nested": {
    82      "apiVersion": "foo/v1",
    83      "kind": "Foo",
    84      "unspecified": "bar",
    85      "metadata": {
    86        "name": "instance",
    87        "unspecified": "bar"
    88      },
    89      "spec": {
    90        "unspecified": "bar",
    91        "embedded": {
    92          "apiVersion": "foo/v1",
    93          "kind": "Foo",
    94          "unspecified": "bar",
    95          "metadata": {
    96            "name": "instance",
    97            "unspecified": "bar"
    98          },
    99          "spec": {
   100            "unspecified": "bar"
   101          }
   102        }
   103      }
   104    }
   105  }
   106  `, schema: &structuralschema.Structural{
   107  			Generic: structuralschema.Generic{Type: "object"},
   108  			Properties: map[string]structuralschema.Structural{
   109  				"pruned": {
   110  					Generic: structuralschema.Generic{Type: "object"},
   111  					Extensions: structuralschema.Extensions{
   112  						XEmbeddedResource: true,
   113  					},
   114  					Properties: map[string]structuralschema.Structural{
   115  						"spec": {
   116  							Generic: structuralschema.Generic{Type: "object"},
   117  						},
   118  					},
   119  				},
   120  				"preserving": {
   121  					Generic: structuralschema.Generic{Type: "object"},
   122  					Extensions: structuralschema.Extensions{
   123  						XEmbeddedResource:      true,
   124  						XPreserveUnknownFields: true,
   125  					},
   126  				},
   127  				"nested": {
   128  					Generic: structuralschema.Generic{Type: "object"},
   129  					Extensions: structuralschema.Extensions{
   130  						XEmbeddedResource: true,
   131  					},
   132  					Properties: map[string]structuralschema.Structural{
   133  						"spec": {
   134  							Generic: structuralschema.Generic{Type: "object"},
   135  							Properties: map[string]structuralschema.Structural{
   136  								"embedded": {
   137  									Generic: structuralschema.Generic{Type: "object"},
   138  									Extensions: structuralschema.Extensions{
   139  										XEmbeddedResource: true,
   140  									},
   141  									Properties: map[string]structuralschema.Structural{
   142  										"spec": {
   143  											Generic: structuralschema.Generic{Type: "object"},
   144  										},
   145  									},
   146  								},
   147  							},
   148  						},
   149  					},
   150  				},
   151  			},
   152  		}, expected: `
   153  {
   154    "apiVersion": "foo/v1",
   155    "kind": "Foo",
   156    "metadata": {
   157      "name": "instance",
   158      "unspecified": "bar"
   159    },
   160    "unspecified":"bar",
   161    "pruned": {
   162      "apiVersion": "foo/v1",
   163      "kind": "Foo",
   164      "unspecified": "bar",
   165      "metadata": {
   166        "name": "instance"
   167      },
   168      "spec": {
   169        "unspecified": "bar"
   170      }
   171    },
   172    "preserving": {
   173      "apiVersion": "foo/v1",
   174      "kind": "Foo",
   175      "unspecified": "bar",
   176      "metadata": {
   177        "name": "instance"
   178      },
   179      "spec": {
   180        "unspecified": "bar"
   181      }
   182    },
   183    "nested": {
   184      "apiVersion": "foo/v1",
   185      "kind": "Foo",
   186      "unspecified": "bar",
   187      "metadata": {
   188        "name": "instance"
   189      },
   190      "spec": {
   191        "unspecified": "bar",
   192        "embedded": {
   193          "apiVersion": "foo/v1",
   194          "kind": "Foo",
   195          "unspecified": "bar",
   196          "metadata": {
   197            "name": "instance"
   198          },
   199          "spec": {
   200            "unspecified": "bar"
   201          }
   202        }
   203      }
   204    }
   205  }
   206  `, expectedUnknownFields: []string{
   207  			"nested.metadata.unspecified",
   208  			"nested.spec.embedded.metadata.unspecified",
   209  			"preserving.metadata.unspecified",
   210  			"pruned.metadata.unspecified",
   211  		}},
   212  		{name: "x-kubernetes-embedded-resource, with includeRoot=true", json: `
   213  {
   214    "apiVersion": "foo/v1",
   215    "kind": "Foo",
   216    "metadata": {
   217      "name": "instance",
   218      "unspecified": "bar"
   219    },
   220    "unspecified":"bar",
   221    "pruned": {
   222      "apiVersion": "foo/v1",
   223      "kind": "Foo",
   224      "unspecified": "bar",
   225      "metadata": {
   226        "name": "instance",
   227        "unspecified": "bar"
   228      },
   229      "spec": {
   230        "unspecified": "bar"
   231      }
   232    },
   233    "preserving": {
   234      "apiVersion": "foo/v1",
   235      "kind": "Foo",
   236      "unspecified": "bar",
   237      "metadata": {
   238        "name": "instance",
   239        "unspecified": "bar"
   240      },
   241      "spec": {
   242        "unspecified": "bar"
   243      }
   244    },
   245    "nested": {
   246      "apiVersion": "foo/v1",
   247      "kind": "Foo",
   248      "unspecified": "bar",
   249      "metadata": {
   250        "name": "instance",
   251        "unspecified": "bar"
   252      },
   253      "spec": {
   254        "unspecified": "bar",
   255        "embedded": {
   256          "apiVersion": "foo/v1",
   257          "kind": "Foo",
   258          "unspecified": "bar",
   259          "metadata": {
   260            "name": "instance",
   261            "unspecified": "bar"
   262          },
   263          "spec": {
   264            "unspecified": "bar"
   265          }
   266        }
   267      }
   268    }
   269  }
   270  `, includeRoot: true, schema: &structuralschema.Structural{
   271  			Generic: structuralschema.Generic{Type: "object"},
   272  			Properties: map[string]structuralschema.Structural{
   273  				"pruned": {
   274  					Generic: structuralschema.Generic{Type: "object"},
   275  					Extensions: structuralschema.Extensions{
   276  						XEmbeddedResource: true,
   277  					},
   278  					Properties: map[string]structuralschema.Structural{
   279  						"spec": {
   280  							Generic: structuralschema.Generic{Type: "object"},
   281  						},
   282  					},
   283  				},
   284  				"preserving": {
   285  					Generic: structuralschema.Generic{Type: "object"},
   286  					Extensions: structuralschema.Extensions{
   287  						XEmbeddedResource:      true,
   288  						XPreserveUnknownFields: true,
   289  					},
   290  				},
   291  				"nested": {
   292  					Generic: structuralschema.Generic{Type: "object"},
   293  					Extensions: structuralschema.Extensions{
   294  						XEmbeddedResource: true,
   295  					},
   296  					Properties: map[string]structuralschema.Structural{
   297  						"spec": {
   298  							Generic: structuralschema.Generic{Type: "object"},
   299  							Properties: map[string]structuralschema.Structural{
   300  								"embedded": {
   301  									Generic: structuralschema.Generic{Type: "object"},
   302  									Extensions: structuralschema.Extensions{
   303  										XEmbeddedResource: true,
   304  									},
   305  									Properties: map[string]structuralschema.Structural{
   306  										"spec": {
   307  											Generic: structuralschema.Generic{Type: "object"},
   308  										},
   309  									},
   310  								},
   311  							},
   312  						},
   313  					},
   314  				},
   315  			},
   316  		}, expected: `
   317  {
   318    "apiVersion": "foo/v1",
   319    "kind": "Foo",
   320    "metadata": {
   321      "name": "instance"
   322    },
   323    "unspecified":"bar",
   324    "pruned": {
   325      "apiVersion": "foo/v1",
   326      "kind": "Foo",
   327      "unspecified": "bar",
   328      "metadata": {
   329        "name": "instance"
   330      },
   331      "spec": {
   332        "unspecified": "bar"
   333      }
   334    },
   335    "preserving": {
   336      "apiVersion": "foo/v1",
   337      "kind": "Foo",
   338      "unspecified": "bar",
   339      "metadata": {
   340        "name": "instance"
   341      },
   342      "spec": {
   343        "unspecified": "bar"
   344      }
   345    },
   346    "nested": {
   347      "apiVersion": "foo/v1",
   348      "kind": "Foo",
   349      "unspecified": "bar",
   350      "metadata": {
   351        "name": "instance"
   352      },
   353      "spec": {
   354        "unspecified": "bar",
   355        "embedded": {
   356          "apiVersion": "foo/v1",
   357          "kind": "Foo",
   358          "unspecified": "bar",
   359          "metadata": {
   360            "name": "instance"
   361          },
   362          "spec": {
   363            "unspecified": "bar"
   364          }
   365        }
   366      }
   367    }
   368  }`, expectedUnknownFields: []string{
   369  			"metadata.unspecified",
   370  			"nested.metadata.unspecified",
   371  			"nested.spec.embedded.metadata.unspecified",
   372  			"preserving.metadata.unspecified",
   373  			"pruned.metadata.unspecified",
   374  		}},
   375  		{name: "without name", json: `
   376  {
   377    "apiVersion": "foo/v1",
   378    "kind": "Foo",
   379    "metadata": {
   380      "name": "instance"
   381    },
   382    "pruned": {
   383      "apiVersion": "foo/v1",
   384      "kind": "Foo",
   385      "metadata": {
   386        "namespace": "kube-system"
   387      }
   388    }
   389  }
   390  `, schema: &structuralschema.Structural{
   391  			Generic: structuralschema.Generic{Type: "object"},
   392  			Properties: map[string]structuralschema.Structural{
   393  				"pruned": {
   394  					Generic: structuralschema.Generic{Type: "object"},
   395  					Extensions: structuralschema.Extensions{
   396  						XEmbeddedResource: true,
   397  					},
   398  				},
   399  			},
   400  		}, expected: `
   401  {
   402    "apiVersion": "foo/v1",
   403    "kind": "Foo",
   404    "metadata": {
   405      "name": "instance"
   406    },
   407    "pruned": {
   408      "apiVersion": "foo/v1",
   409      "kind": "Foo",
   410      "metadata": {
   411        "namespace": "kube-system"
   412      }
   413    }
   414  }
   415  `},
   416  		{name: "x-kubernetes-embedded-resource, with dropInvalidFields=true", json: `
   417  {
   418    "apiVersion": "foo/v1",
   419    "kind": "Foo",
   420    "metadata": {
   421      "name": "instance"
   422    },
   423    "pruned": {
   424      "apiVersion": 42,
   425      "kind": 42,
   426      "metadata": {
   427        "name": "instance",
   428  	  "namespace": ["abc"],
   429        "labels": {
   430          "foo": 42
   431        }
   432      }
   433    }
   434  }
   435  `, dropInvalidFields: true, schema: &structuralschema.Structural{
   436  			Generic: structuralschema.Generic{Type: "object"},
   437  			Properties: map[string]structuralschema.Structural{
   438  				"pruned": {
   439  					Generic: structuralschema.Generic{Type: "object"},
   440  					Extensions: structuralschema.Extensions{
   441  						XEmbeddedResource: true,
   442  					},
   443  					Properties: map[string]structuralschema.Structural{
   444  						"spec": {
   445  							Generic: structuralschema.Generic{Type: "object"},
   446  						},
   447  					},
   448  				},
   449  			},
   450  		}, expected: `
   451  {
   452    "apiVersion": "foo/v1",
   453    "kind": "Foo",
   454    "metadata": {
   455      "name": "instance"
   456    },
   457    "pruned": {
   458      "metadata": {
   459        "name": "instance"
   460      }
   461    }
   462  }
   463  `},
   464  		{name: "invalid metadata type, with dropInvalidFields=true", json: `
   465  {
   466    "apiVersion": "foo/v1",
   467    "kind": "Foo",
   468    "metadata": {
   469      "name": "instance"
   470    },
   471    "pruned": {
   472      "apiVersion": 42,
   473      "kind": 42,
   474      "metadata": [42]
   475    }
   476  }
   477  `, dropInvalidFields: true, schema: &structuralschema.Structural{
   478  			Generic: structuralschema.Generic{Type: "object"},
   479  			Properties: map[string]structuralschema.Structural{
   480  				"pruned": {
   481  					Generic: structuralschema.Generic{Type: "object"},
   482  					Extensions: structuralschema.Extensions{
   483  						XEmbeddedResource: true,
   484  					},
   485  				},
   486  			},
   487  		}, expected: `
   488  {
   489    "apiVersion": "foo/v1",
   490    "kind": "Foo",
   491    "metadata": {
   492      "name": "instance"
   493    },
   494    "pruned": {
   495      "metadata": [42]
   496    }
   497  }
   498  `},
   499  	}
   500  	for _, tt := range tests {
   501  		t.Run(tt.name, func(t *testing.T) {
   502  			var in interface{}
   503  			if err := json.Unmarshal([]byte(tt.json), &in); err != nil {
   504  				t.Fatal(err)
   505  			}
   506  
   507  			var expected interface{}
   508  			if err := json.Unmarshal([]byte(tt.expected), &expected); err != nil {
   509  				t.Fatal(err)
   510  			}
   511  
   512  			err, unknownFields := CoerceWithOptions(nil, in, tt.schema, tt.includeRoot, CoerceOptions{
   513  				DropInvalidFields:       tt.dropInvalidFields,
   514  				ReturnUnknownFieldPaths: true,
   515  			})
   516  			if tt.expectedError && err == nil {
   517  				t.Error("expected error, but did not get any")
   518  			} else if !tt.expectedError && err != nil {
   519  				t.Errorf("expected no error, but got: %v", err)
   520  			} else if !reflect.DeepEqual(in, expected) {
   521  				var buf bytes.Buffer
   522  				enc := json.NewEncoder(&buf)
   523  				enc.SetIndent("", "  ")
   524  				err := enc.Encode(in)
   525  				if err != nil {
   526  					t.Fatalf("unexpected result mashalling error: %v", err)
   527  				}
   528  				t.Errorf("expected: %s\ngot: %s\ndiff: %s", tt.expected, buf.String(), cmp.Diff(expected, in))
   529  			}
   530  			if !reflect.DeepEqual(unknownFields, tt.expectedUnknownFields) {
   531  				t.Errorf("expected unknown fields:\n\t%v\ngot:\n\t%v\n", strings.Join(tt.expectedUnknownFields, "\n\t"), strings.Join(unknownFields, "\n\t"))
   532  			}
   533  		})
   534  	}
   535  }
   536  

View as plain text