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