View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau.bean.swagger;
18  
19  import static org.apache.juneau.TestUtils.*;
20  import static org.apache.juneau.bean.swagger.SwaggerBuilder.*;
21  import static org.junit.jupiter.api.Assertions.*;
22  
23  import org.apache.juneau.*;
24  import org.apache.juneau.collections.*;
25  import org.junit.jupiter.api.*;
26  
27  /**
28   * Testcase for {@link Swagger}.
29   */
30  class Swagger_Test extends TestBase {
31  
32  	@Nested class A_basicTests extends TestBase {
33  
34  		private static final BeanTester<Swagger> TESTER =
35  			testBean(
36  				bean()
37  					.setBasePath("a")
38  					.setConsumes(MediaType.of("b"))
39  					.setDefinitions(map("c1", JsonMap.of("type", "c2")))
40  					.setExternalDocs(externalDocumentation("d"))
41  					.setHost("e")
42  					.setInfo(info("f1", "f2"))
43  					.setParameters(map("g1", parameterInfo("g2", "g3")))
44  					.setPaths(map("h1", operationMap().append("get", operation().setSummary("h2"))))
45  					.setProduces(MediaType.of("i"))
46  					.setResponses(map("j1", responseInfo().setDescription("j2")))
47  					.setSchemes("k")
48  					.setSecurity(list(map("l1", list("l2"))))
49  					.setSecurityDefinitions(map("m1", securityScheme().setType("m2")))
50  					.setSwagger("n")
51  					.setTags(tag().setName("o"))
52  			)
53  			.props("basePath,consumes,definitions{c1{type}},externalDocs{url},host,info{title,version},parameters{g1{in,name}},paths{h1{get{summary}}},produces,responses{j1{description}},schemes,security{0{l1}},securityDefinitions{m1{type}},swagger,tags{0{name}}")
54  			.vals("a,[b],{{c2}},{d},e,{f1,f2},{{g2,g3}},{{{h2}}},[i],{{j2}},[k],{{[l2]}},{{m2}},n,{{o}}")
55  			.json("{basePath:'a',consumes:['b'],definitions:{c1:{type:'c2'}},externalDocs:{url:'d'},host:'e',info:{title:'f1',version:'f2'},parameters:{g1:{'in':'g2',name:'g3'}},paths:{h1:{get:{summary:'h2'}}},produces:['i'],responses:{j1:{description:'j2'}},schemes:['k'],security:[{l1:['l2']}],securityDefinitions:{m1:{type:'m2'}},swagger:'n',tags:[{name:'o'}]}")
56  			.string("{'basePath':'a','consumes':['b'],'definitions':{'c1':{'type':'c2'}},'externalDocs':{'url':'d'},'host':'e','info':{'title':'f1','version':'f2'},'parameters':{'g1':{'in':'g2','name':'g3'}},'paths':{'h1':{'get':{'summary':'h2'}}},'produces':['i'],'responses':{'j1':{'description':'j2'}},'schemes':['k'],'security':[{'l1':['l2']}],'securityDefinitions':{'m1':{'type':'m2'}},'swagger':'n','tags':[{'name':'o'}]}".replace('\'', '"'))
57  		;
58  
59  		@Test void a01_gettersAndSetters() {
60  			TESTER.assertGettersAndSetters();
61  		}
62  
63  		@Test void a02_copy() {
64  			TESTER.assertCopy();
65  		}
66  
67  		@Test void a03_toJson() {
68  			TESTER.assertToJson();
69  		}
70  
71  		@Test void a04_fromJson() {
72  			TESTER.assertFromJson();
73  		}
74  
75  		@Test void a05_roundTrip() {
76  			TESTER.assertRoundTrip();
77  		}
78  
79  		@Test void a06_toString() {
80  			TESTER.assertToString();
81  		}
82  
83  		@Test void a07_keySet() {
84  			assertList(TESTER.bean().keySet(), "basePath", "consumes", "definitions", "externalDocs", "host", "info", "parameters", "paths", "produces", "responses", "schemes", "security", "securityDefinitions", "swagger", "tags");
85  		}
86  
87  		@Test void a08_otherGettersAndSetters() {
88  			// Test special getters
89  			var x = bean()
90  				.addPath("a1", "get", operation().setSummary("a2"))
91  				.addPath("b1", "get", operation().addResponse("200", responseInfo("b2")).setParameters(parameterInfo("b3", "b4")))
92  				.addParameter("c1", parameterInfo("c2", "c3"));
93  
94  			assertBean(x.getPath("a1"), "get{summary}", "{a2}");
95  			assertBean(x.getOperation("a1", "get"), "summary", "a2");
96  			assertBean(x.getResponseInfo("b1", "get", "200"), "description", "b2");
97  			assertBean(x.getResponseInfo("b1", "get", 200), "description", "b2");
98  			assertBean(x.getParameterInfo("b1", "get", "b3", "b4"), "in,name", "b3,b4");
99  
100 			// Test varargs variants of setters
101 			x = bean()
102 				.setConsumes(MediaType.of("e1"), MediaType.of("e2"))
103 				.setProduces(MediaType.of("f1"), MediaType.of("f2"))
104 				.setSchemes("g1", "g2");
105 
106 			assertBean(x, "consumes,produces,schemes", "[e1,e2],[f1,f2],[g1,g2]");
107 		}
108 
109 		@Test void a09_nullParameters() {
110 			var x = bean();
111 
112 			assertThrows(IllegalArgumentException.class, ()->x.getPath(null));
113 			assertThrows(IllegalArgumentException.class, ()->x.getOperation(null, "a"));
114 			assertThrows(IllegalArgumentException.class, ()->x.getOperation("a", null));
115 			assertThrows(IllegalArgumentException.class, ()->x.getResponseInfo(null, "a", "a"));
116 			assertThrows(IllegalArgumentException.class, ()->x.getResponseInfo("a", null, "a"));
117 			assertThrows(IllegalArgumentException.class, ()->x.getResponseInfo("a", "a", null));
118 			assertThrows(IllegalArgumentException.class, ()->x.getParameterInfo(null, "a", "a", "a"));
119 			assertThrows(IllegalArgumentException.class, ()->x.getParameterInfo("a", null, "a", "a"));
120 			assertThrows(IllegalArgumentException.class, ()->x.getParameterInfo("a", "a", null, "a"));
121 			assertThrows(IllegalArgumentException.class, ()->x.get(null, String.class));
122 			assertThrows(IllegalArgumentException.class, ()->x.set(null, "value"));
123 			assertThrows(IllegalArgumentException.class, ()->x.addDefinition(null, JsonMap.of("a", "b")));
124 			assertThrows(IllegalArgumentException.class, ()->x.addDefinition("a", null));
125 			assertThrows(IllegalArgumentException.class, ()->x.addParameter(null, parameterInfo("a", "b")));
126 			assertThrows(IllegalArgumentException.class, ()->x.addParameter("a", null));
127 			assertThrows(IllegalArgumentException.class, ()->x.addPath(null, "get", operation()));
128 			assertThrows(IllegalArgumentException.class, ()->x.addPath("a", null, operation()));
129 			assertThrows(IllegalArgumentException.class, ()->x.addPath("a", "get", null));
130 			assertThrows(IllegalArgumentException.class, ()->x.addResponse(null, responseInfo()));
131 			assertThrows(IllegalArgumentException.class, ()->x.addResponse("a", null));
132 			assertThrows(IllegalArgumentException.class, ()->x.addSecurity(null, "a"));
133 			assertThrows(IllegalArgumentException.class, ()->x.addSecurityDefinition(null, securityScheme()));
134 			assertThrows(IllegalArgumentException.class, ()->x.addSecurityDefinition("a", null));
135 		}
136 
137 		@Test void a10_collectionSetters() {
138 			// Test Collection variants of setters
139 			var x = bean()
140 				.setConsumes(list(
141 					MediaType.of("a1"),
142 					MediaType.of("a2")
143 				))
144 				.setProduces(list(
145 					MediaType.of("b1"),
146 					MediaType.of("b2")
147 				))
148 				.setSchemes(list("c1", "c2"))
149 				.setTags(list(tag().setName("d1"), tag().setName("d2")))
150 				.setSecurity(list(
151 					map("e1", list("e2")),
152 					map("e3", list("e4"))
153 				));
154 
155 			assertBean(x,
156 				"consumes,produces,schemes,tags{0{name},1{name}},security{0{e1},1{e3}}",
157 				"[a1,a2],[b1,b2],[c1,c2],{{d1},{d2}},{{[e2]},{[e4]}}"
158 			);
159 		}
160 
161 		@Test void a11_varargAdders() {
162 			// Test varargs addX methods - call each method twice
163 			var x = bean()
164 				.addConsumes(MediaType.of("a1"))
165 				.addConsumes(MediaType.of("a2"))
166 				.addProduces(MediaType.of("b1"))
167 				.addProduces(MediaType.of("b2"))
168 				.addSchemes("c1")
169 				.addSchemes("c2")
170 				.addTags(tag().setName("d1"))
171 				.addTags(tag().setName("d2"));
172 
173 			assertBean(x,
174 				"consumes,produces,schemes,tags{0{name},1{name}}",
175 				"[a1,a2],[b1,b2],[c1,c2],{{d1},{d2}}"
176 			);
177 		}
178 
179 		@Test void a12_collectionAdders() {
180 			// Test Collection addX methods - call each method twice
181 			var x = bean()
182 				.addConsumes(list(MediaType.of("a1")))
183 				.addConsumes(list(MediaType.of("a2")))
184 				.addProduces(list(MediaType.of("b1")))
185 				.addProduces(list(MediaType.of("b2")))
186 				.addSchemes(list("c1"))
187 				.addSchemes(list("c2"))
188 				.addSecurity(list(map("d1",list("d2"))))
189 				.addSecurity(list(map("d3",list("d4"))))
190 				.addTags(list(tag().setName("e1")))
191 				.addTags(list(tag().setName("e2")));
192 
193 			assertBean(x,
194 				"consumes,produces,schemes,security,tags{0{name},1{name}}",
195 				"[a1,a2],[b1,b2],[c1,c2],[{d1=[d2]},{d3=[d4]}],{{e1},{e2}}"
196 			);
197 		}
198 
199 		@Test void a13_asMap() {
200 			assertBean(
201 				bean()
202 					.setBasePath("a")
203 					.setHost("b")
204 					.set("x1", "x1a")
205 					.asMap(),
206 				"basePath,host,swagger,x1",
207 				"a,b,2.0,x1a"
208 			);
209 		}
210 
211 		@Test void a14_extraKeys() {
212 			var x = bean().set("x1", "x1a").set("x2", "x2a");
213 			assertList(x.extraKeys(), "x1", "x2");
214 			assertEmpty(bean().extraKeys());
215 		}
216 
217 		@Test void a15_addResponse() {
218 			// Test addResponse method
219 			var x = bean()
220 				.addResponse("200", responseInfo().setDescription("a"))
221 				.addResponse("404", responseInfo().setDescription("b"));
222 
223 			assertBean(x,
224 				"responses{200{description},404{description}}",
225 				"{{a},{b}}"
226 			);
227 		}
228 
229 		@Test void a16_addSecurity() {
230 			// Test addSecurity method
231 			var x = bean()
232 				.addSecurity("scheme1", "a", "b")
233 				.addSecurity("scheme2", "c");
234 
235 			assertBean(x,
236 				"security{0{scheme1},1{scheme2}}",
237 				"{{[a,b]},{[c]}}"
238 			);
239 		}
240 
241 		@Test void a17_addSecurityDefinition() {
242 			// Test addSecurityDefinition method
243 			var x = bean()
244 				.addSecurityDefinition("def1", securityScheme().setType("a"))
245 				.addSecurityDefinition("def2", securityScheme().setType("b"));
246 
247 			assertBean(x,
248 				"securityDefinitions{def1{type},def2{type}}",
249 				"{{a},{b}}"
250 			);
251 		}
252 
253 		@Test void a18_getOperationNullPath() {
254 			var a = swagger()
255 				.addPath("/existing", "get", operation().setSummary("test"));
256 
257 			// Test getOperation when path doesn't exist (returns null)
258 			assertNull(a.getOperation("/nonexistent", "get"));
259 
260 			// Test getResponseInfo when path doesn't exist (returns null)
261 			assertNull(a.getResponseInfo("/nonexistent", "get", "200"));
262 
263 			// Test getResponseInfo when operation doesn't exist (returns null)
264 			assertNull(a.getResponseInfo("/test", "post", "200"));
265 
266 			// Test getParameterInfo when path doesn't exist (returns null)
267 			assertNull(a.getParameterInfo("/nonexistent", "get", "query", "param"));
268 
269 			// Test getParameterInfo when operation doesn't exist (returns null)
270 			assertNull(a.getParameterInfo("/test", "post", "query", "param"));
271 		}
272 
273 		@Test void a19_getMethodWithInvalidProperty() {
274 			var a = swagger();
275 
276 			// Test get method with invalid property (should call super.get)
277 			assertNull(a.get("invalidProperty", String.class));
278 		}
279 
280 		@Test void a20_strictMode() {
281 			assertThrows(RuntimeException.class, () -> bean().strict().set("foo", "bar"));
282 			assertDoesNotThrow(() -> bean().set("foo", "bar"));
283 
284 			assertFalse(bean().isStrict());
285 			assertTrue(bean().strict().isStrict());
286 			assertFalse(bean().strict(false).isStrict());
287 		}
288 
289 		@Test void a21_asJson() {
290 			var x = swagger().setHost("a");
291 			var json = x.asJson();
292 			assertTrue(json.contains("a"));
293 		}
294 	}
295 
296 	@Nested class B_emptyTests extends TestBase {
297 
298 		private static final BeanTester<Swagger> TESTER =
299 			testBean(bean())
300 			.props("basePath,host,swagger,info,tags,schemes,consumes,produces,paths,definitions,parameters,responses,securityDefinitions,security,externalDocs")
301 			.vals("<null>,<null>,2.0,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>")
302 			.json("{swagger:'2.0'}")
303 			.string("{\"swagger\":\"2.0\"}")
304 		;
305 
306 		@Test void b01_gettersAndSetters() {
307 			TESTER.assertGettersAndSetters();
308 		}
309 
310 		@Test void b02_copy() {
311 			TESTER.assertCopy();
312 		}
313 
314 		@Test void b03_toJson() {
315 			TESTER.assertToJson();
316 		}
317 
318 		@Test void b04_fromJson() {
319 			TESTER.assertFromJson();
320 		}
321 
322 		@Test void b05_roundTrip() {
323 			TESTER.assertRoundTrip();
324 		}
325 
326 		@Test void b06_toString() {
327 			TESTER.assertToString();
328 		}
329 
330 		@Test void b07_keySet() {
331 			assertList(TESTER.bean().keySet(), "swagger");
332 			assertList(TESTER.bean().setSwagger(null).keySet());
333 		}
334 	}
335 
336 	@Nested class C_extraProperties extends TestBase {
337 
338 		private static final BeanTester<Swagger> TESTER =
339 			testBean(
340 				bean()
341 					.set("basePath", "a")
342 					.set("consumes", list(MediaType.of("b")))
343 					.set("definitions", map("c1", JsonMap.of("type", "c2")))
344 					.set("externalDocs", externalDocumentation("d"))
345 					.set("host", "e")
346 					.set("info", info("f1", "f2"))
347 					.set("parameters", map("g1", parameterInfo("g2", "g3")))
348 					.set("paths", map("h1", operationMap().append("get", operation().setSummary("h2"))))
349 					.set("produces", list(MediaType.of("i")))
350 					.set("responses", map("j1", responseInfo().setDescription("j2")))
351 					.set("schemes", list("k"))
352 					.set("security", list(map("l1", list("l2"))))
353 					.set("securityDefinitions", map("m1", securityScheme().setType("m2")))
354 					.set("swagger", "n")
355 					.set("tags", list(tag().setName("o")))
356 					.set("x1", "x1a")
357 					.set("x2", null)
358 			)
359 			.props("basePath,consumes,definitions{c1{type}},externalDocs{url},host,info{title,version},parameters{g1{in,name}},paths{h1{get{summary}}},produces,responses{j1{description}},schemes,security{0{l1}},securityDefinitions{m1{type}},swagger,tags{0{name}},x1,x2")
360 			.vals("a,[b],{{c2}},{d},e,{f1,f2},{{g2,g3}},{{{h2}}},[i],{{j2}},[k],{{[l2]}},{{m2}},n,{{o}},x1a,<null>")
361 			.json("{basePath:'a',consumes:['b'],definitions:{c1:{type:'c2'}},externalDocs:{url:'d'},host:'e',info:{title:'f1',version:'f2'},parameters:{g1:{'in':'g2',name:'g3'}},paths:{h1:{get:{summary:'h2'}}},produces:['i'],responses:{j1:{description:'j2'}},schemes:['k'],security:[{l1:['l2']}],securityDefinitions:{m1:{type:'m2'}},swagger:'n',tags:[{name:'o'}],x1:'x1a'}")
362 			.string("{'basePath':'a','consumes':['b'],'definitions':{'c1':{'type':'c2'}},'externalDocs':{'url':'d'},'host':'e','info':{'title':'f1','version':'f2'},'parameters':{'g1':{'in':'g2','name':'g3'}},'paths':{'h1':{'get':{'summary':'h2'}}},'produces':['i'],'responses':{'j1':{'description':'j2'}},'schemes':['k'],'security':[{'l1':['l2']}],'securityDefinitions':{'m1':{'type':'m2'}},'swagger':'n','tags':[{'name':'o'}],'x1':'x1a'}".replace('\'', '"'))
363 		;
364 
365 		@Test void c01_gettersAndSetters() {
366 			TESTER.assertGettersAndSetters();
367 		}
368 
369 		@Test void c02_copy() {
370 			TESTER.assertCopy();
371 		}
372 
373 		@Test void c03_toJson() {
374 			TESTER.assertToJson();
375 		}
376 
377 		@Test void c04_fromJson() {
378 			TESTER.assertFromJson();
379 		}
380 
381 		@Test void c05_roundTrip() {
382 			TESTER.assertRoundTrip();
383 		}
384 
385 		@Test void c06_toString() {
386 			TESTER.assertToString();
387 		}
388 
389 		@Test void c07_keySet() {
390 			assertList(TESTER.bean().keySet(), "basePath", "consumes", "definitions", "externalDocs", "host", "info", "parameters", "paths", "produces", "responses", "schemes", "security", "securityDefinitions", "swagger", "tags", "x1", "x2");
391 		}
392 
393 		@Test void c08_get() {
394 			assertMapped(
395 				TESTER.bean(), (obj,prop) -> obj.get(prop, Object.class),
396 				"basePath,consumes,definitions{c1{type}},externalDocs{url},host,info{title,version},parameters{g1{in,name}},paths{h1{get{summary}}},produces,responses{j1{description}},schemes,security{0{l1}},securityDefinitions{m1{type}},swagger,tags{0{name}},x1,x2",
397 				"a,[b],{{c2}},{d},e,{f1,f2},{{g2,g3}},{{{h2}}},[i],{{j2}},[k],{{[l2]}},{{m2}},n,{{o}},x1a,<null>"
398 			);
399 		}
400 
401 		@Test void c09_getTypes() {
402 			assertMapped(
403 				TESTER.bean(), (obj,prop) -> simpleClassNameOf(obj.get(prop, Object.class)),
404 				"basePath,consumes,definitions,externalDocs,host,info,parameters,paths,produces,responses,schemes,security,securityDefinitions,swagger,tags,x1,x2",
405 				"String,LinkedHashSet,LinkedHashMap,ExternalDocumentation,String,Info,LinkedHashMap,TreeMap,LinkedHashSet,LinkedHashMap,LinkedHashSet,ArrayList,LinkedHashMap,String,LinkedHashSet,String,<null>"
406 			);
407 		}
408 
409 		@Test void c10_nullPropertyValue() {
410 			assertThrows(IllegalArgumentException.class, ()->bean().get(null));
411 			assertThrows(IllegalArgumentException.class, ()->bean().get(null, String.class));
412 			assertThrows(IllegalArgumentException.class, ()->bean().set(null, "a"));
413 		}
414 	}
415 
416 	@Nested class D_refs extends TestBase {
417 
418 
419 		@Test void d01_findRef() {
420 			var x = swagger().addDefinition("a1", JsonMap.of("type", "a2"));
421 			assertBean(
422 				x.findRef("#/definitions/a1", JsonMap.class),
423 				"type",
424 				"a2"
425 			);
426 
427 			assertNull(x.findRef("#/definitions/notfound", JsonMap.class));
428 
429 			assertThrows(IllegalArgumentException.class, () -> x.findRef(null, JsonMap.class));
430 			assertThrows(IllegalArgumentException.class, () -> x.findRef("a", null));
431 			assertThrows(IllegalArgumentException.class, () -> x.findRef("", JsonMap.class));
432 			assertThrowsWithMessage(BasicRuntimeException.class, "Unsupported reference:  'invalid'", () -> x.findRef("invalid", JsonMap.class));
433 		}
434 
435 		@Test void d02_findRefInvalidType() {
436 			// Test findRef with invalid type - should throw exception
437 			var x = swagger()
438 				.addDefinition("Pet", JsonMap.of("type", "object"))
439 				.addResponse("Error", responseInfo().setDescription("Error response"))
440 				.addParameter("petId", parameterInfo("path", "id"));
441 
442 			// Test trying to parse a definition into an invalid type
443 			assertThrows(Exception.class, () -> x.findRef("#/definitions/Pet", Integer.class));
444 
445 			// Test trying to parse a response into an invalid type
446 			assertThrows(Exception.class, () -> x.findRef("#/responses/Error", Integer.class));
447 
448 			// Test trying to parse a parameter into an invalid type
449 			assertThrows(Exception.class, () -> x.findRef("#/parameters/petId", Integer.class));
450 		}
451 
452 	}
453 
454 	//---------------------------------------------------------------------------------------------
455 	// Helper methods
456 	//---------------------------------------------------------------------------------------------
457 
458 	private static Swagger bean() {
459 		return swagger();
460 	}
461 
462 }