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