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