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