120 lines
4.1 KiB
Python
120 lines
4.1 KiB
Python
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
DOMAIN = ROOT / "docs" / "domain"
|
|
GOLDEN = ROOT / "test" / "fixtures" / "golden" / "accident-scenarios.json"
|
|
|
|
|
|
def load_json(path):
|
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def test_stage_registry_and_transition_matrix_are_closed():
|
|
stage_registry = load_json(DOMAIN / "stage-codes.json")
|
|
transitions = load_json(DOMAIN / "stage-transitions.json")
|
|
codes = {entry["code"] for entry in stage_registry["codes"]}
|
|
|
|
assert len(codes) == len(stage_registry["codes"])
|
|
assert {"0000", "0004", "0005", "1001", "1002", "3001", "3002"} <= codes
|
|
assert set(transitions["allowed"]) == codes
|
|
|
|
for source, targets in transitions["allowed"].items():
|
|
assert set(targets) <= codes, source
|
|
|
|
for terminal in {"0000", "0001", "0002", "0003", "0004", "0005"}:
|
|
assert transitions["allowed"][terminal] == []
|
|
|
|
|
|
def test_photo_sequences_cannot_skip_steps():
|
|
transitions = load_json(DOMAIN / "stage-transitions.json")
|
|
|
|
for sequence in transitions["photo_sequences"].values():
|
|
for current, following in zip(sequence, sequence[1:]):
|
|
assert following in transitions["allowed"][current]
|
|
later_steps = set(sequence[sequence.index(following) + 1 :])
|
|
assert later_steps.isdisjoint(transitions["allowed"][current])
|
|
|
|
|
|
def test_field_registry_groups_are_complete_and_unique():
|
|
registry = load_json(DOMAIN / "field-registry.json")
|
|
fields = registry["fields"]
|
|
keys = [field["key"] for field in fields]
|
|
|
|
assert len(keys) == len(set(keys))
|
|
assert set(keys) == {
|
|
key for group_keys in registry["groups"].values() for key in group_keys
|
|
}
|
|
assert {"sfzmhm1", "sjhm1", "hphm1", "sfzmhm2", "sjhm2", "hphm2"} <= {
|
|
field["key"] for field in fields if field["sensitive"]
|
|
}
|
|
assert {"sfzmwh1", "sjwh1", "sfzmwh2", "sjwh2"} <= {
|
|
field["key"] for field in fields if not field["external_write"]
|
|
}
|
|
|
|
|
|
def test_golden_scenarios_are_unique_and_use_known_stages():
|
|
stages = {
|
|
entry["code"]
|
|
for entry in load_json(DOMAIN / "stage-codes.json")["codes"]
|
|
}
|
|
scenarios = load_json(GOLDEN)["scenarios"]
|
|
case_ids = [case["case_id"] for case in scenarios]
|
|
|
|
assert len(scenarios) >= 12
|
|
assert len(case_ids) == len(set(case_ids))
|
|
for case in scenarios:
|
|
assert case["initial_stage"] in stages
|
|
if "expected_stage" in case:
|
|
assert case["expected_stage"] in stages
|
|
for turn in case.get("turns", []):
|
|
if "expected_stage" in turn:
|
|
assert turn["expected_stage"] in stages
|
|
|
|
|
|
def test_golden_turn_sequences_follow_the_transition_matrix():
|
|
allowed = load_json(DOMAIN / "stage-transitions.json")["allowed"]
|
|
scenarios = load_json(GOLDEN)["scenarios"]
|
|
|
|
for case in scenarios:
|
|
current = case["initial_stage"]
|
|
for turn in case.get("turns", []):
|
|
expected = turn.get("expected_stage")
|
|
if expected is None:
|
|
continue
|
|
if "expected_error" not in turn:
|
|
assert expected in allowed[current], (
|
|
case["case_id"],
|
|
current,
|
|
expected,
|
|
)
|
|
current = expected
|
|
|
|
|
|
def test_golden_fixture_contains_no_realistic_phone_or_national_id():
|
|
raw = GOLDEN.read_text(encoding="utf-8")
|
|
|
|
assert not re.search(r"(?<!\d)1[3-9]\d{9}(?!\d)", raw)
|
|
assert not re.search(r"(?<!\d)\d{17}[\dXx](?!\d)", raw)
|
|
|
|
|
|
def test_repository_sources_and_examples_do_not_embed_fastgpt_tokens():
|
|
token_pattern = re.compile(r"fastgpt-[A-Za-z0-9]{20,}")
|
|
candidates = [ROOT / ".env.example"]
|
|
|
|
for directory in ("src", "test", "docs"):
|
|
candidates.extend(
|
|
path
|
|
for path in (ROOT / directory).rglob("*")
|
|
if path.is_file() and "__pycache__" not in path.parts
|
|
)
|
|
|
|
for path in candidates:
|
|
try:
|
|
text = path.read_text(encoding="utf-8")
|
|
except UnicodeDecodeError:
|
|
continue
|
|
assert not token_pattern.search(text), path
|