1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.juneau.bean.openapi3;
18
19 import static org.apache.juneau.TestUtils.*;
20 import static org.apache.juneau.bean.openapi3.OpenApiBuilder.*;
21 import static org.junit.jupiter.api.Assertions.*;
22
23 import java.net.*;
24 import java.util.*;
25
26 import org.apache.juneau.*;
27 import org.apache.juneau.collections.*;
28 import org.junit.jupiter.api.*;
29
30
31
32
33 class Items_Test extends TestBase {
34
35 @Nested class A_basicTests extends TestBase {
36
37 private static final BeanTester<Items> TESTER =
38 testBean(
39 bean()
40 .setCollectionFormat("a")
41 .setDefault("b")
42 .setEnum(list("c1", "c2"))
43 .setExclusiveMaximum(true)
44 .setExclusiveMinimum(true)
45 .setFormat("d")
46 .setItems(bean().setType("e"))
47 .setMaxItems(1)
48 .setMaxLength(2)
49 .setMaximum(3)
50 .setMinItems(4)
51 .setMinLength(5)
52 .setMinimum(6)
53 .setMultipleOf(7)
54 .setPattern("f")
55 .setRef("g")
56 .setType("h")
57 .setUniqueItems(true)
58 )
59 .props("collectionFormat,default,enum,exclusiveMaximum,exclusiveMinimum,format,items{type},maximum,maxItems,maxLength,minimum,minItems,minLength,multipleOf,pattern,ref,type,uniqueItems")
60 .vals("a,b,[c1,c2],true,true,d,{e},3,1,2,6,4,5,7,f,g,h,true")
61 .json("{'$ref':'g',collectionFormat:'a','default':'b','enum':['c1','c2'],exclusiveMaximum:true,exclusiveMinimum:true,format:'d',items:{type:'e'},maxItems:1,maxLength:2,maximum:3,minItems:4,minLength:5,minimum:6,multipleOf:7,pattern:'f',type:'h',uniqueItems:true}")
62 .string("{'$ref':'g','collectionFormat':'a','default':'b','enum':['c1','c2'],'exclusiveMaximum':true,'exclusiveMinimum':true,'format':'d','items':{'type':'e'},'maxItems':1,'maxLength':2,'maximum':3,'minItems':4,'minLength':5,'minimum':6,'multipleOf':7,'pattern':'f','type':'h','uniqueItems':true}".replace('\'','"'))
63 ;
64
65 @Test void a01_gettersAndSetters() {
66 TESTER.assertGettersAndSetters();
67 }
68
69 @Test void a02_copy() {
70 TESTER.assertCopy();
71 }
72
73 @Test void a03_toJson() {
74 TESTER.assertToJson();
75 }
76
77 @Test void a04_fromJson() {
78 TESTER.assertFromJson();
79 }
80
81 @Test void a05_roundTrip() {
82 TESTER.assertRoundTrip();
83 }
84
85 @Test void a06_toString() {
86 TESTER.assertToString();
87 }
88
89 @Test void a07_keySet() {
90 assertList(TESTER.bean().keySet(), "$ref", "collectionFormat", "default", "enum", "exclusiveMaximum", "exclusiveMinimum", "format", "items", "maxItems", "maxLength", "maximum", "minItems", "minLength", "minimum", "multipleOf", "pattern", "type", "uniqueItems");
91 }
92
93 @Test void a08_nullParameters() {
94 var x = bean();
95 assertThrows(IllegalArgumentException.class, () -> x.get(null, String.class));
96 assertThrows(IllegalArgumentException.class, () -> x.set(null, "value"));
97 }
98
99 @Test void a08b_getSetRef() {
100
101 var x = bean();
102 x.set("$ref", "#/components/schemas/MyItem");
103 assertEquals("#/components/schemas/MyItem", x.get("$ref", String.class));
104 assertEquals("#/components/schemas/MyItem", x.getRef());
105 }
106
107 @Test void a09_addMethods() {
108 assertBean(
109 bean()
110 .addEnum("a1", "a2"),
111 "enum",
112 "[a1,a2]"
113 );
114 }
115
116 @Test void a10_asMap() {
117 assertBean(
118 bean()
119 .setType("a")
120 .set("x1", "x1a")
121 .asMap(),
122 "type,x1",
123 "a,x1a"
124 );
125 }
126
127 @Test void a11_extraKeys() {
128 var x = bean().set("x1", "x1a").set("x2", "x2a");
129 assertList(x.extraKeys(), "x1", "x2");
130 assertEmpty(bean().extraKeys());
131 }
132
133 @Test void a12_getItemsProperty() {
134 var x = bean().setItems(bean().setType("a"));
135 assertBean(x.get("items", Items.class), "type", "a");
136 }
137
138 @Test void a13_strictMode() {
139 assertThrows(RuntimeException.class, () -> bean().strict().set("foo", "bar"));
140 assertDoesNotThrow(() -> bean().set("foo", "bar"));
141
142 assertFalse(bean().isStrict());
143 assertTrue(bean().strict().isStrict());
144 assertFalse(bean().strict(false).isStrict());
145
146 var x = bean().strict();
147 var y = bean();
148 assertThrowsWithMessage(IllegalArgumentException.class, "Invalid value passed in to setType(String). Value='invalid', valid values=['string','number','integer','boolean','array']", () -> x.setType("invalid"));
149 assertDoesNotThrow(() -> x.setType("string"));
150 assertDoesNotThrow(() -> x.setType("number"));
151 assertDoesNotThrow(() -> x.setType("integer"));
152 assertDoesNotThrow(() -> x.setType("boolean"));
153 assertDoesNotThrow(() -> x.setType("array"));
154 assertDoesNotThrow(() -> y.setType("invalid"));
155
156 assertThrowsWithMessage(RuntimeException.class, "Invalid value passed in to setCollectionFormat(String). Value='invalid', valid values=[csv, ssv, tsv, pipes, multi]", () -> x.setCollectionFormat("invalid"));
157 assertDoesNotThrow(() -> x.setCollectionFormat("csv"));
158 assertDoesNotThrow(() -> x.setCollectionFormat("ssv"));
159 assertDoesNotThrow(() -> x.setCollectionFormat("tsv"));
160 assertDoesNotThrow(() -> x.setCollectionFormat("pipes"));
161 assertDoesNotThrow(() -> x.setCollectionFormat("multi"));
162 assertDoesNotThrow(() -> y.setCollectionFormat("invalid"));
163 }
164 }
165
166 @Nested class B_emptyTests extends TestBase {
167
168 private static final BeanTester<Items> TESTER =
169 testBean(bean())
170 .props("type,format,items,collectionFormat,default,maximum,exclusiveMaximum,minimum,exclusiveMinimum,maxLength,minLength,pattern,maxItems,minItems,uniqueItems,enum,multipleOf,ref")
171 .vals("<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>")
172 .json("{}")
173 .string("{}")
174 ;
175
176 @Test void b01_gettersAndSetters() {
177 TESTER.assertGettersAndSetters();
178 }
179
180 @Test void b02_copy() {
181 TESTER.assertCopy();
182 }
183
184 @Test void b03_toJson() {
185 TESTER.assertToJson();
186 }
187
188 @Test void b04_fromJson() {
189 TESTER.assertFromJson();
190 }
191
192 @Test void b05_roundTrip() {
193 TESTER.assertRoundTrip();
194 }
195
196 @Test void b06_toString() {
197 TESTER.assertToString();
198 }
199
200 @Test void b07_keySet() {
201 assertEmpty(TESTER.bean().keySet());
202 }
203 }
204
205 @Nested class C_extraProperties extends TestBase {
206 private static final BeanTester<Items> TESTER =
207 testBean(
208 bean()
209 .set("additionalItems", schemaInfo("a"))
210 .set("allOf", list(schemaInfo("b1"), schemaInfo("b2")))
211 .set("collectionFormat", "c")
212 .set("default", "d")
213 .set("discriminator", "e")
214 .set("enum", list("f1", "f2"))
215 .set("example", "g")
216 .set("exclusiveMaximum", true)
217 .set("exclusiveMinimum", true)
218 .set("externalDocs", externalDocumentation().setUrl(URI.create("h")))
219 .set("format", "i")
220 .set("items", bean().setType("j"))
221 .set("maxItems", 1)
222 .set("maxLength", 2)
223 .set("maxProperties", 3)
224 .set("maximum", 4)
225 .set("minItems", 5)
226 .set("minLength", 6)
227 .set("minProperties", 7)
228 .set("minimum", 8)
229 .set("multipleOf", 9)
230 .set("pattern", "k")
231 .set("properties", map("l1", schemaInfo("l2")))
232 .set("readOnly", true)
233 .set("required", list("m1", "m2"))
234 .set("title", "n")
235 .set("type", "o")
236 .set("uniqueItems", true)
237 .set("xml", xml().setName("p"))
238 .set("x1", "x1a")
239 .set("x2", null)
240 )
241 .props("additionalItems{type},allOf{#{type}},collectionFormat,default,discriminator,enum{#{toString}},example,exclusiveMaximum,exclusiveMinimum,externalDocs{url},format,items{type},maxItems,maxLength,maxProperties,maximum,minItems,minLength,minProperties,minimum,multipleOf,pattern,properties{l1{type}},readOnly,required{#{toString}},title,type,uniqueItems,xml{name},x1,x2")
242 .vals("{a},{[{b1},{b2}]},c,d,e,{[{f1},{f2}]},g,true,true,{h},i,{j},1,2,3,4,5,6,7,8,9,k,{{l2}},true,{[{m1},{m2}]},n,o,true,{p},x1a,<null>")
243 .json("{additionalItems:{type:'a'},allOf:[{type:'b1'},{type:'b2'}],collectionFormat:'c','default':'d',discriminator:'e','enum':['f1','f2'],example:'g',exclusiveMaximum:true,exclusiveMinimum:true,externalDocs:{url:'h'},format:'i',items:{type:'j'},maxItems:1,maxLength:2,maxProperties:3,maximum:4,minItems:5,minLength:6,minProperties:7,minimum:8,multipleOf:9,pattern:'k',properties:{l1:{type:'l2'}},readOnly:true,required:['m1','m2'],title:'n',type:'o',uniqueItems:true,x1:'x1a',xml:{name:'p'}}")
244 .string("{'additionalItems':{'type':'a'},'allOf':[{'type':'b1'},{'type':'b2'}],'collectionFormat':'c','default':'d','discriminator':'e','enum':['f1','f2'],'example':'g','exclusiveMaximum':true,'exclusiveMinimum':true,'externalDocs':{'url':'h'},'format':'i','items':{'type':'j'},'maxItems':1,'maxLength':2,'maxProperties':3,'maximum':4,'minItems':5,'minLength':6,'minProperties':7,'minimum':8,'multipleOf':9,'pattern':'k','properties':{'l1':{'type':'l2'}},'readOnly':true,'required':['m1','m2'],'title':'n','type':'o','uniqueItems':true,'x1':'x1a','xml':{'name':'p'}}".replace('\'', '"'))
245 ;
246
247 @Test void c01_gettersAndSetters() {
248 TESTER.assertGettersAndSetters();
249 }
250
251 @Test void c02_copy() {
252 TESTER.assertCopy();
253 }
254
255 @Test void c03_toJson() {
256 TESTER.assertToJson();
257 }
258
259 @Test void c04_fromJson() {
260 TESTER.assertFromJson();
261 }
262
263 @Test void c05_roundTrip() {
264 TESTER.assertRoundTrip();
265 }
266
267 @Test void c06_toString() {
268 TESTER.assertToString();
269 }
270
271 @Test void c07_keySet() {
272 assertList(TESTER.bean().keySet(), "additionalItems", "allOf", "collectionFormat", "default", "discriminator", "enum", "example", "exclusiveMaximum", "exclusiveMinimum", "externalDocs", "format", "items", "maxItems", "maxLength", "maxProperties", "maximum", "minItems", "minLength", "minProperties", "minimum", "multipleOf", "pattern", "properties", "readOnly", "required", "title", "type", "uniqueItems", "x1", "x2", "xml");
273 }
274
275 @Test void c08_get() {
276 assertMapped(
277 TESTER.bean(), (obj,prop) -> obj.get(prop, Object.class),
278 "additionalItems{type},allOf{#{type}},collectionFormat,default,discriminator,enum{#{toString}},example,exclusiveMaximum,exclusiveMinimum,externalDocs{url},format,items{type},maxItems,maxLength,maxProperties,maximum,minItems,minLength,minProperties,minimum,multipleOf,pattern,properties{l1{type}},readOnly,required{#{toString}},title,type,uniqueItems,xml{name},x1,x2",
279 "{a},{[{b1},{b2}]},c,d,e,{[{f1},{f2}]},g,true,true,{h},i,{j},1,2,3,4,5,6,7,8,9,k,{{l2}},true,{[{m1},{m2}]},n,o,true,{p},x1a,<null>"
280 );
281 }
282
283 @Test void c09_getTypes() {
284 assertMapped(
285 TESTER.bean(), (obj,prop) -> simpleClassNameOf(obj.get(prop, Object.class)),
286 "additionalItems,allOf,collectionFormat,default,discriminator,enum,example,exclusiveMaximum,exclusiveMinimum,externalDocs,format,items,maxItems,maxLength,maxProperties,maximum,minItems,minLength,minProperties,minimum,multipleOf,pattern,properties,readOnly,required,title,type,uniqueItems,xml,x1,x2",
287 "SchemaInfo,ArrayList,String,String,String,ArrayList,String,Boolean,Boolean,ExternalDocumentation,String,Items,Integer,Integer,Integer,Integer,Integer,Integer,Integer,Integer,Integer,String,LinkedHashMap,Boolean,ArrayList,String,String,Boolean,Xml,String,<null>"
288 );
289 }
290
291 @Test void c10_nullPropertyValue() {
292 assertThrows(IllegalArgumentException.class, ()->bean().get(null));
293 assertThrows(IllegalArgumentException.class, ()->bean().get(null, String.class));
294 assertThrows(IllegalArgumentException.class, ()->bean().set(null, "a"));
295 }
296 }
297
298 @Nested class D_refs extends TestBase {
299
300 @Test void d01_resolveRefs_basic() {
301 var openApi = openApi()
302 .setComponents(components().setSchemas(Map.of(
303 "MyItem", schemaInfo().setType("string")
304 )));
305 assertBean(
306 items().setRef("#/components/schemas/MyItem").resolveRefs(openApi, new ArrayDeque<>(), 10),
307 "type",
308 "string"
309 );
310 }
311
312 @Test void d02_resolveRefs_nestedItems() {
313 var openApi = openApi()
314 .setComponents(components().setSchemas(Map.of(
315 "MyItem", schemaInfo().setType("string"),
316 "MyArray", schemaInfo().setType("array").setItems(items().setRef("#/components/schemas/MyItem"))
317 )));
318
319 assertBean(
320 items().setRef("#/components/schemas/MyArray").resolveRefs(openApi, new ArrayDeque<>(), 10),
321 "type,items{type}",
322 "array,{string}"
323 );
324 }
325
326 @Test void d03_resolveRefs_maxDepth() {
327 var openApi = openApi()
328 .setComponents(components().setSchemas(Map.of(
329 "MyItem", schemaInfo().setType("string"),
330 "MyArray", schemaInfo().setType("array").setItems(items().setRef("#/components/schemas/MyItem"))
331 )));
332 assertBean(
333 items().setRef("#/components/schemas/MyArray").resolveRefs(openApi, new ArrayDeque<>(), 1),
334 "type,items{ref}",
335 "array,{#/components/schemas/MyItem}"
336 );
337 }
338
339 @Test void d04_resolveRefsWithRef() {
340 var openApi = openApi()
341 .setComponents(components().setSchemas(Map.of(
342 "MyItem", schemaInfo().setType("string")
343 )));
344
345 assertBean(
346 items().setRef("#/components/schemas/MyItem").resolveRefs(openApi, new ArrayDeque<>(), 10),
347 "type",
348 "string"
349 );
350
351 var refStack = new ArrayDeque<String>();
352 refStack.add("#/components/schemas/MyItem");
353
354
355 assertBean(
356 items().setRef("#/components/schemas/MyItem").resolveRefs(openApi, refStack, 10),
357 "ref",
358 "#/components/schemas/MyItem"
359 );
360
361
362 assertBean(
363 items().setRef("#/components/schemas/MyItem").resolveRefs(openApi, new ArrayDeque<>(), 0),
364 "ref",
365 "#/components/schemas/MyItem"
366 );
367
368
369 assertBean(
370 items()
371 .set("properties", JsonMap.of("prop1", JsonMap.of("$ref", "#/components/schemas/MyItem")))
372 .resolveRefs(openApi, new ArrayDeque<>(), 10),
373 "properties{prop1{type}}",
374 "{{string}}"
375 );
376
377
378 assertBean(
379 items().setItems(items().setRef("#/components/schemas/MyItem")).resolveRefs(openApi, new ArrayDeque<>(), 10),
380 "items{type}",
381 "{string}"
382 );
383
384
385 assertBean(
386 items().set("example", "test").resolveRefs(openApi, new ArrayDeque<>(), 10),
387 "example",
388 "<null>"
389 );
390
391
392 assertBean(
393 items().setType("string").resolveRefs(openApi, new ArrayDeque<>(), 10),
394 "type,example",
395 "string,<null>"
396 );
397
398
399 assertBean(
400 items().setType("string").resolveRefs(openApi, new ArrayDeque<>(), 10),
401 "type,items,example",
402 "string,<null>,<null>"
403 );
404
405
406 assertBean(
407 items().setType("string").resolveRefs(openApi, new ArrayDeque<>(), 10),
408 "type,example",
409 "string,<null>"
410 );
411 }
412
413 @Test void d04_resolveRefs_noRefNoItems() {
414
415 var openApi = openApi()
416 .setComponents(components().setSchemas(map("MyItem", schemaInfo().setType("string"))));
417
418 var items = bean()
419 .setType("string")
420 .setFormat("text");
421
422 var result = items.resolveRefs(openApi, new ArrayDeque<>(), 10);
423
424
425 assertSame(items, result);
426 assertEquals("string", result.getType());
427 assertEquals("text", result.getFormat());
428 }
429
430 @Test void d05_resolveRefs_circularReference() {
431
432 var openApi = openApi()
433 .setComponents(components().setSchemas(map(
434 "Item1", schemaInfo().setRef("#/components/schemas/Item2"),
435 "Item2", schemaInfo().setRef("#/components/schemas/Item1")
436 )));
437
438 var refStack = new ArrayDeque<String>();
439 refStack.add("#/components/schemas/Item1");
440
441 var item = items()
442 .setType("object")
443 .set("properties", JsonMap.of("prop1", JsonMap.of("$ref", "#/components/schemas/Item1")));
444 var result = item.resolveRefs(openApi, refStack, 10);
445
446
447 assertSame(item, result);
448 }
449
450 @Test void d06_resolveRefs_maxDepthDirect() {
451
452 var openApi = openApi()
453 .setComponents(components().setSchemas(map("MyItem", schemaInfo().setType("string"))));
454
455 var refStack = new ArrayDeque<String>();
456 refStack.add("dummy1");
457 refStack.add("dummy2");
458 refStack.add("dummy3");
459
460 var item = items()
461 .setType("object")
462 .set("properties", JsonMap.of("prop1", JsonMap.of("$ref", "#/components/schemas/MyItem")));
463 var result = item.resolveRefs(openApi, refStack, 3);
464
465
466 assertSame(item, result);
467 }
468
469 @Test void d07_resolveRefs_withJsonList() {
470
471 var openApi = openApi()
472 .setComponents(components().setSchemas(map("MyItem", schemaInfo().setType("string"))));
473
474 var item = items()
475 .setType("object")
476 .set("someList", JsonList.of(JsonMap.of("$ref", "#/components/schemas/MyItem")));
477 var result = item.resolveRefs(openApi, new ArrayDeque<>(), 10);
478
479
480 assertSame(item, result);
481 }
482
483 @Test void d08_resolveRefs_withPrimitiveValue() {
484
485 var openApi = openApi()
486 .setComponents(components().setSchemas(map("MyItem", schemaInfo().setType("string"))));
487
488 var item = items()
489 .setType("object")
490 .set("someString", "plain string value")
491 .set("someNumber", 42)
492 .set("someBoolean", true);
493 var result = item.resolveRefs(openApi, new ArrayDeque<>(), 10);
494
495
496 assertSame(item, result);
497 assertEquals("plain string value", item.get("someString", String.class));
498 assertEquals(42, item.get("someNumber", Integer.class));
499 assertEquals(true, item.get("someBoolean", Boolean.class));
500 }
501 }
502
503
504
505
506
507 private static Items bean() {
508 return items();
509 }
510 }