glam/scripts/test_relationship_validation.py
2025-12-05 15:30:23 +01:00

255 lines
8.5 KiB
Python

#!/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()