#!/usr/bin/env python3 """ Test script for relationship domain/range constraint validation. Tests the validate_relationship_constraints() function with various valid and invalid relationship configurations. """ import sys from pathlib import Path # Add src to path sys.path.insert(0, str(Path(__file__).parent.parent / "src")) from glam_extractor.annotators.base import ( validate_relationship_constraints, _type_matches_constraint, RELATIONSHIP_CONSTRAINTS, ) def test_type_matching(): """Test the hierarchical type matching logic.""" print("\n" + "="*60) print("Testing _type_matches_constraint()") print("="*60) # Test exact match assert _type_matches_constraint("AGT.PER", ["AGT.PER"]) == True print(" [PASS] Exact match: AGT.PER in [AGT.PER]") # Test subtype match (AGT.PER matches parent AGT) assert _type_matches_constraint("AGT.PER", ["AGT"]) == True print(" [PASS] Subtype match: AGT.PER in [AGT]") # Test GRP.HER matches GRP assert _type_matches_constraint("GRP.HER", ["GRP"]) == True print(" [PASS] Subtype match: GRP.HER in [GRP]") # Test parent matches if child is allowed assert _type_matches_constraint("AGT", ["AGT.PER"]) == True print(" [PASS] Parent match: AGT in [AGT.PER]") # Test mismatch assert _type_matches_constraint("WRK.TXT", ["AGT"]) == False print(" [PASS] Mismatch: WRK.TXT NOT in [AGT]") # Test empty list (any type allowed) assert _type_matches_constraint("WRK.VIS", []) == True print(" [PASS] Empty list: WRK.VIS in []") # Test None type assert _type_matches_constraint(None, ["AGT"]) == False print(" [PASS] None type: None NOT in [AGT]") print("\n All type matching tests PASSED!") def test_valid_relationships(): """Test relationships that should pass validation.""" print("\n" + "="*60) print("Testing VALID relationships") print("="*60) test_cases = [ # REL.CRE.AUT - Author relationship ("REL.CRE.AUT", "AGT.PER", "WRK.TXT", "Martin Luther authored 95 Theses"), # REL.CRE.ART - Artist created artwork ("REL.CRE.ART", "AGT.PER", "WRK.VIS", "Rembrandt created The Night Watch"), # REL.SOC.FAM.SPO - Spouse relationship ("REL.SOC.FAM.SPO", "AGT.PER", "AGT.PER", "Martin Luther married Katharina von Bora"), # REL.CUS.KEP - Kept by heritage institution ("REL.CUS.KEP", "WRK.VIS", "GRP.HER", "The Night Watch kept by Rijksmuseum"), # REL.SPA.LOC - Location relationship ("REL.SPA.LOC", "GRP.HER", "TOP.SET", "Rijksmuseum located in Amsterdam"), # REL.ORG.FND - Founded by ("REL.ORG.FND", "AGT.PER", "GRP.HER", "Person founded museum"), # REL.SOC.MEM - Membership ("REL.SOC.MEM", "AGT.PER", "GRP.HER", "Person member of organization"), # REL.WRK.PRT - Part of work ("REL.WRK.PRT", "WRK.TXT", "WRK.SER", "Book part of series"), ] all_passed = True for rel_type, subject, obj, description in test_cases: result = validate_relationship_constraints(rel_type, subject, obj) if result.is_valid: print(f" [PASS] {rel_type}: {description}") else: print(f" [FAIL] {rel_type}: {description}") print(f" Warnings: {result.warnings}") all_passed = False if all_passed: print(f"\n All {len(test_cases)} valid relationship tests PASSED!") else: print(f"\n Some valid relationship tests FAILED!") def test_invalid_relationships(): """Test relationships that should fail validation (generate warnings).""" print("\n" + "="*60) print("Testing INVALID relationships (should generate warnings)") print("="*60) test_cases = [ # Wrong domain - place cannot be author ("REL.CRE.AUT", "TOP.SET", "WRK.TXT", "Place as author (invalid)"), # Wrong range - person cannot be authored ("REL.CRE.AUT", "AGT.PER", "AGT.PER", "Person authored person (invalid)"), # Wrong domain - work cannot have spouse ("REL.SOC.FAM.SPO", "WRK.TXT", "AGT.PER", "Work as spouse (invalid)"), # Wrong range - person as manifestation ("REL.WRK.MAN", "WRK.MAN", "AGT.PER", "Person as expression (invalid)"), # Wrong domain for organizational ("REL.ORG.PAR", "AGT.PER", "GRP", "Person as parent org (invalid)"), ] all_passed = True for rel_type, subject, obj, description in test_cases: result = validate_relationship_constraints(rel_type, subject, obj) if not result.is_valid and result.warnings: print(f" [PASS] {rel_type}: {description}") print(f" Warning: {result.warnings[0][:60]}...") else: print(f" [FAIL] {rel_type}: Expected warnings but got none") all_passed = False if all_passed: print(f"\n All {len(test_cases)} invalid relationship tests PASSED!") else: print(f"\n Some invalid relationship tests FAILED!") def test_strict_mode(): """Test that strict mode converts warnings to errors.""" print("\n" + "="*60) print("Testing STRICT mode validation") print("="*60) # Invalid relationship result_soft = validate_relationship_constraints( "REL.CRE.AUT", "TOP.SET", "WRK.TXT", strict=False ) result_strict = validate_relationship_constraints( "REL.CRE.AUT", "TOP.SET", "WRK.TXT", strict=True ) # Soft mode should have warnings assert len(result_soft.warnings) > 0 and len(result_soft.errors) == 0 print(" [PASS] Soft mode: Generates warnings, no errors") # Strict mode should have errors assert len(result_strict.errors) > 0 and len(result_strict.warnings) == 0 print(" [PASS] Strict mode: Generates errors, no warnings") print("\n All strict mode tests PASSED!") def test_constraint_coverage(): """Verify all relationship types have constraints defined.""" print("\n" + "="*60) print("Testing constraint coverage") print("="*60) # Core relationship types that MUST have constraints required_types = [ "REL.CRE", "REL.CRE.AUT", "REL.CRE.ART", "REL.SOC", "REL.SOC.FAM", "REL.SOC.FAM.SPO", "REL.CUS", "REL.CUS.KEP", "REL.CUS.OWN", "REL.ORG", "REL.ORG.PAR", "REL.WRK", "REL.WRK.EXP", "REL.SPA", "REL.SPA.LOC", "REL.EVT", "REL.EVT.PAR", ] missing = [] for rel_type in required_types: if rel_type not in RELATIONSHIP_CONSTRAINTS: missing.append(rel_type) if missing: print(f" [FAIL] Missing constraints for: {missing}") else: print(f" [PASS] All {len(required_types)} required relationship types have constraints") print(f"\n Total constraints defined: {len(RELATIONSHIP_CONSTRAINTS)}") def test_validation_result_details(): """Test that validation results contain expected details.""" print("\n" + "="*60) print("Testing validation result details") print("="*60) result = validate_relationship_constraints( "REL.CRE.AUT", "TOP.SET", "WRK.TXT" ) # Check result fields assert result.relationship_type == "REL.CRE.AUT" print(f" [PASS] relationship_type: {result.relationship_type}") assert result.subject_type == "TOP.SET" print(f" [PASS] subject_type: {result.subject_type}") assert result.object_type == "WRK.TXT" print(f" [PASS] object_type: {result.object_type}") assert len(result.expected_domains) > 0 print(f" [PASS] expected_domains: {result.expected_domains}") assert len(result.expected_ranges) > 0 print(f" [PASS] expected_ranges: {result.expected_ranges}") assert result.domain_valid == False print(f" [PASS] domain_valid: {result.domain_valid}") assert result.range_valid == True # WRK.TXT is valid print(f" [PASS] range_valid: {result.range_valid}") print("\n All validation result detail tests PASSED!") def main(): """Run all tests.""" print("\n" + "="*60) print("RELATIONSHIP CONSTRAINT VALIDATION TESTS") print("="*60) test_type_matching() test_valid_relationships() test_invalid_relationships() test_strict_mode() test_constraint_coverage() test_validation_result_details() print("\n" + "="*60) print("ALL TESTS COMPLETED SUCCESSFULLY!") print("="*60 + "\n") if __name__ == "__main__": main()