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.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  
25  import org.apache.juneau.*;
26  import org.junit.jupiter.api.*;
27  
28  /**
29   * Testcase for {@link OpenApi}.
30   */
31  class OpenApi_Test extends TestBase {
32  
33  	@Nested class A_basicTests extends TestBase {
34  
35  		private static final BeanTester<OpenApi> TESTER =
36  			testBean(
37  				bean()
38  					.setComponents(components().setSchemas(map("a1", schemaInfo().setType("a2"))))
39  					.setExternalDocs(externalDocumentation().setUrl(URI.create("b")))
40  					.setInfo(info().setTitle("c1").setVersion("c2"))
41  					.setOpenapi("3.0.0")
42  					.setPaths(map("d1", pathItem().setGet(operation().setSummary("d2"))))
43  					.setSecurity(list(securityRequirement().setRequirements(map("e1",list("e2")))))
44  					.setServers(list(server().setUrl(URI.create("f"))))
45  					.setTags(list(tag().setName("g")))
46  			)
47  			.props("components{schemas{a1{type}}},externalDocs{url},info{title,version},openapi,paths{d1{get{summary}}},security{0{requirements{e1}}},servers{0{url}},tags{0{name}}")
48  			.vals("{{{a2}}},{b},{c1,c2},3.0.0,{{{d2}}},{{{[e2]}}},{{f}},{{g}}")
49  			.json("{components:{schemas:{a1:{type:'a2'}}},externalDocs:{url:'b'},info:{title:'c1',version:'c2'},openapi:'3.0.0',paths:{d1:{get:{summary:'d2'}}},security:[{requirements:{e1:['e2']}}],servers:[{url:'f'}],tags:[{name:'g'}]}")
50  			.string("{'components':{'schemas':{'a1':{'type':'a2'}}},'externalDocs':{'url':'b'},'info':{'title':'c1','version':'c2'},'openapi':'3.0.0','paths':{'d1':{'get':{'summary':'d2'}}},'security':[{'requirements':{'e1':['e2']}}],'servers':[{'url':'f'}],'tags':[{'name':'g'}]}".replace('\'','"'))
51  		;
52  
53  		@Test void a01_gettersAndSetters() {
54  			TESTER.assertGettersAndSetters();
55  		}
56  
57  		@Test void a02_copy() {
58  			TESTER.assertCopy();
59  		}
60  
61  		@Test void a03_toJson() {
62  			TESTER.assertToJson();
63  		}
64  
65  		@Test void a04_fromJson() {
66  			TESTER.assertFromJson();
67  		}
68  
69  		@Test void a05_roundTrip() {
70  			TESTER.assertRoundTrip();
71  		}
72  
73  		@Test void a06_toString() {
74  			TESTER.assertToString();
75  		}
76  
77  		@Test void a07_keySet() {
78  			assertList(TESTER.bean().keySet(), "components", "externalDocs", "info", "openapi", "paths", "security", "servers", "tags");
79  		}
80  
81  		@Test void a08_nullParameters() {
82  			var x = bean();
83  			assertThrows(IllegalArgumentException.class, () -> x.get(null, String.class));
84  			assertThrows(IllegalArgumentException.class, () -> x.set(null, "value"));
85  		}
86  
87  		@Test void a09_addMethods() {
88  			assertBean(
89  				bean()
90  					.addPath("/test", pathItem().setGet(operation().setSummary("a"))),
91  				"paths{/test{get{summary}}}",
92  				"{{{a}}}"
93  			);
94  			
95  			// Test addTags
96  			var x1 = bean()
97  				.addTags(tag("b1"), tag("b2"))
98  				.addTags(list(tag("b3")));
99  			assertBean(x1, "tags{#{name}}", "{[{b1},{b2},{b3}]}");
100 			
101 			// Test addServers
102 			var x2 = bean()
103 				.addServers(server().setUrl(URI.create("http://c1.com")), server().setUrl(URI.create("http://c2.com")))
104 				.addServers(list(server().setUrl(URI.create("http://c3.com"))));
105 			assertBean(x2, "servers{#{url}}", "{[{http://c1.com},{http://c2.com},{http://c3.com}]}");
106 			
107 			// Test addSecurity
108 			var x3 = bean()
109 				.addSecurity(securityRequirement().setRequirements(map("d1", list("d2"))))
110 				.addSecurity(list(securityRequirement().setRequirements(map("d3", list("d4")))));
111 			assertBean(x3, "security{#{requirements}}", "{[{{d1=[d2]}},{{d3=[d4]}}]}");
112 		}
113 
114 		@Test void a09b_setTagsVarargs() {
115 			// Test setTags(Tag...) varargs method to cover that code path
116 			var x = bean().setTags(tag("t1"), tag("t2"), tag("t3"));
117 			assertBean(x, "tags{#{name}}", "{[{t1},{t2},{t3}]}");
118 		}
119 
120 		@Test void a10_asMap() {
121 			assertBean(
122 				bean()
123 					.setInfo(info().setTitle("a1").setVersion("a2"))
124 					.set("x1", "x1a")
125 					.asMap(),
126 				"info{title,version},openapi,x1",
127 				"{a1,a2},3.0.0,x1a"
128 			);
129 		}
130 
131 		@Test void a11_extraKeys() {
132 			var x = bean().set("x1", "x1a").set("x2", "x2a");
133 			assertList(x.extraKeys(), "x1", "x2");
134 			assertEmpty(bean().extraKeys());
135 		}
136 
137 		@Test void a12_strictMode() {
138 			assertThrows(RuntimeException.class, () -> bean().strict().set("foo", "bar"));
139 			assertDoesNotThrow(() -> bean().set("foo", "bar"));
140 
141 			assertFalse(bean().isStrict());
142 			assertTrue(bean().strict().isStrict());
143 			assertFalse(bean().strict(false).isStrict());
144 		}
145 
146 		@Test void a13_collectionSetters() {
147 			var x = bean()
148 				.setSecurity(list(securityRequirement().setRequirements(map("a1", list("a2"))), securityRequirement().setRequirements(map("b1", list("b2")))))
149 				.setServers(list(server().setUrl(URI.create("http://example1.com")), server().setUrl(URI.create("http://example2.com"))))
150 				.setTags(list(tag().setName("c1"), tag().setName("c2")));
151 
152 			assertBean(x,
153 				"security{#{requirements}},servers{#{url}},tags{#{name}}",
154 				"{[{{a1=[a2]}},{{b1=[b2]}}]},{[{http://example1.com},{http://example2.com}]},{[{c1},{c2}]}"
155 			);
156 		}
157 		
158 		@Test void a14_addPath() {
159 			// Test addPath method
160 			var x = bean()
161 				.addPath("/test", pathItem().setGet(operation().setSummary("Test operation")))
162 				.addPath("/test2", pathItem().setPost(operation().setSummary("Test POST operation")));
163 
164 			assertBean(x,
165 				"paths{/test{get{summary}},/test2{post{summary}}}",
166 				"{{{Test operation}},{{Test POST operation}}}"
167 			);
168 
169 			// Test addPath with null path (covers the null check branch)
170 			assertThrows(IllegalArgumentException.class, () -> x.addPath(null, pathItem()));
171 			assertThrows(IllegalArgumentException.class, () -> x.addPath("/test", null));
172 		}
173 	}
174 
175 	@Nested class B_emptyTests extends TestBase {
176 
177 		private static final BeanTester<OpenApi> TESTER =
178 			testBean(bean())
179 			.props("openapi,info,externalDocs,servers,tags,paths,components,security")
180 			.vals("3.0.0,<null>,<null>,<null>,<null>,<null>,<null>,<null>")
181 			.json("{openapi:'3.0.0'}")
182 			.string("{\"openapi\":\"3.0.0\"}")
183 		;
184 
185 		@Test void b01_gettersAndSetters() {
186 			TESTER.assertGettersAndSetters();
187 		}
188 
189 		@Test void b02_copy() {
190 			TESTER.assertCopy();
191 		}
192 
193 		@Test void b03_toJson() {
194 			TESTER.assertToJson();
195 		}
196 
197 		@Test void b04_fromJson() {
198 			TESTER.assertFromJson();
199 		}
200 
201 		@Test void b05_roundTrip() {
202 			TESTER.assertRoundTrip();
203 		}
204 
205 		@Test void b06_toString() {
206 			TESTER.assertToString();
207 		}
208 
209 		@Test void b07_keySet() {
210 			assertList(TESTER.bean().keySet(), "openapi");
211 		}
212 
213 		@Test void b08_keySet_infoNull() {
214 			// Explicitly test keySet when info is null to cover that branch
215 			var x = bean().setOpenapi("3.0.0"); // info is null
216 			assertList(x.keySet(), "openapi");
217 			assertFalse(x.keySet().contains("info"));
218 		}
219 	}
220 
221 	@Nested class C_extraProperties extends TestBase {
222 		private static final BeanTester<OpenApi> TESTER =
223 			testBean(
224 				bean()
225 					.set("components", components().setSchemas(map("a1", schemaInfo("a2"))))
226 					.set("externalDocs", externalDocumentation().setUrl(URI.create("b")))
227 					.set("info", info().setTitle("c").setVersion("d"))
228 					.set("openapi", "e")
229 					.set("paths", map("f1", pathItem().setGet(operation().setSummary("f2"))))
230 					.set("security", list(securityRequirement().setRequirements(map("g1",list("g2")))))
231 					.set("servers", list(server().setUrl(URI.create("h"))))
232 					.set("tags", list(tag("i")))
233 					.set("x1", "x1a")
234 					.set("x2", null)
235 			)
236 			.props("components{schemas{a1{type}}},externalDocs{url},info{title,version},openapi,paths{f1{get{summary}}},security{#{requirements{g1{#{toString}}}}},servers{#{url}},tags{#{name}},x1,x2")
237 			.vals("{{{a2}}},{b},{c,d},e,{{{f2}}},{[{{{[{g2}]}}}]},{[{h}]},{[{i}]},x1a,<null>")
238 			.json("{components:{schemas:{a1:{type:'a2'}}},externalDocs:{url:'b'},info:{title:'c',version:'d'},openapi:'e',paths:{f1:{get:{summary:'f2'}}},security:[{requirements:{g1:['g2']}}],servers:[{url:'h'}],tags:[{name:'i'}],x1:'x1a'}")
239 			.string("{'components':{'schemas':{'a1':{'type':'a2'}}},'externalDocs':{'url':'b'},'info':{'title':'c','version':'d'},'openapi':'e','paths':{'f1':{'get':{'summary':'f2'}}},'security':[{'requirements':{'g1':['g2']}}],'servers':[{'url':'h'}],'tags':[{'name':'i'}],'x1':'x1a'}".replace('\'', '"'))
240 		;
241 
242 		@Test void c01_gettersAndSetters() {
243 			TESTER.assertGettersAndSetters();
244 		}
245 
246 		@Test void c02_copy() {
247 			TESTER.assertCopy();
248 		}
249 
250 		@Test void c03_toJson() {
251 			TESTER.assertToJson();
252 		}
253 
254 		@Test void c04_fromJson() {
255 			TESTER.assertFromJson();
256 		}
257 
258 		@Test void c05_roundTrip() {
259 			TESTER.assertRoundTrip();
260 		}
261 
262 		@Test void c06_toString() {
263 			TESTER.assertToString();
264 		}
265 
266 		@Test void c07_keySet() {
267 			assertList(TESTER.bean().keySet(), "components", "externalDocs", "info", "openapi", "paths", "security", "servers", "tags", "x1", "x2");
268 		}
269 
270 		@Test void c08_get() {
271 			assertMapped(
272 				TESTER.bean(), (obj,prop) -> obj.get(prop, Object.class),
273 				"components{schemas{a1{type}}},externalDocs{url},info{title,version},openapi,paths{f1{get{summary}}},security{#{requirements{g1{#{toString}}}}},servers{#{url}},tags{#{name}},x1,x2",
274 				"{{{a2}}},{b},{c,d},e,{{{f2}}},{[{{{[{g2}]}}}]},{[{h}]},{[{i}]},x1a,<null>"
275 			);
276 		}
277 
278 		@Test void c09_getTypes() {
279 			assertMapped(
280 				TESTER.bean(), (obj,prop) -> simpleClassNameOf(obj.get(prop, Object.class)),
281 				"components,externalDocs,info,openapi,paths,security,servers,tags,x1,x2",
282 				"Components,ExternalDocumentation,Info,String,TreeMap,ArrayList,ArrayList,ArrayList,String,<null>"
283 			);
284 		}
285 
286 		@Test void c10_nullPropertyValue() {
287 			assertThrows(IllegalArgumentException.class, ()->bean().get(null));
288 			assertThrows(IllegalArgumentException.class, ()->bean().get(null, String.class));
289 			assertThrows(IllegalArgumentException.class, ()->bean().set(null, "a"));
290 		}
291 	}
292 
293 	@Nested class D_utilityMethods extends TestBase {
294 
295 		@Test void d01_findRef() {
296 			var x = openApi()
297 				.setComponents(components().setSchemas(map("a1", schemaInfo().setType("a2"))));
298 
299 			assertBean(
300 				x.findRef("#/components/schemas/a1", SchemaInfo.class),
301 				"type",
302 				"a2"
303 			);
304 
305 			assertNull(x.findRef("#/components/schemas/notfound", SchemaInfo.class));
306 
307 			assertThrows(IllegalArgumentException.class, () -> x.findRef(null, SchemaInfo.class));
308 			assertThrows(IllegalArgumentException.class, () -> x.findRef("a", null));
309 			assertThrows(IllegalArgumentException.class, () -> x.findRef("", SchemaInfo.class));
310 			assertThrowsWithMessage(BasicRuntimeException.class, "Unsupported reference:  'invalid'", () -> x.findRef("invalid", SchemaInfo.class));
311 		}
312 	}
313 
314 	//---------------------------------------------------------------------------------------------
315 	// Helper methods
316 	//---------------------------------------------------------------------------------------------
317 
318 	private static OpenApi bean() {
319 		return openApi();
320 	}
321 }