1
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"),
78 invalid("metadata", "labels"),
79 invalid("metadata", "annotations"),
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