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.junit.bct;
18  
19  import static org.apache.juneau.junit.bct.BctAssertions.*;
20  import static org.junit.jupiter.api.Assertions.*;
21  
22  import java.util.*;
23  import java.util.function.*;
24  import java.util.stream.*;
25  
26  import org.apache.juneau.*;
27  import org.junit.jupiter.api.*;
28  import org.opentest4j.*;
29  
30  /**
31   * Unit tests for the {@link Assertions2} class.
32   *
33   * <p>This test class focuses on testing the assertion methods' behavior, error handling,
34   * and argument passing. The underlying BeanConverter functionality is tested separately
35   * in BasicBeanConverter_Test.</p>
36   */
37  class BctAssertions_Test extends TestBase {
38  
39  	// ====================================================================================================
40  	// AssertionArgs Tests
41  	// ====================================================================================================
42  
43  	@Nested
44  	class A_assertionArgs extends TestBase {
45  
46  		@Test
47  		void a01_args() {
48  			var args = args();
49  			assertNotNull(args);
50  			assertTrue(args.getBeanConverter().isEmpty());
51  			assertNull(args.getMessage());
52  		}
53  	}
54  
55  	// ====================================================================================================
56  	// Bean Property Tests
57  	// ====================================================================================================
58  
59  	@Nested
60  	class B_assertBean extends TestBase {
61  
62  		@Test
63  		void b01_basicAssertion() {
64  			var person = new TestPerson("Alice", 25);
65  
66  			// Test successful assertion
67  			assertDoesNotThrow(() -> assertBean(person, "name", "Alice"));
68  		}
69  
70  		@Test
71  		void b02_withCustomArgs() {
72  			var person = new TestPerson("Bob", 30);
73  			var args = args().setMessage("Custom message");
74  
75  			assertDoesNotThrow(() -> assertBean(args, person, "name", "Bob"));
76  		}
77  
78  		@Test
79  		void b03_nullObject() {
80  			var e = assertThrows(AssertionFailedError.class, () -> assertBean(null, "name", "test"));
81  			assertContains("Actual was null", e.getMessage());
82  		}
83  
84  		@Test
85  		void b04_assertionFailure() {
86  			var person = new TestPerson("Eve", 28);
87  
88  			var e = assertThrows(AssertionFailedError.class, () -> assertBean(person, "name", "Wrong"));
89  			assertContains("expected: <Wrong>", e.getMessage());
90  			assertContains("but was: <Eve>", e.getMessage());
91  		}
92  
93  		@Test
94  		void b05_customMessage() {
95  			var person = new TestPerson("Charlie", 35);
96  			var args = args().setMessage("Custom error message");
97  
98  			var e = assertThrows(AssertionFailedError.class, () -> assertBean(args, person, "name", "Wrong"));
99  			assertContains("Custom error message", e.getMessage());
100 		}
101 	}
102 
103 	// ====================================================================================================
104 	// Multiple Beans Tests
105 	// ====================================================================================================
106 
107 	@Nested
108 	class C_assertBeans extends TestBase {
109 
110 		@Test
111 		void c01_basicBeansAssertion() {
112 			var people = List.of(new TestPerson("Alice", 25), new TestPerson("Bob", 30));
113 
114 			assertDoesNotThrow(() -> assertBeans(people, "name", "Alice", "Bob"));
115 		}
116 
117 		@Test
118 		void c02_withCustomArgs() {
119 			var people = List.of(new TestPerson("Charlie", 35));
120 			var args = args().setMessage("Custom beans message");
121 
122 			assertDoesNotThrow(() -> assertBeans(args, people, "name", "Charlie"));
123 		}
124 
125 		@Test
126 		void c03_emptyCollection() {
127 			assertDoesNotThrow(() -> assertBeans(List.of(), "name"));
128 		}
129 
130 		@Test
131 		void c04_sizeMismatch() {
132 			var people = List.of(new TestPerson("Dave", 40));
133 
134 			var e = assertThrows(AssertionFailedError.class, () -> assertBeans(people, "name", "Dave", "Extra"));
135 			assertContains("Wrong number of beans", e.getMessage());
136 		}
137 
138 		@Test
139 		void c05_contentMismatch() {
140 			var people = List.of(new TestPerson("Eve", 28));
141 
142 			var e = assertThrows(AssertionFailedError.class, () -> assertBeans(people, "name", "Wrong"));
143 			assertContains("Bean at row", e.getMessage());
144 		}
145 
146 		@Test
147 		void c06_nullCollection() {
148 			var e = assertThrows(AssertionFailedError.class, () -> assertBeans((Object)null, "name", "test"));
149 			assertContains("Value was null", e.getMessage());
150 		}
151 	}
152 
153 	// ====================================================================================================
154 	// Mapped Property Tests
155 	// ====================================================================================================
156 
157 	@Nested
158 	class D_assertMapped extends TestBase {
159 
160 		@Test
161 		void d01_basicMapping() {
162 			var person = new TestPerson("Grace", 27);
163 			BiFunction<TestPerson, String, Object> mapper = (p, prop) -> p.getName().toUpperCase();
164 
165 			assertDoesNotThrow(() -> assertMapped(person, mapper, "upperName", "GRACE"));
166 		}
167 
168 		@Test
169 		void d02_withCustomArgs() {
170 			var person = new TestPerson("Henry", 45);
171 			var args = args().setMessage("Custom mapped message");
172 			BiFunction<TestPerson, String, Object> mapper = (p, prop) -> p.getName();
173 
174 			assertDoesNotThrow(() -> assertMapped(args, person, mapper, "name", "Henry"));
175 		}
176 
177 		@Test
178 		void d03_mappingMismatch() {
179 			var person = new TestPerson("Jack", 50);
180 			BiFunction<TestPerson, String, Object> mapper = (p, prop) -> p.getName();
181 
182 			var e = assertThrows(AssertionFailedError.class, () -> assertMapped(person, mapper, "name", "Wrong"));
183 			assertContains("expected: <Wrong>", e.getMessage());
184 			assertContains("but was: <Jack>", e.getMessage());
185 		}
186 	}
187 
188 	// ====================================================================================================
189 	// String Contains Tests
190 	// ====================================================================================================
191 
192 	@Nested
193 	class E_assertContains extends TestBase {
194 
195 		@Test
196 		void e01_basicContains() {
197 			assertDoesNotThrow(() -> assertContains("Hello", "Hello World"));
198 		}
199 
200 		@Test
201 		void e02_withCustomArgs() {
202 			var args = args().setMessage("Custom contains message");
203 
204 			assertDoesNotThrow(() -> assertContains(args, "Test", "Test String"));
205 		}
206 
207 		@Test
208 		void e03_doesNotContain() {
209 			var e = assertThrows(AssertionFailedError.class, () -> assertContains("Missing", "Hello World"));
210 			assertContains("String did not contain expected substring", e.getMessage());
211 		}
212 
213 		@Test
214 		void e04_nullValue() {
215 			var e = assertThrows(IllegalArgumentException.class, () -> assertContains("test", null));
216 			assertContains("cannot be null", e.getMessage());
217 		}
218 	}
219 
220 	// ====================================================================================================
221 	// Contains All Tests
222 	// ====================================================================================================
223 
224 	@Nested
225 	class F_assertContainsAll extends TestBase {
226 
227 		@Test
228 		void f01_basicContainsAll() {
229 			assertDoesNotThrow(() -> assertContainsAll("Hello World", "Hello", "World"));
230 		}
231 
232 		@Test
233 		void f02_withCustomArgs() {
234 			var args = args().setMessage("Custom contains all message");
235 
236 			assertDoesNotThrow(() -> assertContainsAll(args, (Object)"Testing", "Test"));
237 		}
238 
239 		@Test
240 		void f03_missingSubstring() {
241 			var e = assertThrows(AssertionFailedError.class, () -> assertContainsAll("Hello World", "Hello", "Missing"));
242 			assertContains("String did not contain expected substring", e.getMessage());
243 			assertContains("Missing", e.getMessage());
244 		}
245 
246 		@Test
247 		void f04_nullValue() {
248 			var e = assertThrows(AssertionFailedError.class, () -> assertContainsAll((Object)null, "test"));
249 			assertContains("Value was null", e.getMessage());
250 		}
251 
252 		@Test
253 		void f05_multipleErrors() {
254 			// Test that multiple missing substrings are collected and reported together
255 			var text = "Hello World Testing";
256 			var expected = new String[]{"Hello", "Missing1", "World", "Missing2", "Testing"};
257 
258 			var e = assertThrows(AssertionFailedError.class, () -> assertContainsAll(text, expected));
259 
260 			// Should report multiple errors in a single assertion failure
261 			var message = e.getMessage();
262 			assertContains("2 substring assertions failed", message);
263 			assertContains("String did not contain expected substring", message);
264 
265 			// Should mention both missing substrings
266 			assertContains("Missing1", message);
267 			assertContains("Missing2", message);
268 
269 			// Should include the actual text
270 			assertContains("Hello World Testing", message);
271 		}
272 
273 		@Test
274 		void f06_singleError() {
275 			// Test that single errors are still reported as single assertion failures
276 			var text = "Hello World";
277 			var expected = new String[]{"Hello", "Missing", "World"};
278 
279 			var e = assertThrows(AssertionFailedError.class, () -> assertContainsAll(text, expected));
280 
281 			// Should report single error normally (not as "1 substring assertions failed")
282 			var message = e.getMessage();
283 			assertDoesNotThrow(() -> assertTrue(!message.contains("1 substring assertions failed")));
284 			assertContains("String did not contain expected substring", message);
285 			assertContains("Missing", message);
286 		}
287 	}
288 
289 	// ====================================================================================================
290 	// Empty Tests
291 	// ====================================================================================================
292 
293 	@Nested
294 	class G_assertEmpty extends TestBase {
295 
296 		@Test
297 		void g01_basicEmpty() {
298 			assertDoesNotThrow(() -> assertEmpty(List.of()));
299 			assertDoesNotThrow(() -> assertEmpty(new String[0]));
300 		}
301 
302 		@Test
303 		void g02_withCustomArgs() {
304 			var args = args().setMessage("Custom empty message");
305 
306 			assertDoesNotThrow(() -> assertEmpty(args, List.of()));
307 		}
308 
309 		@Test
310 		void g03_notEmpty() {
311 			var e = assertThrows(AssertionFailedError.class, () -> assertEmpty(List.of("item")));
312 			assertContains("Value was not empty", e.getMessage());
313 		}
314 
315 		@Test
316 		void g04_nullValue() {
317 			var e = assertThrows(AssertionError.class, () -> assertEmpty(null));
318 			assertContains("Value was null", e.getMessage());
319 		}
320 	}
321 
322 	// ====================================================================================================
323 	// List Tests
324 	// ====================================================================================================
325 
326 	@Nested
327 	class H_assertList extends TestBase {
328 
329 		@Test
330 		void h01_basicList() {
331 			assertDoesNotThrow(() -> assertList(List.of("a", "b", "c"), "a", "b", "c"));
332 		}
333 
334 		@Test
335 		void h02_withCustomArgs() {
336 			var args = args().setMessage("Custom list message");
337 
338 			assertDoesNotThrow(() -> assertList(args, List.of(1, 2), 1, 2));
339 		}
340 
341 		@Test
342 		void h03_sizeMismatch() {
343 			var e = assertThrows(AssertionFailedError.class, () -> assertList(List.of("a", "b"), "a", "b", "c"));
344 			assertContains("Wrong list length", e.getMessage());
345 		}
346 
347 		@Test
348 		void h04_elementMismatch() {
349 			var e = assertThrows(AssertionFailedError.class, () -> assertList(List.of("a", "b"), "a", "wrong"));
350 			assertContains("Element at index 1 did not match", e.getMessage());
351 		}
352 
353 		@Test
354 		void h05_nullValue() {
355 			var e = assertThrows(IllegalArgumentException.class, () -> assertList(null, "test"));
356 			assertContains("cannot be null", e.getMessage());
357 		}
358 
359 		@Test
360 		void h06_predicateValidation() {
361 			// Test lines 765-766: Predicate-based element validation
362 			var args = args().setMessage("Custom predicate message");
363 			var numbers = Arrays.asList(1, 2, 3, 4, 5);
364 
365 			// Test successful predicate validation
366 			assertDoesNotThrow(() -> assertList(args, numbers,
367 				(Predicate<Integer>) x -> x == 1,   // First element should equal 1
368 				(Predicate<Integer>) x -> x > 1,    // Second element should be > 1
369 				"3",                                // Third element as string
370 				(Predicate<Integer>) x -> x % 2 == 0, // Fourth element should be even
371 				(Predicate<Integer>) x -> x == 5     // Fifth element should equal 5
372 			));
373 
374 			// Test failed predicate validation - use single element list to avoid length mismatch
375 			var singleNumber = Arrays.asList(1);
376 			var e = assertThrows(AssertionFailedError.class, () ->
377 				assertList(args, singleNumber, (Predicate<Integer>) x -> x == 99)); // Should fail
378 			assertContains("Element at index 0 did not pass predicate", e.getMessage());
379 			assertContains("actual: <1>", e.getMessage());
380 		}
381 
382 		@Test
383 		void h07_multipleErrors() {
384 			// Test that multiple assertion errors are collected and reported together
385 			var list = Arrays.asList("a", "wrong1", "c", "wrong2", "e");
386 			var expected = new Object[]{"a", "b", "c", "d", "e"};
387 
388 			var e = assertThrows(AssertionFailedError.class, () -> assertList(list, expected));
389 
390 			// Should report multiple errors in a single assertion failure
391 			var message = e.getMessage();
392 			assertContains("2 list assertions failed", message);
393 			assertContains("Element at index 1 did not match", message);
394 			assertContains("Element at index 3 did not match", message);
395 
396 			// Should include both expected and actual values
397 			assertContains("expected: <b>", message);
398 			assertContains("but was: <wrong1>", message);
399 			assertContains("expected: <d>", message);
400 			assertContains("but was: <wrong2>", message);
401 		}
402 
403 		@Test
404 		void h08_singleError() {
405 			// Test that single errors are still reported as single assertion failures
406 			var list = Arrays.asList("a", "wrong", "c");
407 			var expected = new Object[]{"a", "b", "c"};
408 
409 			var e = assertThrows(AssertionFailedError.class, () -> assertList(list, expected));
410 
411 			// Should report single error normally (not as "1 list assertions failed")
412 			var message = e.getMessage();
413 			assertDoesNotThrow(() -> assertTrue(!message.contains("1 list assertions failed")));
414 			assertContains("Element at index 1 did not match", message);
415 		}
416 	}
417 
418 	// ====================================================================================================
419 	// Map Tests
420 	// ====================================================================================================
421 
422 	@Nested
423 	class H_assertMap extends TestBase {
424 
425 		@Test
426 		void h01_basicMap() {
427 			assertDoesNotThrow(() -> assertMap(Map.of("a", "1", "b", "2"), "a=1", "b=2"));
428 		}
429 
430 		@Test
431 		void h02_withCustomArgs() {
432 			var args = args().setMessage("Custom map message");
433 			assertDoesNotThrow(() -> assertMap(args, Map.of("key", "value"), "key=value"));
434 		}
435 
436 		@Test
437 		void h03_sizeMismatch() {
438 			var e = assertThrows(AssertionFailedError.class, () -> assertMap(Map.of("a", "1"), "a=1", "b=2"));
439 			assertContains("Wrong list length", e.getMessage());
440 		}
441 
442 		@Test
443 		void h04_elementMismatch() {
444 			var e = assertThrows(AssertionFailedError.class, () -> assertMap(Map.of("a", "1", "b", "2"), "a=1", "b=wrong"));
445 			assertContains("Element at index 1 did not match", e.getMessage());
446 		}
447 
448 		@Test
449 		void h05_nullValue() {
450 			var e = assertThrows(AssertionFailedError.class, () -> assertMap(null, "test"));
451 			assertContains("Value was null", e.getMessage());
452 		}
453 
454 		@Test
455 		void h06_predicateValidation() {
456 			// Test predicate-based map entry validation
457 			var args = args().setMessage("Custom predicate message");
458 			var map = Map.of("count", 42, "enabled", true);
459 
460 			// Test successful predicate validation
461 			assertDoesNotThrow(() -> assertMap(args, map,
462 				(Predicate<Map.Entry<String, Object>>) entry -> entry.getKey().equals("count") && entry.getValue().equals(42),
463 				(Predicate<Map.Entry<String, Object>>) entry -> entry.getKey().equals("enabled") && entry.getValue().equals(true)
464 			));
465 
466 			// Test failed predicate validation
467 			var singleEntryMap = Map.of("count", 1);
468 			var e = assertThrows(AssertionFailedError.class, () ->
469 				assertMap(args, singleEntryMap, (Predicate<Map.Entry<String, Object>>) entry -> entry.getValue().equals(99))); // Should fail
470 			assertContains("Element at index 0 did not pass predicate", e.getMessage());
471 			assertContains("actual: <count=1>", e.getMessage());
472 		}
473 
474 		@Test
475 		void h07_multipleErrors() {
476 			// Test that multiple assertion errors are collected and reported together
477 			var map = Map.of("a", "1", "b", "wrong1", "c", "3", "d", "wrong2");
478 			var expected = new Object[]{"a=1", "b=2", "c=3", "d=4"};
479 
480 			var e = assertThrows(AssertionFailedError.class, () -> assertMap(map, expected));
481 
482 			// Should report multiple errors in a single assertion failure
483 			var message = e.getMessage();
484 			assertContains("2 list assertions failed", message);
485 			assertContains("Element at index 1 did not match", message);
486 			assertContains("Element at index 3 did not match", message);
487 
488 			// Should include both expected and actual values
489 			assertContains("expected: <b=2>", message);
490 			assertContains("but was: <b=wrong1>", message);
491 			assertContains("expected: <d=4>", message);
492 			assertContains("but was: <d=wrong2>", message);
493 		}
494 
495 		@Test
496 		void h08_singleError() {
497 			// Test that single errors are still reported as single assertion failures
498 			var map = Map.of("a", "1", "b", "wrong");
499 			var e = assertThrows(AssertionFailedError.class, () -> assertMap(map, "a=1", "b=2"));
500 
501 			// Should be a single assertion failure, not multiple
502 			var message = e.getMessage();
503 			assertDoesNotThrow(() -> assertContains("Element at index 1 did not match", message));
504 			assertDoesNotThrow(() -> assertContains("expected: <b=2>", message));
505 			assertDoesNotThrow(() -> assertContains("but was: <b=wrong>", message));
506 		}
507 
508 		@Test
509 		void h09_nestedMaps() {
510 			// Test nested map structures
511 			var nestedMap = Map.of("a", Map.of("b", 1));
512 			assertDoesNotThrow(() -> assertMap(nestedMap, "a={b=1}"));
513 		}
514 
515 		@Test
516 		void h10_mapsWithArrays() {
517 			// Test maps with array values
518 			var mapWithArrays = Map.of("a", Map.of("b", new Integer[]{1, 2}));
519 			assertDoesNotThrow(() -> assertMap(mapWithArrays, "a={b=[1,2]}"));
520 		}
521 
522 		@Test
523 		void h11_mapOrdering() {
524 			// Test that map ordering is deterministic (HashMap gets converted to TreeMap)
525 			var unorderedMap = new HashMap<String, String>();
526 			unorderedMap.put("z", "last");
527 			unorderedMap.put("a", "first");
528 			unorderedMap.put("m", "middle");
529 
530 			// Should be ordered by key: a=first, m=middle, z=last
531 			assertDoesNotThrow(() -> assertMap(unorderedMap, "a=first", "m=middle", "z=last"));
532 		}
533 
534 		@Test
535 		void h12_linkedHashMapOrdering() {
536 			// Test that LinkedHashMap preserves insertion order
537 			var linkedMap = new LinkedHashMap<String, String>();
538 			linkedMap.put("first", "1");
539 			linkedMap.put("second", "2");
540 			linkedMap.put("third", "3");
541 
542 			assertDoesNotThrow(() -> assertMap(linkedMap, "first=1", "second=2", "third=3"));
543 		}
544 
545 		@Test
546 		void h13_treeMapOrdering() {
547 			// Test that TreeMap preserves natural ordering
548 			var treeMap = new TreeMap<String, String>();
549 			treeMap.put("zebra", "last");
550 			treeMap.put("apple", "first");
551 			treeMap.put("monkey", "middle");
552 
553 			// Should be ordered by key: apple=first, monkey=middle, zebra=last
554 			assertDoesNotThrow(() -> assertMap(treeMap, "apple=first", "monkey=middle", "zebra=last"));
555 		}
556 	}
557 
558 	// ====================================================================================================
559 	// Not Empty Tests
560 	// ====================================================================================================
561 
562 	@Nested
563 	class I_assertNotEmpty extends TestBase {
564 
565 		@Test
566 		void i01_basicNotEmpty() {
567 			assertDoesNotThrow(() -> assertNotEmpty(List.of("item")));
568 			assertDoesNotThrow(() -> assertNotEmpty(new String[]{"item"}));
569 		}
570 
571 		@Test
572 		void i02_withCustomArgs() {
573 			var args = args().setMessage("Custom not empty message");
574 
575 			assertDoesNotThrow(() -> assertNotEmpty(args, List.of("content")));
576 		}
577 
578 		@Test
579 		void i03_actuallyEmpty() {
580 			var e = assertThrows(AssertionFailedError.class, () -> assertNotEmpty(List.of()));
581 			assertContains("Value was empty", e.getMessage());
582 		}
583 
584 		@Test
585 		void i04_nullValue() {
586 			var e = assertThrows(AssertionError.class, () -> assertNotEmpty(null));
587 			assertContains("Value was null", e.getMessage());
588 		}
589 	}
590 
591 	// ====================================================================================================
592 	// Size Tests
593 	// ====================================================================================================
594 
595 	@Nested
596 	class J_assertSize extends TestBase {
597 
598 		@Test
599 		void j01_basicSizes() {
600 			assertDoesNotThrow(() -> assertSize(3, List.of("a", "b", "c")));
601 			assertDoesNotThrow(() -> assertSize(5, "hello"));
602 		}
603 
604 		@Test
605 		void j02_withCustomArgs() {
606 			var args = args().setMessage("Custom size message");
607 
608 			assertDoesNotThrow(() -> assertSize(args, 2, List.of("a", "b")));
609 		}
610 
611 		@Test
612 		void j03_wrongSize() {
613 			var e = assertThrows(AssertionFailedError.class, () -> assertSize(5, List.of("a", "b", "c")));
614 			assertContains("Value not expected size", e.getMessage());
615 		}
616 
617 		@Test
618 		void j04_nullValue() {
619 			var e = assertThrows(AssertionError.class, () -> assertSize(0, null));
620 			assertContains("Value was null", e.getMessage());
621 		}
622 	}
623 
624 	// ====================================================================================================
625 	// String Tests
626 	// ====================================================================================================
627 
628 	@Nested
629 	class K_assertString extends TestBase {
630 
631 		@Test
632 		void k01_basicString() {
633 			assertDoesNotThrow(() -> assertString("hello", "hello"));
634 		}
635 
636 		@Test
637 		void k02_withCustomArgs() {
638 			var args = args().setMessage("Custom string message");
639 
640 			assertDoesNotThrow(() -> assertString(args, "test", "test"));
641 		}
642 
643 		@Test
644 		void k03_stringMismatch() {
645 			var e = assertThrows(AssertionFailedError.class, () -> assertString("expected", "actual"));
646 			assertContains("expected: <expected>", e.getMessage());
647 			assertContains("but was: <actual>", e.getMessage());
648 		}
649 
650 		@Test
651 		void k04_nullValue() {
652 			var e = assertThrows(AssertionError.class, () -> assertString("test", null));
653 			assertContains("Value was null", e.getMessage());
654 		}
655 	}
656 
657 	// ====================================================================================================
658 	// Pattern Matching Tests
659 	// ====================================================================================================
660 
661 	@Nested
662 	class L_assertMatchesGlob extends TestBase {
663 
664 		@Test
665 		void l01_basicGlobPatterns() {
666 			assertDoesNotThrow(() -> assertMatchesGlob("hello*", "hello world"));
667 			assertDoesNotThrow(() -> assertMatchesGlob("h?llo", "hello"));
668 		}
669 
670 		@Test
671 		void l02_withCustomArgs() {
672 			var args = args().setMessage("Custom glob message");
673 
674 			assertDoesNotThrow(() -> assertMatchesGlob(args, "test*", "testing"));
675 		}
676 
677 		@Test
678 		void l03_patternMismatch() {
679 			var e = assertThrows(AssertionFailedError.class, () -> assertMatchesGlob("hello*", "goodbye"));
680 			assertContains("Pattern didn't match", e.getMessage());
681 			assertContains("pattern: <hello*>", e.getMessage());
682 			assertContains("but was: <goodbye>", e.getMessage());
683 		}
684 
685 		@Test
686 		void l04_nullValue() {
687 			var e = assertThrows(AssertionError.class, () -> assertMatchesGlob("*", null));
688 			assertContains("Value was null", e.getMessage());
689 		}
690 
691 		@Test
692 		void l05_nullPattern() {
693 			var e = assertThrows(IllegalArgumentException.class, () -> assertMatchesGlob(null, "test"));
694 			assertContains("cannot be null", e.getMessage());
695 		}
696 	}
697 
698 	// ====================================================================================================
699 	// Test Helper Classes
700 	// ====================================================================================================
701 
702 	static class TestPerson {
703 		private final String name;
704 		private final int age;
705 		private TestAddress address;
706 
707 		TestPerson(String name, int age) {
708 			this(name, age, null);
709 		}
710 
711 		TestPerson(String name, int age, TestAddress address) {
712 			this.name = name;
713 			this.age = age;
714 			this.address = address;
715 		}
716 
717 		String getName() { return name; }
718 		int getAge() { return age; }
719 		TestAddress getAddress() { return address; }
720 		void setAddress(TestAddress address) { this.address = address; }
721 
722 		@Override
723 		public String toString() {
724 			return "TestPerson{name='" + name + "', age=" + age + "}";
725 		}
726 	}
727 
728 	static class TestAddress {
729 		private final String street;
730 		private final String city;
731 
732 		TestAddress(String street, String city) {
733 			this.street = street;
734 			this.city = city;
735 		}
736 
737 		String getStreet() { return street; }
738 		String getCity() { return city; }
739 
740 		@Override
741 		public String toString() {
742 			return "TestAddress{street='" + street + "', city='" + city + "'}";
743 		}
744 	}
745 
746 	// ====================================================================================================
747 	// Enhanced Edge Case Tests
748 	// ====================================================================================================
749 
750 	@Nested
751 	class H_enhancedEdgeCases extends TestBase {
752 
753 		@Test
754 		void h01_assertListWithMixedTypes() {
755 			// Test list with mixed data types
756 			var mixedList = Arrays.asList("string", 42, true, 3.14, null);
757 
758 			assertList(mixedList, "string", 42, true, 3.14, null);
759 			assertSize(5, mixedList);
760 			assertContains("42", mixedList); // Number stringified
761 			assertContains("true", mixedList); // Boolean stringified
762 		}
763 
764 		@Test
765 		void h02_assertMatchesGlobWithComplexPatterns() {
766 			// Test glob matching with various patterns
767 			var testStrings = Arrays.asList(
768 				"hello.txt", "test_file.log", "document.pdf",
769 				"IMG_001.jpg", "data.xml", "script.js"
770 			);
771 
772 			assertMatchesGlob("*.txt", testStrings.get(0));
773 			assertMatchesGlob("test_*", testStrings.get(1));
774 			assertMatchesGlob("*.pdf", testStrings.get(2));
775 			assertMatchesGlob("IMG_???.jpg", testStrings.get(3));
776 			assertMatchesGlob("*.xml", testStrings.get(4));
777 			assertMatchesGlob("script.*", testStrings.get(5));
778 		}
779 
780 		@Test
781 		void h03_assertEmptyWithVariousEmptyTypes() {
782 			// Test empty assertions with different empty types
783 			assertEmpty(Arrays.asList());
784 			assertEmpty(new HashMap<>());
785 			assertEmpty(new HashSet<>());
786 			assertEmpty(Optional.empty());
787 		}
788 
789 		@Test
790 		void h04_assertNotEmptyWithVariousNonEmptyTypes() {
791 			// Test non-empty assertions
792 			assertNotEmpty(Arrays.asList("item"));
793 			assertNotEmpty(Map.of("key", "value"));
794 			assertNotEmpty(Set.of("element"));
795 			assertNotEmpty(Optional.of("value"));
796 		}
797 
798 		@Test
799 		void h05_assertContainsAllWithPartialMatches() {
800 			// Test containsAll with partial string matches
801 			var text = "The quick brown fox jumps over the lazy dog";
802 
803 			assertContainsAll(text, "quick", "fox", "lazy");
804 			assertContainsAll(text, "The", "dog");
805 
806 			// Test with object that stringifies to contain multiple values
807 			var complexObj = Map.of(
808 				"description", "This is a test with multiple keywords: alpha, beta, gamma"
809 			);
810 			assertContainsAll(complexObj, "alpha", "beta", "gamma", "keywords");
811 		}
812 
813 		@Test
814 		void h06_assertSizeWithCustomListifiableObjects() {
815 			// Test size assertions with objects that can be converted to lists
816 			var stringArray = new String[]{"a", "b", "c"};
817 			var intArray = new int[]{1, 2, 3, 4, 5};
818 
819 			assertSize(3, stringArray);
820 			assertSize(5, intArray);
821 
822 			// Test with Stream (gets converted to list)
823 			assertSize(4, Stream.of("w", "x", "y", "z"));
824 		}
825 	}
826 }