1
16
17 package model
18
19 import (
20 "reflect"
21 "testing"
22
23 "github.com/google/cel-go/common/types"
24
25 "google.golang.org/protobuf/proto"
26
27 "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
28 apiservercel "k8s.io/apiserver/pkg/cel"
29 )
30
31 func TestSchemaDeclType(t *testing.T) {
32 ts := testSchema()
33 cust := SchemaDeclType(ts, false)
34 if cust.TypeName() != "object" {
35 t.Errorf("incorrect type name, got %v, wanted object", cust.TypeName())
36 }
37 if len(cust.Fields) != 4 {
38 t.Errorf("incorrect number of fields, got %d, wanted 4", len(cust.Fields))
39 }
40 for _, f := range cust.Fields {
41 prop, found := ts.Properties[f.Name]
42 if !found {
43 t.Errorf("type field not found in schema, field: %s", f.Name)
44 }
45 fdv := f.DefaultValue()
46 if prop.Default.Object != nil {
47 pdv := types.DefaultTypeAdapter.NativeToValue(prop.Default.Object)
48 if !reflect.DeepEqual(fdv, pdv) {
49 t.Errorf("field and schema do not agree on default value for field: %s, field value: %v, schema default: %v", f.Name, fdv, pdv)
50 }
51 }
52 if (prop.ValueValidation == nil || len(prop.ValueValidation.Enum) == 0) && len(f.EnumValues()) != 0 {
53 t.Errorf("field had more enum values than the property. field: %s", f.Name)
54 }
55 if prop.ValueValidation != nil {
56 fevs := f.EnumValues()
57 for _, fev := range fevs {
58 found := false
59 for _, pev := range prop.ValueValidation.Enum {
60 celpev := types.DefaultTypeAdapter.NativeToValue(pev.Object)
61 if reflect.DeepEqual(fev, celpev) {
62 found = true
63 break
64 }
65 }
66 if !found {
67 t.Errorf(
68 "could not find field enum value in property definition. field: %s, enum: %v",
69 f.Name, fev)
70 }
71 }
72 }
73 }
74 if ts.ValueValidation != nil {
75 for _, name := range ts.ValueValidation.Required {
76 df, found := cust.FindField(name)
77 if !found {
78 t.Errorf("custom type missing required field. field=%s", name)
79 }
80 if !df.Required {
81 t.Errorf("field marked as required in schema, but optional in type. field=%s", df.Name)
82 }
83 }
84 }
85 }
86
87 func TestSchemaDeclTypes(t *testing.T) {
88 ts := testSchema()
89 cust := SchemaDeclType(ts, true).MaybeAssignTypeName("CustomObject")
90 typeMap := apiservercel.FieldTypeMap("CustomObject", cust)
91 nested, _ := cust.FindField("nested")
92 metadata, _ := cust.FindField("metadata")
93 expectedObjTypeMap := map[string]*apiservercel.DeclType{
94 "CustomObject": cust,
95 "CustomObject.nested": nested.Type,
96 "CustomObject.metadata": metadata.Type,
97 }
98 objTypeMap := map[string]*apiservercel.DeclType{}
99 for name, t := range typeMap {
100 if t.IsObject() {
101 objTypeMap[name] = t
102 }
103 }
104 if len(objTypeMap) != len(expectedObjTypeMap) {
105 t.Errorf("got different type set. got=%v, wanted=%v", objTypeMap, expectedObjTypeMap)
106 }
107 for exp, expType := range expectedObjTypeMap {
108 actType, found := objTypeMap[exp]
109 if !found {
110 t.Errorf("missing type in rule types: %s", exp)
111 continue
112 }
113 expT, err := expType.ExprType()
114 if err != nil {
115 t.Errorf("fail to get cel type: %s", err)
116 }
117 actT, err := actType.ExprType()
118 if err != nil {
119 t.Errorf("fail to get cel type: %s", err)
120 }
121 if !proto.Equal(expT, actT) {
122 t.Errorf("incompatible CEL types. got=%v, wanted=%v", expT, actT)
123 }
124 }
125 }
126
127 func testSchema() *schema.Structural {
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169 ts := &schema.Structural{
170 Generic: schema.Generic{
171 Type: "object",
172 },
173 Properties: map[string]schema.Structural{
174 "name": {
175 Generic: schema.Generic{
176 Type: "string",
177 },
178 },
179 "value": {
180 Generic: schema.Generic{
181 Type: "integer",
182 Default: schema.JSON{Object: int64(1)},
183 },
184 ValueValidation: &schema.ValueValidation{
185 Format: "int64",
186 Enum: []schema.JSON{{Object: int64(1)}, {Object: int64(2)}, {Object: int64(3)}},
187 },
188 },
189 "nested": {
190 Generic: schema.Generic{
191 Type: "object",
192 },
193 Properties: map[string]schema.Structural{
194 "subname": {
195 Generic: schema.Generic{
196 Type: "string",
197 },
198 },
199 "flags": {
200 Generic: schema.Generic{
201 Type: "object",
202 AdditionalProperties: &schema.StructuralOrBool{
203 Structural: &schema.Structural{
204 Generic: schema.Generic{
205 Type: "boolean",
206 },
207 },
208 },
209 },
210 },
211 "dates": {
212 Generic: schema.Generic{
213 Type: "array",
214 },
215 Items: &schema.Structural{
216 Generic: schema.Generic{
217 Type: "string",
218 },
219 ValueValidation: &schema.ValueValidation{
220 Format: "date-time",
221 },
222 },
223 },
224 },
225 },
226 "metadata": {
227 Generic: schema.Generic{
228 Type: "object",
229 },
230 Properties: map[string]schema.Structural{
231 "name": {
232 Generic: schema.Generic{
233 Type: "string",
234 },
235 },
236 "value": {
237 Generic: schema.Generic{
238 Type: "array",
239 },
240 Items: &schema.Structural{
241 Generic: schema.Generic{
242 Type: "string",
243 },
244 },
245 },
246 },
247 },
248 },
249 }
250 return ts
251 }
252
253 func arraySchema(arrayType, format string, maxItems *int64) *schema.Structural {
254 return &schema.Structural{
255 Generic: schema.Generic{
256 Type: "array",
257 },
258 Items: &schema.Structural{
259 Generic: schema.Generic{
260 Type: arrayType,
261 },
262 ValueValidation: &schema.ValueValidation{
263 Format: format,
264 },
265 },
266 ValueValidation: &schema.ValueValidation{
267 MaxItems: maxItems,
268 },
269 }
270 }
271
272 func TestEstimateMaxLengthJSON(t *testing.T) {
273 type maxLengthTest struct {
274 Name string
275 InputSchema *schema.Structural
276 ExpectedMaxElements int64
277 }
278 tests := []maxLengthTest{
279 {
280 Name: "booleanArray",
281 InputSchema: arraySchema("boolean", "", nil),
282
283 ExpectedMaxElements: 629145,
284 },
285 {
286 Name: "durationArray",
287 InputSchema: arraySchema("string", "duration", nil),
288
289 ExpectedMaxElements: 786431,
290 },
291 {
292 Name: "datetimeArray",
293 InputSchema: arraySchema("string", "date-time", nil),
294
295 ExpectedMaxElements: 142987,
296 },
297 {
298 Name: "dateArray",
299 InputSchema: arraySchema("string", "date", nil),
300
301 ExpectedMaxElements: 241978,
302 },
303 {
304 Name: "numberArray",
305 InputSchema: arraySchema("integer", "", nil),
306
307 ExpectedMaxElements: 1572863,
308 },
309 {
310 Name: "stringArray",
311 InputSchema: arraySchema("string", "", nil),
312
313 ExpectedMaxElements: 1048575,
314 },
315 {
316 Name: "stringMap",
317 InputSchema: &schema.Structural{
318 Generic: schema.Generic{
319 Type: "object",
320 AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
321 Generic: schema.Generic{
322 Type: "string",
323 },
324 }},
325 },
326 },
327
328 ExpectedMaxElements: 393215,
329 },
330 {
331 Name: "objectOptionalPropertyArray",
332 InputSchema: &schema.Structural{
333 Generic: schema.Generic{
334 Type: "array",
335 },
336 Items: &schema.Structural{
337 Generic: schema.Generic{
338 Type: "object",
339 },
340 Properties: map[string]schema.Structural{
341 "required": {
342 Generic: schema.Generic{
343 Type: "string",
344 },
345 },
346 "optional": {
347 Generic: schema.Generic{
348 Type: "string",
349 },
350 },
351 },
352 ValueValidation: &schema.ValueValidation{
353 Required: []string{"required"},
354 },
355 },
356 },
357
358 ExpectedMaxElements: 185042,
359 },
360 {
361 Name: "arrayWithLength",
362 InputSchema: arraySchema("integer", "int64", maxPtr(10)),
363
364 ExpectedMaxElements: 10,
365 },
366 {
367 Name: "stringWithLength",
368 InputSchema: &schema.Structural{
369 Generic: schema.Generic{
370 Type: "string",
371 },
372 ValueValidation: &schema.ValueValidation{
373 MaxLength: maxPtr(20),
374 },
375 },
376
377
378 ExpectedMaxElements: 80,
379 },
380 {
381 Name: "mapWithLength",
382 InputSchema: &schema.Structural{
383 Generic: schema.Generic{
384 Type: "object",
385 AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
386 Generic: schema.Generic{
387 Type: "string",
388 },
389 }},
390 },
391 ValueValidation: &schema.ValueValidation{
392 Format: "string",
393 MaxProperties: maxPtr(15),
394 },
395 },
396
397 ExpectedMaxElements: 15,
398 },
399 {
400 Name: "durationMaxSize",
401 InputSchema: &schema.Structural{
402 Generic: schema.Generic{
403 Type: "string",
404 },
405 ValueValidation: &schema.ValueValidation{
406 Format: "duration",
407 },
408 },
409
410 ExpectedMaxElements: apiservercel.MaxDurationSizeJSON,
411 },
412 {
413 Name: "dateSize",
414 InputSchema: &schema.Structural{
415 Generic: schema.Generic{
416 Type: "string",
417 },
418 ValueValidation: &schema.ValueValidation{
419 Format: "date",
420 },
421 },
422
423 ExpectedMaxElements: apiservercel.JSONDateSize,
424 },
425 {
426 Name: "maxdatetimeSize",
427 InputSchema: &schema.Structural{
428 Generic: schema.Generic{
429 Type: "string",
430 },
431 ValueValidation: &schema.ValueValidation{
432 Format: "date-time",
433 },
434 },
435
436 ExpectedMaxElements: apiservercel.MaxDatetimeSizeJSON,
437 },
438 {
439 Name: "maxintOrStringSize",
440 InputSchema: &schema.Structural{
441 Extensions: schema.Extensions{
442 XIntOrString: true,
443 },
444 },
445
446 ExpectedMaxElements: apiservercel.DefaultMaxRequestSizeBytes - 2,
447 },
448 {
449 Name: "objectDefaultFieldArray",
450 InputSchema: &schema.Structural{
451 Generic: schema.Generic{
452 Type: "array",
453 },
454 Items: &schema.Structural{
455 Generic: schema.Generic{
456 Type: "object",
457 },
458 Properties: map[string]schema.Structural{
459 "field": {
460 Generic: schema.Generic{
461 Type: "string",
462 Default: schema.JSON{Object: "default"},
463 },
464 },
465 },
466 ValueValidation: &schema.ValueValidation{
467 Required: []string{"field"},
468 },
469 },
470 },
471
472 ExpectedMaxElements: 1048575,
473 },
474 {
475 Name: "byteStringSize",
476 InputSchema: &schema.Structural{
477 Generic: schema.Generic{
478 Type: "string",
479 },
480 ValueValidation: &schema.ValueValidation{
481 Format: "byte",
482 },
483 },
484
485 ExpectedMaxElements: 3145726,
486 },
487 {
488 Name: "byteStringSetMaxLength",
489 InputSchema: &schema.Structural{
490 Generic: schema.Generic{
491 Type: "string",
492 },
493 ValueValidation: &schema.ValueValidation{
494 Format: "byte",
495 MaxLength: maxPtr(20),
496 },
497 },
498
499
500 ExpectedMaxElements: 20,
501 },
502 }
503 for _, testCase := range tests {
504 t.Run(testCase.Name, func(t *testing.T) {
505 decl := SchemaDeclType(testCase.InputSchema, false)
506 if decl.MaxElements != testCase.ExpectedMaxElements {
507 t.Errorf("wrong maxElements (got %d, expected %d)", decl.MaxElements, testCase.ExpectedMaxElements)
508 }
509 })
510 }
511 }
512
513 func maxPtr(max int64) *int64 {
514 return &max
515 }
516
517 func genNestedSchema(depth int) *schema.Structural {
518 var generator func(d int) schema.Structural
519 generator = func(d int) schema.Structural {
520 nodeTemplate := schema.Structural{
521 Generic: schema.Generic{
522 Type: "object",
523 AdditionalProperties: &schema.StructuralOrBool{},
524 },
525 }
526 if d == 1 {
527 return nodeTemplate
528 } else {
529 mapType := generator(d - 1)
530 nodeTemplate.Generic.AdditionalProperties.Structural = &mapType
531 return nodeTemplate
532 }
533 }
534 schema := generator(depth)
535 return &schema
536 }
537
538 func BenchmarkDeeplyNestedSchemaDeclType(b *testing.B) {
539 benchmarkSchema := genNestedSchema(10)
540 b.ResetTimer()
541 for i := 0; i < b.N; i++ {
542 SchemaDeclType(benchmarkSchema, false)
543 }
544 }
545
View as plain text