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