...

Source file src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/validation_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  	"testing"
    21  
    22  	structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
    23  	"k8s.io/apimachinery/pkg/util/json"
    24  	"k8s.io/apimachinery/pkg/util/validation/field"
    25  )
    26  
    27  func TestValidateEmbeddedResource(t *testing.T) {
    28  	tests := []struct {
    29  		name   string
    30  		object map[string]interface{}
    31  		errors []validationMatch
    32  	}{
    33  		{name: "empty", object: map[string]interface{}{}, errors: []validationMatch{
    34  			required("apiVersion"),
    35  			required("kind"),
    36  		}},
    37  		{name: "version and kind", object: map[string]interface{}{
    38  			"apiVersion": "foo/v1",
    39  			"kind":       "Foo",
    40  		}},
    41  		{name: "invalid kind", object: map[string]interface{}{
    42  			"apiVersion": "foo/v1",
    43  			"kind":       "foo.bar-com",
    44  		}, errors: []validationMatch{
    45  			invalid("kind"),
    46  		}},
    47  		{name: "no name", object: map[string]interface{}{
    48  			"apiVersion": "foo/v1",
    49  			"kind":       "Foo",
    50  			"metadata": map[string]interface{}{
    51  				"namespace": "kube-system",
    52  			},
    53  		}},
    54  		{name: "no namespace", object: map[string]interface{}{
    55  			"apiVersion": "foo/v1",
    56  			"kind":       "Foo",
    57  			"metadata": map[string]interface{}{
    58  				"name": "foo",
    59  			},
    60  		}},
    61  		{name: "invalid", object: map[string]interface{}{
    62  			"apiVersion": "foo/v1",
    63  			"kind":       "Foo",
    64  			"metadata": map[string]interface{}{
    65  				"name":      "..",
    66  				"namespace": "$$$",
    67  				"labels": map[string]interface{}{
    68  					"#": "#",
    69  				},
    70  				"annotations": map[string]interface{}{
    71  					"#": "#",
    72  				},
    73  			},
    74  		}, errors: []validationMatch{
    75  			invalid("metadata", "name"),
    76  			invalid("metadata", "namespace"),
    77  			invalid("metadata", "labels"),      // key
    78  			invalid("metadata", "labels"),      // value
    79  			invalid("metadata", "annotations"), // key
    80  		}},
    81  	}
    82  	for _, tt := range tests {
    83  		t.Run(tt.name, func(t *testing.T) {
    84  			schema := &structuralschema.Structural{Extensions: structuralschema.Extensions{XEmbeddedResource: true}}
    85  			errs := validateEmbeddedResource(nil, tt.object, schema)
    86  			seenErrs := make([]bool, len(errs))
    87  
    88  			for _, expectedError := range tt.errors {
    89  				found := false
    90  				for i, err := range errs {
    91  					if expectedError.matches(err) && !seenErrs[i] {
    92  						found = true
    93  						seenErrs[i] = true
    94  						break
    95  					}
    96  				}
    97  
    98  				if !found {
    99  					t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), errs)
   100  				}
   101  			}
   102  
   103  			for i, seen := range seenErrs {
   104  				if !seen {
   105  					t.Errorf("unexpected error: %v", errs[i])
   106  				}
   107  			}
   108  		})
   109  	}
   110  }
   111  
   112  func TestValidate(t *testing.T) {
   113  	tests := []struct {
   114  		name        string
   115  		object      string
   116  		includeRoot bool
   117  		errors      []validationMatch
   118  	}{
   119  		{name: "empty", object: `{}`, errors: []validationMatch{}},
   120  		{name: "include root", object: `{}`, includeRoot: true, errors: []validationMatch{
   121  			required("apiVersion"),
   122  			required("kind"),
   123  		}},
   124  		{name: "embedded", object: `
   125  {
   126    "embedded": {}
   127  }`, errors: []validationMatch{
   128  			required("embedded", "apiVersion"),
   129  			required("embedded", "kind"),
   130  		}},
   131  		{name: "nested", object: `
   132  {
   133    "nested": {
   134      "embedded": {}
   135    }
   136  }`, errors: []validationMatch{
   137  			required("nested", "apiVersion"),
   138  			required("nested", "kind"),
   139  			required("nested", "embedded", "apiVersion"),
   140  			required("nested", "embedded", "kind"),
   141  		}},
   142  		{name: "items", object: `
   143  {
   144    "items": [{}]
   145  }`, errors: []validationMatch{
   146  			required("items[0]", "apiVersion"),
   147  			required("items[0]", "kind"),
   148  		}},
   149  		{name: "additionalProperties", object: `
   150  {
   151    "additionalProperties": {"foo":{}}
   152  }`, errors: []validationMatch{
   153  			required("additionalProperties[foo]", "apiVersion"),
   154  			required("additionalProperties[foo]", "kind"),
   155  		}},
   156  	}
   157  	for _, tt := range tests {
   158  		t.Run(tt.name, func(t *testing.T) {
   159  			schema := &structuralschema.Structural{
   160  				Properties: map[string]structuralschema.Structural{
   161  					"embedded": {Extensions: structuralschema.Extensions{XEmbeddedResource: true}},
   162  					"nested": {
   163  						Extensions: structuralschema.Extensions{XEmbeddedResource: true},
   164  						Properties: map[string]structuralschema.Structural{
   165  							"embedded": {Extensions: structuralschema.Extensions{XEmbeddedResource: true}},
   166  						},
   167  					},
   168  					"items": {
   169  						Items: &structuralschema.Structural{
   170  							Extensions: structuralschema.Extensions{XEmbeddedResource: true},
   171  						},
   172  					},
   173  					"additionalProperties": {
   174  						Generic: structuralschema.Generic{
   175  							AdditionalProperties: &structuralschema.StructuralOrBool{
   176  								Structural: &structuralschema.Structural{
   177  									Extensions: structuralschema.Extensions{XEmbeddedResource: true},
   178  								},
   179  							},
   180  						},
   181  					},
   182  				},
   183  			}
   184  
   185  			var obj map[string]interface{}
   186  			if err := json.Unmarshal([]byte(tt.object), &obj); err != nil {
   187  				t.Fatal(err)
   188  			}
   189  
   190  			errs := Validate(nil, obj, schema, tt.includeRoot)
   191  			seenErrs := make([]bool, len(errs))
   192  
   193  			for _, expectedError := range tt.errors {
   194  				found := false
   195  				for i, err := range errs {
   196  					if expectedError.matches(err) && !seenErrs[i] {
   197  						found = true
   198  						seenErrs[i] = true
   199  						break
   200  					}
   201  				}
   202  
   203  				if !found {
   204  					t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), errs)
   205  				}
   206  			}
   207  
   208  			for i, seen := range seenErrs {
   209  				if !seen {
   210  					t.Errorf("unexpected error: %v", errs[i])
   211  				}
   212  			}
   213  		})
   214  	}
   215  }
   216  
   217  type validationMatch struct {
   218  	path      *field.Path
   219  	errorType field.ErrorType
   220  }
   221  
   222  func required(path ...string) validationMatch {
   223  	return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeRequired}
   224  }
   225  func invalid(path ...string) validationMatch {
   226  	return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeInvalid}
   227  }
   228  
   229  func (v validationMatch) matches(err *field.Error) bool {
   230  	return err.Type == v.errorType && err.Field == v.path.String()
   231  }
   232  

View as plain text