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 java.net.*;
26  import java.util.*;
27  
28  import org.apache.juneau.*;
29  import org.apache.juneau.MediaType;
30  import org.junit.jupiter.api.*;
31  
32  /**
33   * Testcase for {@link Operation}.
34   */
35  class Operation_Test extends TestBase {
36  
37  	@Nested class A_basicTests extends TestBase {
38  
39  		private static final BeanTester<Operation> TESTER =
40  			testBean(
41  				bean()
42  					.setConsumes(MediaType.of("a"))
43  					.setDeprecated(true)
44  					.setDescription("b")
45  					.setExternalDocs(externalDocumentation().setUrl(URI.create("c")))
46  					.setOperationId("d")
47  					.setParameters(parameterInfo().setName("e"))
48  					.setProduces(MediaType.of("f"))
49  					.setResponses(map("x1", responseInfo().setDescription("x2")))
50  					.setSchemes("g")
51  					.setSecurity(map("h", l("i")))
52  					.setSummary("j")
53  					.setTags("k")
54  			)
55  			.props("consumes,deprecated,description,externalDocs{url},operationId,parameters{#{name}},produces,responses{x1{description}},schemes,security{#{h}},summary,tags")
56  			.vals("[a],true,b,{c},d,{[{e}]},[f],{{x2}},[g],{[{[i]}]},j,[k]")
57  			.json("{consumes:['a'],deprecated:true,description:'b',externalDocs:{url:'c'},operationId:'d',parameters:[{name:'e'}],produces:['f'],responses:{x1:{description:'x2'}},schemes:['g'],security:[{h:['i']}],summary:'j',tags:['k']}")
58  			.string("{'consumes':['a'],'deprecated':true,'description':'b','externalDocs':{'url':'c'},'operationId':'d','parameters':[{'name':'e'}],'produces':['f'],'responses':{'x1':{'description':'x2'}},'schemes':['g'],'security':[{'h':['i']}],'summary':'j','tags':['k']}".replace('\'', '"'))
59  		;
60  
61  		@Test void a01_gettersAndSetters() {
62  			TESTER.assertGettersAndSetters();
63  		}
64  
65  		@Test void a02_copy() {
66  			TESTER.assertCopy();
67  		}
68  
69  		@Test void a03_toJson() {
70  			TESTER.assertToJson();
71  		}
72  
73  		@Test void a04_fromJson() {
74  			TESTER.assertFromJson();
75  		}
76  
77  		@Test void a05_roundTrip() {
78  			TESTER.assertRoundTrip();
79  		}
80  
81  		@Test void a06_toString() {
82  			TESTER.assertToString();
83  		}
84  
85  		@Test void a07_keySet() {
86  			assertList(TESTER.bean().keySet(), "consumes", "deprecated", "description", "externalDocs", "operationId", "parameters", "produces", "responses", "schemes", "security", "summary", "tags");
87  		}
88  
89  		@Test void a08_otherGettersAndSetters() {
90  			// Test special getters
91  			var x = bean()
92  				.setParameters(parameterInfo("a1", "a2"))
93  				.setResponses(map("b1", responseInfo("b2"), "200", responseInfo("b3")));
94  
95  			assertBean(x.getParameter("a1", "a2"), "in,name", "a1,a2");
96  			assertBean(x.getResponse("b1"), "description", "b2");
97  			assertBean(x.getResponse(200), "description", "b3");
98  
99  			assertNull(bean().getResponse("x"));
100 
101 			// Test Collection variant of addSecurity
102 			x = bean()
103 				.addSecurity(l(
104 					m("c1", l("c2")),
105 					m("c3", l("c4"))
106 				));
107 
108 			assertBean(x, "security{0{c1},1{c3}}", "{{[c2]},{[c4]}}");
109 		}
110 
111 		@Test void a09_nullParameters() {
112 			var x = bean();
113 
114 			assertThrows(IllegalArgumentException.class, ()->x.getParameter(null, "a"));
115 			assertThrows(IllegalArgumentException.class, ()->x.getResponse(null));
116 			assertThrows(IllegalArgumentException.class, ()->x.addResponse(null, responseInfo()));
117 			assertThrows(IllegalArgumentException.class, ()->x.addResponse("200", null));
118 			assertThrows(IllegalArgumentException.class, ()->x.addSecurity(null, "a"));
119 			assertThrows(IllegalArgumentException.class, ()->x.addSecurity(null));
120 		}
121 
122 		@Test void a10_collectionSetters() {
123 			// Test Collection variants of setters
124 			var x = bean()
125 				.setParameters(l(
126 					parameterInfo("a1", "a2"),
127 					parameterInfo("a3", "a4")
128 				))
129 				.setConsumes(l(
130 					MediaType.of("b1"),
131 					MediaType.of("b2")
132 				))
133 				.setProduces(l(
134 					MediaType.of("c1"),
135 					MediaType.of("c2")
136 				))
137 				.setSchemes(l("d1", "d2"))
138 				.setSecurity(l(
139 					m("e1", l("e2")),
140 					m("e3", l("e4"))
141 				));
142 
143 			assertBean(x,
144 				"parameters{#{in,name}},consumes,produces,schemes,security{0{e1},1{e3}}",
145 				"{[{a1,a2},{a3,a4}]},[b1,b2],[c1,c2],[d1,d2],{{[e2]},{[e4]}}"
146 			);
147 		}
148 
149 		@Test void a11_varargAdders() {
150 			// Test varargs addX methods - call each method twice
151 			var x = bean()
152 				.addConsumes(MediaType.of("a1"))
153 				.addConsumes(MediaType.of("a2"))
154 				.addProduces(MediaType.of("b1"))
155 				.addProduces(MediaType.of("b2"))
156 				.addSchemes("c1")
157 				.addSchemes("c2")
158 				.addTags("d1")
159 				.addTags("d2");
160 
161 			assertBean(x,
162 				"consumes,produces,schemes,tags",
163 				"[a1,a2],[b1,b2],[c1,c2],[d1,d2]"
164 			);
165 		}
166 
167 		@Test void a12_collectionAdders() {
168 			// Test Collection addX methods - call each method twice
169 			var x = bean()
170 				.addConsumes(l(MediaType.of("a1")))
171 				.addConsumes(l(MediaType.of("a2")))
172 				.addParameters(l(parameterInfo("query", "a")))
173 				.addParameters(l(parameterInfo("path", "b")))
174 				.addProduces(l(MediaType.of("b1")))
175 				.addProduces(l(MediaType.of("b2")))
176 				.addSchemes(l("c1"))
177 				.addSchemes(l("c2"))
178 				.addTags(l("d1"))
179 				.addTags(l("d2"));
180 
181 			assertBean(x,
182 				"consumes,produces,schemes,tags",
183 				"[a1,a2],[b1,b2],[c1,c2],[d1,d2]"
184 			);
185 		}
186 
187 		@Test void a13_asMap() {
188 			assertBean(
189 				bean()
190 					.setDescription("a")
191 					.setOperationId("b")
192 					.set("x1", "x1a")
193 					.asMap(),
194 				"description,operationId,x1",
195 				"a,b,x1a"
196 			);
197 		}
198 
199 		@Test void a14_extraKeys() {
200 			var x = bean().set("x1", "x1a").set("x2", "x2a");
201 			assertList(x.extraKeys(), "x1", "x2");
202 			assertEmpty(bean().extraKeys());
203 		}
204 
205 		@Test void a15_addSecurity() {
206 			// Test addSecurity method
207 			var x = bean()
208 				.addSecurity("scheme1", "a", "b")
209 				.addSecurity("scheme2", "c");
210 
211 			assertBean(x,
212 				"security{0{scheme1},1{scheme2}}",
213 				"{{[a,b]},{[c]}}"
214 			);
215 		}
216 
217 		@Test void a16_addSecurityCollection() {
218 			// Test addSecurity with Collection
219 			Map<String,List<String>> map1 = map();
220 			map1.put("scheme1", l("a"));
221 			Map<String,List<String>> map2 = map();
222 			map2.put("scheme2", l("b"));
223 
224 			Collection<Map<String,List<String>>> coll1 = l(map1);
225 			Collection<Map<String,List<String>>> coll2 = l(map2);
226 
227 			var x = bean()
228 				.addSecurity(coll1)
229 				.addSecurity(coll2);
230 
231 			assertBean(x,
232 				"security{0{scheme1},1{scheme2}}",
233 				"{{[a]},{[b]}}"
234 			);
235 		}
236 
237 		@Test void a17_getParameter() {
238 			// Test getParameter method with different scenarios
239 			var x = bean()
240 				.addParameters(
241 					parameterInfo("query", "param1"),
242 					parameterInfo("path", "param2"),
243 					parameterInfo("body", null) // body parameter with null name
244 				);
245 
246 			// Test getting an existing parameter
247 			assertNotNull(x.getParameter("query", "param1"));
248 
249 			// Test getting a non-existent parameter
250 			assertNull(x.getParameter("query", "nonexistent"));
251 
252 			// Test getting a parameter with different location
253 			assertNull(x.getParameter("header", "param1"));
254 
255 			// Test getting a body parameter (special case - name can be null)
256 			assertNotNull(x.getParameter("body", null));
257 			assertNotNull(x.getParameter("body", "anyName")); // body matches regardless of name
258 
259 			// Test with null parameters list (covers the null check branch)
260 			var y = bean();
261 			assertNull(y.getParameter("query", "param1"));
262 
263 			// Test with parameters that include a body parameter (covers the "body" branch)
264 			x = bean()
265 				.setParameters(l(
266 					parameterInfo("query", "param1"),
267 					parameterInfo("body", null) // body parameter with null name
268 				));
269 
270 			// Test normal parameter lookup
271 			var param1 = x.getParameter("query", "param1");
272 			assertNotNull(param1);
273 			assertEquals("param1", param1.getName());
274 			assertEquals("query", param1.getIn());
275 
276 			assertNull(x.getParameter("query", "nonexistent"));
277 
278 			// Test body parameter lookup (this covers the missing branch)
279 			var bodyParam = x.getParameter("body", null);
280 			assertNotNull(bodyParam);
281 			assertEquals("body", bodyParam.getIn());
282 
283 			// Test body parameter with any name (should still match)
284 			var bodyParam2 = x.getParameter("body", "anyName");
285 			assertNotNull(bodyParam2);
286 			assertEquals("body", bodyParam2.getIn());
287 		}
288 
289 		@Test void a18_strictMode() {
290 			assertThrows(RuntimeException.class, () -> bean().strict().set("foo", "bar"));
291 			assertDoesNotThrow(() -> bean().set("foo", "bar"));
292 
293 			assertFalse(bean().isStrict());
294 			assertTrue(bean().strict().isStrict());
295 			assertFalse(bean().strict(false).isStrict());
296 		}
297 
298 		@Test void a19_isDeprecated() {
299 			assertFalse(bean().isDeprecated());
300 			assertFalse(bean().setDeprecated(false).isDeprecated());
301 			assertTrue(bean().setDeprecated(true).isDeprecated());
302 		}
303 
304 		@Test void a20_addResponse() {
305 			var b = bean().addResponse("200", responseInfo()).addResponse("201", responseInfo());
306 			assertSize(2, b.getResponses());
307 		}
308 	}
309 
310 	@Nested class B_emptyTests extends TestBase {
311 
312 		private static final BeanTester<Operation> TESTER =
313 			testBean(bean())
314 			.props("description,operationId,summary,tags,externalDocs,consumes,produces,parameters,responses,schemes,deprecated,security")
315 			.vals("<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,false,<null>")
316 			.json("{}")
317 			.string("{}")
318 		;
319 
320 		@Test void b01_gettersAndSetters() {
321 			TESTER.assertGettersAndSetters();
322 		}
323 
324 		@Test void b02_copy() {
325 			TESTER.assertCopy();
326 		}
327 
328 		@Test void b03_toJson() {
329 			TESTER.assertToJson();
330 		}
331 
332 		@Test void b04_fromJson() {
333 			TESTER.assertFromJson();
334 		}
335 
336 		@Test void b05_roundTrip() {
337 			TESTER.assertRoundTrip();
338 		}
339 
340 		@Test void b06_toString() {
341 			TESTER.assertToString();
342 		}
343 
344 		@Test void b07_keySet() {
345 			assertEmpty(TESTER.bean().keySet());
346 		}
347 	}
348 
349 	@Nested class C_extraProperties extends TestBase {
350 
351 		private static final BeanTester<Operation> TESTER =
352 			testBean(
353 				bean()
354 					.set("consumes", set(MediaType.of("a")))
355 					.set("deprecated", true)
356 					.set("description", "b")
357 					.set("externalDocs", externalDocumentation().setUrl(URI.create("c")))
358 					.set("operationId", "d")
359 					.set("parameters", l(parameterInfo().setName("e")))
360 					.set("produces", set(MediaType.of("f")))
361 					.set("responses", m("x1", responseInfo().setDescription("x2")))
362 					.set("schemes", set("g"))
363 					.set("security", l(map("h", l("i"))))
364 					.set("summary", "j")
365 					.set("tags", set("k"))
366 					.set("x3", "x3a")
367 					.set("x4", null)
368 			)
369 			.props("consumes,deprecated,description,externalDocs{url},operationId,parameters{#{name}},produces,responses{x1{description}},schemes,security{#{h}},summary,tags,x3,x4")
370 			.vals("[a],true,b,{c},d,{[{e}]},[f],{{x2}},[g],{[{[i]}]},j,[k],x3a,<null>")
371 			.json("{consumes:['a'],deprecated:true,description:'b',externalDocs:{url:'c'},operationId:'d',parameters:[{name:'e'}],produces:['f'],responses:{x1:{description:'x2'}},schemes:['g'],security:[{h:['i']}],summary:'j',tags:['k'],x3:'x3a'}")
372 			.string("{'consumes':['a'],'deprecated':true,'description':'b','externalDocs':{'url':'c'},'operationId':'d','parameters':[{'name':'e'}],'produces':['f'],'responses':{'x1':{'description':'x2'}},'schemes':['g'],'security':[{'h':['i']}],'summary':'j','tags':['k'],'x3':'x3a'}".replace('\'', '"'))
373 		;
374 
375 		@Test void c01_gettersAndSetters() {
376 			TESTER.assertGettersAndSetters();
377 		}
378 
379 		@Test void c02_copy() {
380 			TESTER.assertCopy();
381 		}
382 
383 		@Test void c03_toJson() {
384 			TESTER.assertToJson();
385 		}
386 
387 		@Test void c04_fromJson() {
388 			TESTER.assertFromJson();
389 		}
390 
391 		@Test void c05_roundTrip() {
392 			TESTER.assertRoundTrip();
393 		}
394 
395 		@Test void c06_toString() {
396 			TESTER.assertToString();
397 		}
398 
399 		@Test void c07_keySet() {
400 			assertList(TESTER.bean().keySet(), "consumes", "deprecated", "description", "externalDocs", "operationId", "parameters", "produces", "responses", "schemes", "security", "summary", "tags", "x3", "x4");
401 		}
402 
403 		@Test void c08_get() {
404 			assertMapped(
405 				TESTER.bean(), (obj,prop) -> obj.get(prop, Object.class),
406 				"consumes,deprecated,description,externalDocs{url},operationId,parameters{#{name}},produces,responses{x1{description}},schemes,security{#{h}},summary,tags,x3,x4",
407 				"[a],true,b,{c},d,{[{e}]},[f],{{x2}},[g],{[{[i]}]},j,[k],x3a,<null>"
408 			);
409 		}
410 
411 		@Test void c09_getTypes() {
412 			assertMapped(
413 				TESTER.bean(), (obj,prop) -> cns(obj.get(prop, Object.class)),
414 				"consumes,deprecated,description,externalDocs,operationId,parameters,produces,responses,schemes,security,summary,tags,x3,x4",
415 				"LinkedHashSet,Boolean,String,ExternalDocumentation,String,ArrayList,LinkedHashSet,LinkedHashMap,LinkedHashSet,ArrayList,String,LinkedHashSet,String,<null>"
416 			);
417 		}
418 
419 		@Test void c10_nullPropertyValue() {
420 			assertThrows(IllegalArgumentException.class, ()->bean().get(null));
421 			assertThrows(IllegalArgumentException.class, ()->bean().get(null, String.class));
422 			assertThrows(IllegalArgumentException.class, ()->bean().set(null, "a"));
423 		}
424 	}
425 
426 	//---------------------------------------------------------------------------------------------
427 	// Helper methods
428 	//---------------------------------------------------------------------------------------------
429 
430 	private static Operation bean() {
431 		return operation();
432 	}
433 }