11package io .jenkins .plugins .casc ;
22
3+ import static org .junit .Assert .assertArrayEquals ;
34import static org .junit .Assert .assertEquals ;
5+ import static org .junit .Assert .assertNotNull ;
6+ import static org .junit .Assert .assertTrue ;
47
58import io .jenkins .plugins .casc .model .Mapping ;
9+ import java .util .Arrays ;
10+ import java .util .List ;
611import java .util .Map ;
712import java .util .Set ;
813import java .util .stream .Collectors ;
@@ -20,9 +25,33 @@ public static class Vehicle {}
2025
2126 public static class Car extends Vehicle {}
2227
28+ public static class FakeSecret {}
29+
30+ public static class A_Shape {}
31+
32+ public static class B_Polygon extends A_Shape {}
33+
34+ public static class C_Square extends B_Polygon {}
35+
36+ public interface Z_Interface {}
37+
38+ public static class A_Concrete {}
39+
40+ public static class V_Class {}
41+
42+ public static class W_Class {}
43+
44+ public static class X_Class {}
45+
46+ public static class Y_Class {}
47+
48+ public static class Z_Class {}
49+
2350 @ SuppressWarnings ("unused" )
2451 public static class DummyTarget {
2552
53+ private String [] items ;
54+
2655 public String getStandard () {
2756 return null ;
2857 }
@@ -56,6 +85,120 @@ public Animal getAmbiguous() {
5685 public void setAmbiguous (Dog dog ) {}
5786
5887 public void setAmbiguous (Cat cat ) {}
88+
89+ public void setWriteOnly (String val ) {}
90+
91+ public FakeSecret getMismatchedToken () {
92+ return null ;
93+ }
94+
95+ public void setMismatchedToken (String val ) {}
96+
97+ public String [] getItems () {
98+ return items ;
99+ }
100+
101+ public void setItems (String [] items ) {
102+ this .items = items ;
103+ }
104+
105+ public List <String > getArrayFallback () {
106+ return null ;
107+ }
108+
109+ public void setArrayFallback (String val ) {}
110+
111+ public void setArrayFallback (String [] val ) {}
112+
113+ public FakeSecret getShapeSubtype () {
114+ return null ;
115+ }
116+
117+ public void setShapeSubtype (A_Shape val ) {}
118+
119+ public void setShapeSubtype (B_Polygon val ) {}
120+
121+ public void setShapeSubtype (C_Square val ) {}
122+
123+ public Object getConcreteWins () {
124+ return null ;
125+ }
126+
127+ public void setConcreteWins (Z_Interface val ) {}
128+
129+ public void setConcreteWins (A_Concrete val ) {}
130+
131+ public Object getReverseAlphabetical () {
132+ return null ;
133+ }
134+
135+ public void setReverseAlphabetical (Z_Class val ) {}
136+
137+ public void setReverseAlphabetical (Y_Class val ) {}
138+
139+ public void setReverseAlphabetical (X_Class val ) {}
140+
141+ public void setReverseAlphabetical (W_Class val ) {}
142+
143+ public void setReverseAlphabetical (V_Class val ) {}
144+
145+ public Integer getPrimitiveSetter () {
146+ return null ;
147+ }
148+
149+ public void setPrimitiveSetter (int val ) {}
150+
151+ public int getWrapperSetter () {
152+ return 0 ;
153+ }
154+
155+ public void setWrapperSetter (Integer val ) {}
156+
157+ public Boolean getPrimitiveBoolean () {
158+ return null ;
159+ }
160+
161+ public void setPrimitiveBoolean (boolean val ) {}
162+
163+ public Long getPrimitiveLong () {
164+ return null ;
165+ }
166+
167+ public void setPrimitiveLong (long val ) {}
168+
169+ public Double getPrimitiveDouble () {
170+ return null ;
171+ }
172+
173+ public void setPrimitiveDouble (double val ) {}
174+
175+ public Float getPrimitiveFloat () {
176+ return null ;
177+ }
178+
179+ public void setPrimitiveFloat (float val ) {}
180+
181+ public Byte getPrimitiveByte () {
182+ return null ;
183+ }
184+
185+ public void setPrimitiveByte (byte val ) {}
186+
187+ public Character getPrimitiveChar () {
188+ return null ;
189+ }
190+
191+ public void setPrimitiveChar (char val ) {}
192+
193+ public Short getPrimitiveShort () {
194+ return null ;
195+ }
196+
197+ public void setPrimitiveShort (short val ) {}
198+
199+ public void getVoidEdgeCase () {}
200+
201+ public void setVoidEdgeCase (String val ) {}
59202 }
60203
61204 public static class DummyConfigurator extends BaseConfigurator <DummyTarget > {
@@ -70,6 +213,35 @@ protected DummyTarget instance(Mapping mapping, ConfigurationContext context) {
70213 }
71214 }
72215
216+ @ SuppressWarnings ("unused" )
217+ public static class NoGetterTarget {
218+ public void setCompletelyMissing (String val ) {}
219+
220+ public void setRequiresParam (String val ) {}
221+
222+ public String getRequiresParam (String param ) {
223+ return param ;
224+ }
225+
226+ public void setFakeBoolean (String val ) {}
227+
228+ public String isFakeBoolean () {
229+ return "I am a String, not a boolean" ;
230+ }
231+ }
232+
233+ public static class NoGetterConfigurator extends BaseConfigurator <NoGetterTarget > {
234+ @ Override
235+ public Class <NoGetterTarget > getTarget () {
236+ return NoGetterTarget .class ;
237+ }
238+
239+ @ Override
240+ protected NoGetterTarget instance (Mapping mapping , ConfigurationContext context ) {
241+ return new NoGetterTarget ();
242+ }
243+ }
244+
73245 @ Test
74246 public void testDescribeResolvesBestSetters () {
75247 DummyConfigurator configurator = new DummyConfigurator ();
@@ -78,7 +250,7 @@ public void testDescribeResolvesBestSetters() {
78250 Map <String , Class <?>> resolvedAttributes =
79251 attributes .stream ().collect (Collectors .toMap (Attribute ::getName , attr -> (Class <?>) attr .getType ()));
80252
81- assertEquals ("Should discover exactly 4 configurable properties" , 4 , resolvedAttributes .size ());
253+ assertEquals ("Should discover exactly 20 configurable properties" , 20 , resolvedAttributes .size ());
82254
83255 assertEquals ("Standard setter should resolve to String" , String .class , resolvedAttributes .get ("standard" ));
84256
@@ -93,6 +265,115 @@ public void testDescribeResolvesBestSetters() {
93265 Cat .class ,
94266 resolvedAttributes .get ("ambiguous" ));
95267
96- assertEquals (Set .of ("standard" , "ride" , "pet" , "ambiguous" ), resolvedAttributes .keySet ());
268+ assertEquals (
269+ "Disjoint getter/setter types should fallback to the available setter parameter type" ,
270+ String .class ,
271+ resolvedAttributes .get ("mismatchedToken" ));
272+
273+ assertEquals (
274+ "Array types should be resolved to their component type" ,
275+ String .class ,
276+ resolvedAttributes .get ("items" ));
277+
278+ assertEquals (
279+ "When no exact match exists between getters and setters, array setters should be preferred" ,
280+ String .class ,
281+ resolvedAttributes .get ("arrayFallback" ));
282+
283+ assertEquals (
284+ "When multiple setters exist in an inheritance hierarchy, the most specific subtype should win" ,
285+ C_Square .class ,
286+ resolvedAttributes .get ("shapeSubtype" ));
287+
288+ assertEquals (
289+ "Concrete class should win over interface when both are candidates" ,
290+ A_Concrete .class ,
291+ resolvedAttributes .get ("concreteWins" ));
292+
293+ assertEquals (
294+ "Alphabetical fallback should resolve to the alphabetically first class" ,
295+ V_Class .class ,
296+ resolvedAttributes .get ("reverseAlphabetical" ));
297+
298+ assertEquals (
299+ "Should map primitive setter with wrapper getter" ,
300+ int .class ,
301+ resolvedAttributes .get ("primitiveSetter" ));
302+
303+ assertEquals (
304+ "Should map wrapper setter with primitive getter" ,
305+ Integer .class ,
306+ resolvedAttributes .get ("wrapperSetter" ));
307+
308+ assertEquals (boolean .class , resolvedAttributes .get ("primitiveBoolean" ));
309+ assertEquals (long .class , resolvedAttributes .get ("primitiveLong" ));
310+ assertEquals (double .class , resolvedAttributes .get ("primitiveDouble" ));
311+ assertEquals (float .class , resolvedAttributes .get ("primitiveFloat" ));
312+ assertEquals (byte .class , resolvedAttributes .get ("primitiveByte" ));
313+ assertEquals (char .class , resolvedAttributes .get ("primitiveChar" ));
314+ assertEquals (short .class , resolvedAttributes .get ("primitiveShort" ));
315+ assertEquals (String .class , resolvedAttributes .get ("voidEdgeCase" ));
316+
317+ Attribute <DummyTarget , ?> itemsAttr = attributes .stream ()
318+ .filter (a -> a .getName ().equals ("items" ))
319+ .findFirst ()
320+ .orElseThrow (() -> new AssertionError ("items attribute not found" ));
321+ assertTrue ("items attribute should be marked as multiple" , itemsAttr .isMultiple ());
322+
323+ assertEquals (
324+ Set .of (
325+ "standard" ,
326+ "ride" ,
327+ "pet" ,
328+ "ambiguous" ,
329+ "mismatchedToken" ,
330+ "items" ,
331+ "arrayFallback" ,
332+ "shapeSubtype" ,
333+ "concreteWins" ,
334+ "reverseAlphabetical" ,
335+ "primitiveSetter" ,
336+ "wrapperSetter" ,
337+ "primitiveBoolean" ,
338+ "primitiveLong" ,
339+ "primitiveDouble" ,
340+ "primitiveFloat" ,
341+ "primitiveByte" ,
342+ "primitiveChar" ,
343+ "primitiveShort" ,
344+ "voidEdgeCase" ),
345+ resolvedAttributes .keySet ());
346+ }
347+
348+ @ Test
349+ @ SuppressWarnings ({"unchecked" , "rawtypes" })
350+ public void testCollectionToArrayConversion () throws Exception {
351+ DummyConfigurator configurator = new DummyConfigurator ();
352+ Set <Attribute <DummyTarget , ?>> attributes = configurator .describe ();
353+
354+ Attribute <DummyTarget , ?> itemsAttr = attributes .stream ()
355+ .filter (a -> a .getName ().equals ("items" ))
356+ .findFirst ()
357+ .orElseThrow (() -> new AssertionError ("items attribute not found" ));
358+
359+ DummyTarget target = new DummyTarget ();
360+
361+ List <String > inputCollection = Arrays .asList ("foo" , "bar" , "baz" );
362+
363+ ((Attribute ) itemsAttr ).setValue (target , inputCollection );
364+
365+ String [] result = target .getItems ();
366+ assertNotNull ("Array should have been set" , result );
367+ assertEquals ("Array should have the same size as the collection" , 3 , result .length );
368+ assertArrayEquals (new String [] {"foo" , "bar" , "baz" }, result );
369+ }
370+
371+ @ Test
372+ public void testFindGetterReturnsNullForMissingOrInvalidGetters () {
373+ NoGetterConfigurator configurator = new NoGetterConfigurator ();
374+
375+ Set <Attribute <NoGetterTarget , ?>> attributes = configurator .describe ();
376+
377+ assertTrue ("Properties without valid getters should yield no attributes" , attributes .isEmpty ());
97378 }
98379}
0 commit comments