File size: 9,435 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 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | """Tests for the case_type-driven verification test-case generation feature.
Covers: registry determinism, correct rendering/ground-truth for every one
of the 12 case types, companion-box behavior, and backward compatibility
with the existing (non-case-type) generation path.
"""
from __future__ import annotations
from src.config.loader import load_config
from src.generator import GeneratorContext
from src.verification.case_types import CASE_TABLE
from src.verification.registry import SignerIdentityRegistry
def _make_context(tmp_path, case_type: "str | None" = None, seed: int = 4242):
overrides = [f"seed={seed}", f"paths.output_dir={tmp_path.as_posix()}", "generation.count=1"]
if case_type is not None:
overrides.append(f"quality.case_type={case_type}")
config = load_config(overrides=overrides)
return GeneratorContext(config)
# --------------------------------------------------------------------- #
# Registry
# --------------------------------------------------------------------- #
def test_registry_authorized_signer_is_deterministic(tmp_path):
ctx1 = _make_context(tmp_path)
ctx2 = _make_context(tmp_path)
for template_id, template in ctx1.template_loader.discover().items():
for box in template.annotation.pages[0].signature_boxes:
a = ctx1.identity_registry.authorized_signer_group(template_id, box.id)
b = ctx2.identity_registry.authorized_signer_group(template_id, box.id)
assert a == b
def test_registry_display_name_is_stable_across_instances(tmp_path):
ctx1 = _make_context(tmp_path)
ctx2 = _make_context(tmp_path)
group = ctx1.identity_registry._signer_groups[0]
assert ctx1.identity_registry.display_name(group) == ctx2.identity_registry.display_name(group)
def test_registry_foreign_authorized_picks_a_different_template(tmp_path):
ctx = _make_context(tmp_path)
group, foreign_template_id, foreign_box_id = ctx.identity_registry.foreign_authorized("bank_doc_01")
assert foreign_template_id != "bank_doc_01"
assert group == ctx.identity_registry.authorized_signer_group(foreign_template_id, foreign_box_id)
# --------------------------------------------------------------------- #
# Per-case-type ground truth
# --------------------------------------------------------------------- #
def test_all_case_types_produce_matching_verdict_and_reject_code(tmp_path):
for code, spec in CASE_TABLE.items():
ctx = _make_context(tmp_path, case_type=code, seed=99)
metadata, _, report = ctx.run_sample(1)
v = metadata["verification"]
assert v["case_type"] == code
assert v["verdict"] == spec.verdict, f"{code}: verdict mismatch"
assert v["reject_code"] == spec.reject_code, f"{code}: reject_code mismatch"
assert report is not None and report.passed, (
f"{code}: validation failed: {[i.message for i in report.errors]}"
)
def test_pass_cases_use_the_authorized_signer(tmp_path):
for code in ("1a", "1b", "1d"):
ctx = _make_context(tmp_path, case_type=code, seed=17)
metadata, _, _ = ctx.run_sample(1)
v = metadata["verification"]
assert v["actual_signer_group"] == v["expected_signer_group"]
assert v["actual_source_type"] == "genuine"
assert v["companion"] is None
def test_multi_signer_pass_case_fills_companion_correctly(tmp_path):
ctx = _make_context(tmp_path, case_type="1c", seed=17)
metadata, _, _ = ctx.run_sample(1)
v = metadata["verification"]
assert v["actual_signer_group"] == v["expected_signer_group"]
assert v["companion"] is not None
assert v["companion"]["filled"] is True
assert v["companion"]["matches_expected"] is True
def test_blank_cases_have_no_actual_signer(tmp_path):
for code in ("2a", "5a"):
ctx = _make_context(tmp_path, case_type=code, seed=5)
metadata, _, _ = ctx.run_sample(1)
v = metadata["verification"]
assert v["actual_signer_group"] is None
assert v["actual_source_type"] == "none"
assert metadata["signature_source"]["signature_id"] is None
ctx_5a = _make_context(tmp_path, case_type="5a", seed=5)
metadata_5a, _, _ = ctx_5a.run_sample(1)
assert metadata_5a["verification"]["companion"]["filled"] is True
ctx_2a = _make_context(tmp_path, case_type="2a", seed=5)
metadata_2a, _, _ = ctx_2a.run_sample(1)
assert metadata_2a["verification"]["companion"] is None
def test_identity_mismatch_cases_use_a_different_signer(tmp_path):
for code in ("3a", "5b"):
ctx = _make_context(tmp_path, case_type=code, seed=23)
metadata, _, _ = ctx.run_sample(1)
v = metadata["verification"]
assert v["actual_signer_group"] != v["expected_signer_group"]
assert v["actual_source_type"] == "genuine"
def test_skilled_forgery_case_uses_forged_sample_of_the_authorized_signer(tmp_path):
ctx = _make_context(tmp_path, case_type="3b", seed=23)
metadata, _, _ = ctx.run_sample(1)
v = metadata["verification"]
assert v["actual_signer_group"] == v["expected_signer_group"]
assert v["actual_source_type"] == "forged"
def test_role_mismatch_cases_use_a_legitimately_registered_signer(tmp_path):
ctx = _make_context(tmp_path, case_type="4a", seed=31)
metadata, _, _ = ctx.run_sample(1)
v = metadata["verification"]
# The target box got the *companion* box's own authorized signer.
assert v["actual_signer_group"] == v["companion"]["signer_group"]
assert v["actual_signer_group"] != v["expected_signer_group"]
ctx_4b = _make_context(tmp_path, case_type="4b", seed=31)
metadata_4b, _, _ = ctx_4b.run_sample(1)
v4b = metadata_4b["verification"]
assert v4b["actual_signer_group"] != v4b["expected_signer_group"]
def test_printed_text_case_has_no_biometric_identity(tmp_path):
ctx = _make_context(tmp_path, case_type="2b", seed=11)
metadata, _, _ = ctx.run_sample(1)
v = metadata["verification"]
assert v["actual_signer_group"] is None
assert v["actual_source_type"] == "printed"
# --------------------------------------------------------------------- #
# Backward compatibility
# --------------------------------------------------------------------- #
def test_unset_case_type_is_unaffected(tmp_path):
ctx = _make_context(tmp_path, case_type=None, seed=1)
metadata, _, report = ctx.run_sample(1)
v = metadata["verification"]
assert v == {
"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,
}
assert report is not None and report.passed
# --------------------------------------------------------------------- #
# Multi-script signer pool (Hindi/Bengali/Latin named identities)
# --------------------------------------------------------------------- #
def test_display_name_passes_through_real_names_but_generates_for_anonymous_groups(tmp_path):
ctx = _make_context(tmp_path)
assert ctx.identity_registry.display_name("Priya Sharma") == "Priya Sharma"
generated = ctx.identity_registry.display_name("signer_0014")
assert generated != "signer_0014"
assert " " in generated # looks like "First Last"
def test_named_signers_are_part_of_the_authorized_pool(tmp_path):
ctx = _make_context(tmp_path)
groups = set(ctx.signature_dataset.signer_groups())
assert {"Priya Sharma", "Raj Malhotra", "Vikram Singh", "Suman Das", "Anika Roy", "Carol Wong", "Henry Ooi"} <= groups
def test_named_signer_flows_through_to_metadata_with_correct_script(tmp_path):
"""Find a sample that lands on a named, scripted signer and check it renders cleanly."""
ctx = _make_context(tmp_path, case_type="1a", seed=1)
for i in range(1, 200):
metadata, _, report = ctx.run_sample(i)
v = metadata["verification"]
if v["expected_signer_script"] != "latin":
assert metadata["signer"]["name"] == v["expected_signer_group"]
assert v["actual_signer_script"] == v["expected_signer_script"]
assert report is not None and report.passed
return
raise AssertionError("No sample in range landed on a non-latin-script signer; widen the search range.")
def test_forged_mode_degrades_gracefully_for_signers_without_forgeries(tmp_path):
"""Named signers have no CEDAR-style forged counterpart; sampling 'forged' for them
should fall back to their genuine records rather than crash (SignatureDataset already
logs a warning for this -- this just confirms the caller-facing behavior)."""
ctx = _make_context(tmp_path)
records = ctx.signature_dataset.records_for_group("Priya Sharma", source_filter="forged")
assert len(records) > 0
assert all(r.signer_group == "Priya Sharma" for r in records)
def test_unrecognized_case_type_label_falls_back_to_generic_path(tmp_path):
"""A free-text case_type not in CASE_TABLE behaves exactly like today's plain-label usage."""
ctx = _make_context(tmp_path, case_type="my-custom-label", seed=1)
metadata, _, report = ctx.run_sample(1)
assert metadata["verification"]["case_type"] is None
assert metadata["quality"]["case_type"] == "my-custom-label"
assert report is not None and report.passed
|