File size: 4,248 Bytes
1a12965 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | """Unit tests for src.analysis.validation_engine."""
from __future__ import annotations
import copy
from src.analysis.validation_engine import ValidationEngine, summarize
from src.config.schema import DifficultyWeights, ValidationConfig
VALID_METADATA = {
"document_id": "sample_000001",
"template": "bank_doc_01",
"page": 1,
"page_size": [1000, 800],
"signer": {"name": "Jane Doe", "role": "Account Holder"},
"signature_source": {"signature_id": "sig_0001", "path": "dataset/genuine/x.png"},
"placement": {"rotation_deg": 0.0},
"rendering": {"blend_mode": "multiply"},
"difficulty": {
"misplacement": 0.6, "whitespace": 0.5, "faintness": 0.5, "smallness": 0.5, "low_texture": 0.5,
"score": round(0.35 * 0.6 + 0.20 * 0.5 + 0.20 * 0.5 + 0.15 * 0.5 + 0.10 * 0.5, 4),
"tier": "medium",
},
"quality": {"degraded": False},
"verification": {
"case_type": None, "verdict": None, "reject_code": None,
"target_box_id": None, "expected_signer_group": None, "expected_signer_script": None,
"actual_signer_group": None, "actual_source_type": None, "actual_signer_script": None,
"companion": None,
},
"augmentation": {},
"geometry": {
"detected": True,
"ink_pixels": 120,
"target_box": [100, 100, 200, 150],
"actual_box": [110, 105, 190, 145],
"signature_bbox_px": [110, 105, 190, 145],
"box_px": [100, 100, 200, 150],
"signature_polygon_px": [[110, 105], [190, 105], [190, 145], [110, 145]],
},
"metrics": {"box_coverage_pct": 80.0},
}
def _engine(strict: bool = False) -> ValidationEngine:
return ValidationEngine(ValidationConfig(strict=strict), DifficultyWeights())
def test_valid_metadata_passes():
report = _engine().validate(copy.deepcopy(VALID_METADATA))
assert report.passed, [i.message for i in report.issues]
assert not report.errors
def test_missing_top_level_key_fails():
bad = copy.deepcopy(VALID_METADATA)
del bad["geometry"]
report = _engine().validate(bad)
assert not report.passed
assert any("geometry" in i.field for i in report.errors)
def test_signature_not_detected_fails():
bad = copy.deepcopy(VALID_METADATA)
bad["geometry"]["detected"] = False
report = _engine().validate(bad)
assert not report.passed
def test_degenerate_polygon_fails():
bad = copy.deepcopy(VALID_METADATA)
bad["geometry"]["signature_polygon_px"] = [[0, 0], [1, 1]]
report = _engine().validate(bad)
assert not report.passed
def test_invalid_box_ordering_fails():
bad = copy.deepcopy(VALID_METADATA)
bad["geometry"]["target_box"] = [200, 100, 100, 150] # x2 < x1
report = _engine().validate(bad)
assert not report.passed
def test_box_outside_page_is_warning_not_error_by_default():
bad = copy.deepcopy(VALID_METADATA)
bad["geometry"]["target_box"] = [900, 700, 1200, 900] # exceeds page_size [1000, 800]
report = _engine(strict=False).validate(bad)
assert report.passed # warning only, not strict
assert report.warnings
report_strict = _engine(strict=True).validate(bad)
assert not report_strict.passed
def test_nan_value_fails():
bad = copy.deepcopy(VALID_METADATA)
bad["metrics"]["box_coverage_pct"] = float("nan")
report = _engine().validate(bad)
assert not report.passed
def test_difficulty_score_mismatch_fails():
bad = copy.deepcopy(VALID_METADATA)
bad["difficulty"]["score"] = 0.999 # inconsistent with components
report = _engine().validate(bad)
assert not report.passed
def test_difficulty_tier_mismatch_fails():
bad = copy.deepcopy(VALID_METADATA)
bad["difficulty"]["tier"] = "hard" # inconsistent with score ~0.49 -> medium
report = _engine().validate(bad)
assert not report.passed
def test_summarize_batch():
good = _engine().validate(copy.deepcopy(VALID_METADATA))
bad_meta = copy.deepcopy(VALID_METADATA)
bad_meta["geometry"]["detected"] = False
bad = _engine().validate(bad_meta)
summary = summarize([good, bad])
assert summary.total == 2
assert summary.passed == 1
assert summary.failed == 1
assert "sample_000001" in summary.failed_sample_ids
|