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.util.*;
26  
27  import org.apache.juneau.*;
28  import org.apache.juneau.collections.*;
29  import org.junit.jupiter.api.*;
30  
31  /**
32   * Testcase for {@link HeaderInfo}.
33   */
34  class HeaderInfo_Test extends TestBase {
35  
36  	@Nested class A_basicTests extends TestBase {
37  
38  		private static final BeanTester<HeaderInfo> TESTER =
39  			testBean(
40  				bean()
41  					.setCollectionFormat("a")
42  					.setDefault("b")
43  					.setDescription("c")
44  					.setEnum("d")
45  					.setExample("e")
46  					.setExclusiveMaximum(true)
47  					.setExclusiveMinimum(true)
48  					.setFormat("f")
49  					.setItems(items().setType("g"))
50  					.setMaximum(1)
51  					.setMaxItems(2)
52  					.setMaxLength(3)
53  					.setMinimum(4)
54  					.setMinItems(5)
55  					.setMinLength(6)
56  					.setMultipleOf(7)
57  					.setPattern("h")
58  					.setRef("i")
59  					.setType("j")
60  					.setUniqueItems(true)
61  			)
62  			.props("collectionFormat,default,description,enum,example,exclusiveMaximum,exclusiveMinimum,format,items{type},maximum,maxItems,maxLength,minimum,minItems,minLength,multipleOf,pattern,ref,type,uniqueItems")
63  			.vals("a,b,c,[d],e,true,true,f,{g},1,2,3,4,5,6,7,h,i,j,true")
64  			.json("{'$ref':'i',collectionFormat:'a','default':'b',description:'c','enum':['d'],example:'e',exclusiveMaximum:true,exclusiveMinimum:true,format:'f',items:{type:'g'},maxItems:2,maxLength:3,maximum:1,minItems:5,minLength:6,minimum:4,multipleOf:7,pattern:'h',type:'j',uniqueItems:true}")
65  			.string("{'$ref':'i','collectionFormat':'a','default':'b','description':'c','enum':['d'],'example':'e','exclusiveMaximum':true,'exclusiveMinimum':true,'format':'f','items':{'type':'g'},'maxItems':2,'maxLength':3,'maximum':1,'minItems':5,'minLength':6,'minimum':4,'multipleOf':7,'pattern':'h','type':'j','uniqueItems':true}".replace('\'', '"'))
66  		;
67  
68  		@Test void a01_gettersAndSetters() {
69  			TESTER.assertGettersAndSetters();
70  		}
71  
72  		@Test void a02_copy() {
73  			TESTER.assertCopy();
74  		}
75  
76  		@Test void a03_toJson() {
77  			TESTER.assertToJson();
78  		}
79  
80  		@Test void a04_fromJson() {
81  			TESTER.assertFromJson();
82  		}
83  
84  		@Test void a05_roundTrip() {
85  			TESTER.assertRoundTrip();
86  		}
87  
88  		@Test void a06_toString() {
89  			TESTER.assertToString();
90  		}
91  
92  		@Test void a07_keySet() {
93  			assertList(TESTER.bean().keySet(), "$ref", "collectionFormat", "default", "description", "enum", "example", "exclusiveMaximum", "exclusiveMinimum", "format", "items", "maxItems", "maxLength", "maximum", "minItems", "minLength", "minimum", "multipleOf", "pattern", "type", "uniqueItems");
94  		}
95  
96  		@Test void a08_nullParameters() {
97  			var x = bean();
98  			assertThrows(IllegalArgumentException.class, () -> x.get(null, String.class));
99  			assertThrows(IllegalArgumentException.class, () -> x.set(null, "value"));
100 		}
101 
102 		@Test void a09_addMethods() {
103 			var x = bean().addEnum("a1");
104 			assertNotNull(x);
105 			assertNotNull(x.getEnum());
106 		}
107 
108 		@Test void a10_asMap() {
109 			assertBean(
110 				bean()
111 					.setDescription("a")
112 					.setType("b")
113 					.set("x1", "x1a")
114 					.asMap(),
115 				"description,type,x1",
116 				"a,b,x1a"
117 			);
118 		}
119 
120 		@Test void a11_extraKeys() {
121 			var x = bean().set("x1", "x1a").set("x2", "x2a");
122 			assertList(x.extraKeys(), "x1", "x2");
123 			assertEmpty(bean().extraKeys());
124 		}
125 
126 		@Test void a12_strictMode() {
127 			assertThrows(RuntimeException.class, () -> bean().strict().set("foo", "bar"));
128 			assertDoesNotThrow(() -> bean().set("foo", "bar"));
129 
130 			assertFalse(bean().isStrict());
131 			assertTrue(bean().strict().isStrict());
132 			assertFalse(bean().strict(false).isStrict());
133 
134 			var x = bean().strict();
135 			var y = bean(); // not strict
136 
137 			assertThrowsWithMessage(RuntimeException.class, "Invalid value passed in to setCollectionFormat(String).  Value='invalid', valid values=['csv','ssv','tsv','pipes','multi']", () -> x.setCollectionFormat("invalid"));
138 			assertDoesNotThrow(() -> x.setCollectionFormat("csv"));
139 			assertDoesNotThrow(() -> x.setCollectionFormat("ssv"));
140 			assertDoesNotThrow(() -> x.setCollectionFormat("tsv"));
141 			assertDoesNotThrow(() -> x.setCollectionFormat("pipes"));
142 			assertDoesNotThrow(() -> x.setCollectionFormat("multi"));
143 			assertDoesNotThrow(() -> y.setCollectionFormat("invalid"));
144 
145 			assertThrowsWithMessage(RuntimeException.class, "Invalid value passed in to setType(String).  Value='invalid', valid values=['string','number','integer','boolean','array']", () -> x.setType("invalid"));
146 			assertDoesNotThrow(() -> x.setType("string"));
147 			assertDoesNotThrow(() -> x.setType("number"));
148 			assertDoesNotThrow(() -> x.setType("integer"));
149 			assertDoesNotThrow(() -> x.setType("boolean"));
150 			assertDoesNotThrow(() -> x.setType("array"));
151 			assertDoesNotThrow(() -> y.setType("invalid"));
152 		}
153 	}
154 
155 	@Nested class B_emptyTests extends TestBase {
156 
157 		private static final BeanTester<HeaderInfo> TESTER =
158 			testBean(bean())
159 			.props("description,type,format,items,collectionFormat,default,maximum,exclusiveMaximum,minimum,exclusiveMinimum,maxLength,minLength,pattern,maxItems,minItems,uniqueItems,enum,multipleOf")
160 			.vals("<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>")
161 			.json("{}")
162 			.string("{}")
163 		;
164 
165 		@Test void b01_gettersAndSetters() {
166 			TESTER.assertGettersAndSetters();
167 		}
168 
169 		@Test void b02_copy() {
170 			TESTER.assertCopy();
171 		}
172 
173 		@Test void b03_toJson() {
174 			TESTER.assertToJson();
175 		}
176 
177 		@Test void b04_fromJson() {
178 			TESTER.assertFromJson();
179 		}
180 
181 		@Test void b05_roundTrip() {
182 			TESTER.assertRoundTrip();
183 		}
184 
185 		@Test void b06_toString() {
186 			TESTER.assertToString();
187 		}
188 
189 		@Test void b07_keySet() {
190 			assertEmpty(TESTER.bean().keySet());
191 		}
192 	}
193 
194 	@Nested class C_extraProperties extends TestBase {
195 
196 		private static final BeanTester<HeaderInfo> TESTER =
197 			testBean(
198 				bean()
199 					.set("collectionFormat", "a")
200 					.set("default", "b")
201 					.set("description", "c")
202 					.set("enum", set("d"))
203 					.set("example", "e")
204 					.set("exclusiveMaximum", true)
205 					.set("exclusiveMinimum", true)
206 					.set("format", "f")
207 					.set("items", items().setType("g"))
208 					.set("maximum", 1)
209 					.set("maxItems", 2)
210 					.set("maxLength", 3)
211 					.set("minimum", 4)
212 					.set("minItems", 5)
213 					.set("minLength", 6)
214 					.set("multipleOf", 7)
215 					.set("pattern", "h")
216 					.set("$ref", "i")
217 					.set("type", "j")
218 					.set("uniqueItems", true)
219 					.set("x1", "x1a")
220 					.set("x2", null)
221 			)
222 			.props("collectionFormat,default,description,enum,example,exclusiveMaximum,exclusiveMinimum,format,items{type},maximum,maxItems,maxLength,minimum,minItems,minLength,multipleOf,pattern,ref,type,uniqueItems,x1,x2")
223 			.vals("a,b,c,[d],e,true,true,f,{g},1,2,3,4,5,6,7,h,i,j,true,x1a,<null>")
224 			.json("{'$ref':'i',collectionFormat:'a','default':'b',description:'c','enum':['d'],example:'e',exclusiveMaximum:true,exclusiveMinimum:true,format:'f',items:{type:'g'},maxItems:2,maxLength:3,maximum:1,minItems:5,minLength:6,minimum:4,multipleOf:7,pattern:'h',type:'j',uniqueItems:true,x1:'x1a'}")
225 			.string("{'$ref':'i','collectionFormat':'a','default':'b','description':'c','enum':['d'],'example':'e','exclusiveMaximum':true,'exclusiveMinimum':true,'format':'f','items':{'type':'g'},'maxItems':2,'maxLength':3,'maximum':1,'minItems':5,'minLength':6,'minimum':4,'multipleOf':7,'pattern':'h','type':'j','uniqueItems':true,'x1':'x1a'}".replace('\'', '"'))
226 		;
227 
228 		@Test void c01_gettersAndSetters() {
229 			TESTER.assertGettersAndSetters();
230 		}
231 
232 		@Test void c02_copy() {
233 			TESTER.assertCopy();
234 		}
235 
236 		@Test void c03_toJson() {
237 			TESTER.assertToJson();
238 		}
239 
240 		@Test void c04_fromJson() {
241 			TESTER.assertFromJson();
242 		}
243 
244 		@Test void c05_roundTrip() {
245 			TESTER.assertRoundTrip();
246 		}
247 
248 		@Test void c06_toString() {
249 			TESTER.assertToString();
250 		}
251 
252 		@Test void c07_keySet() {
253 			assertList(TESTER.bean().keySet(), "$ref", "collectionFormat", "default", "description", "enum", "example", "exclusiveMaximum", "exclusiveMinimum", "format", "items", "maxItems", "maxLength", "maximum", "minItems", "minLength", "minimum", "multipleOf", "pattern", "type", "uniqueItems", "x1", "x2");
254 		}
255 
256 		@Test void c08_get() {
257 			assertMapped(
258 				TESTER.bean(), (obj,prop) -> obj.get(prop, Object.class),
259 				"collectionFormat,default,description,enum,example,exclusiveMaximum,exclusiveMinimum,format,items{type},maximum,maxItems,maxLength,minimum,minItems,minLength,multipleOf,pattern,$ref,type,uniqueItems,x1,x2",
260 				"a,b,c,[d],e,true,true,f,{g},1,2,3,4,5,6,7,h,i,j,true,x1a,<null>"
261 			);
262 		}
263 
264 		@Test void c09_getTypes() {
265 			assertMapped(
266 				TESTER.bean(), (obj,prop) -> cns(obj.get(prop, Object.class)),
267 				"collectionFormat,default,description,enum,example,exclusiveMaximum,exclusiveMinimum,format,items,maximum,maxItems,maxLength,minimum,minItems,minLength,multipleOf,pattern,$ref,type,uniqueItems,x1,x2",
268 				"String,String,String,LinkedHashSet,String,Boolean,Boolean,String,Items,Integer,Integer,Integer,Integer,Integer,Integer,Integer,String,String,String,Boolean,String,<null>"
269 			);
270 		}
271 
272 		@Test void c10_nullPropertyValue() {
273 			assertThrows(IllegalArgumentException.class, ()->bean().get(null));
274 			assertThrows(IllegalArgumentException.class, ()->bean().get(null, String.class));
275 			assertThrows(IllegalArgumentException.class, ()->bean().set(null, "a"));
276 		}
277 	}
278 
279 	@Nested class D_refs extends TestBase {
280 
281 		@Test void d01_resolveRefs_basic() {
282 			var swagger = swagger()
283 				.addDefinition("MyHeader", JsonMap.of("type", "string", "description", "My Header"));
284 
285 			assertBean(
286 				headerInfo().setRef("#/definitions/MyHeader").resolveRefs(swagger, new ArrayDeque<>(), 10),
287 				"type,description",
288 				"string,My Header"
289 			);
290 		}
291 
292 		@Test void d02_resolveRefs_withItems() {
293 			var swagger = swagger()
294 				.addDefinition("MyItem", JsonMap.of("type", "string"))
295 				.addDefinition("MyHeader", JsonMap.of("type", "array", "items", JsonMap.of("$ref", "#/definitions/MyItem")));
296 
297 			assertBean(
298 				headerInfo().setRef("#/definitions/MyHeader").resolveRefs(swagger, new ArrayDeque<>(), 10),
299 				"type,items{type}",
300 				"array,{string}"
301 			);
302 		}
303 
304 		@Test void d03_resolveRefs_maxDepth() {
305 			var swagger = swagger()
306 				.addDefinition("MyItem", JsonMap.of("type", "string"))
307 				.addDefinition("MyHeader", JsonMap.of("type", "array", "items", JsonMap.of("$ref", "#/definitions/MyItem")));
308 
309 			assertBean(headerInfo().setRef("#/definitions/MyHeader").resolveRefs(swagger, new ArrayDeque<>(), 1),
310 				"type,items{ref}",
311 				"array,{#/definitions/MyItem}"
312 			);
313 		}
314 
315 		@Test void d04_resolveRefs_noRefNoItems() {
316 			// Test resolveRefs when both ref and items are null (covers the missing branch)
317 			var swagger = swagger();
318 			var header = headerInfo()
319 				.setType("string")
320 				.setDescription("Test header");
321 
322 			var result = header.resolveRefs(swagger, new ArrayDeque<>(), 10);
323 
324 			// Should return the same object unchanged
325 			assertSame(header, result);
326 			assertEquals("string", result.getType());
327 			assertEquals("Test header", result.getDescription());
328 		}
329 
330 		@Test void d05_resolveRefs_noRefWithItems() {
331 			// Test resolveRefs when ref is null but items is not null (covers the missing branch)
332 			var swagger = swagger()
333 				.addDefinition("MyItem", JsonMap.of("type", "string"));
334 
335 			var header = headerInfo()
336 				.setType("array")
337 				.setItems(items().setRef("#/definitions/MyItem"));
338 
339 			var result = header.resolveRefs(swagger, new ArrayDeque<>(), 10);
340 
341 			// Should return the same object with resolved items
342 			assertSame(header, result);
343 			assertEquals("array", result.getType());
344 			assertNotNull(result.getItems());
345 			assertEquals("string", result.getItems().getType());
346 			assertNull(result.getItems().getRef()); // ref should be resolved
347 		}
348 
349 		@Test void d06_resolveRefs_circularReference() {
350 			// Test circular reference detection (covers the refStack.contains(ref) branch)
351 			var swagger = swagger()
352 				.addDefinition("Header1", JsonMap.of("$ref", "#/definitions/Header2"))
353 				.addDefinition("Header2", JsonMap.of("$ref", "#/definitions/Header1"));
354 
355 			var refStack = new ArrayDeque<String>();
356 			refStack.add("#/definitions/Header1"); // Pre-populate the stack to simulate circular reference
357 
358 			var header = headerInfo().setRef("#/definitions/Header1");
359 			var result = header.resolveRefs(swagger, refStack, 10);
360 
361 			// Should return the original object without resolving (circular reference detected)
362 			assertSame(header, result);
363 			assertEquals("#/definitions/Header1", result.getRef());
364 		}
365 
366 		@Test void d07_resolveRefs_maxDepthDirect() {
367 			// Test max depth directly (covers the refStack.size() >= maxDepth branch directly)
368 			var swagger = swagger()
369 				.addDefinition("MyHeader", JsonMap.of("type", "string"));
370 
371 			// Create a refStack that's already at max depth
372 			var refStack = new ArrayDeque<String>();
373 			refStack.add("dummy1");
374 			refStack.add("dummy2");
375 			refStack.add("dummy3");
376 
377 			var header = headerInfo().setRef("#/definitions/MyHeader");
378 			var result = header.resolveRefs(swagger, refStack, 3); // maxDepth = 3, refStack.size() = 3
379 
380 			// Should return the original object without resolving (max depth reached)
381 			assertSame(header, result);
382 			assertEquals("#/definitions/MyHeader", result.getRef());
383 		}
384 	}
385 
386 	//---------------------------------------------------------------------------------------------
387 	// Helper methods
388 	//---------------------------------------------------------------------------------------------
389 
390 	private static HeaderInfo bean() {
391 		return headerInfo();
392 	}
393 }