"""domain_policy.yaml 스키마 검증 + cross-reference 체크.""" from __future__ import annotations import pytest import yaml from pydantic import ValidationError from policy import loader as policy_loader from policy.schema import DomainPolicy def test_default_yaml_loads(policy): """기본 yaml 이 pydantic 검증 통과.""" assert isinstance(policy, DomainPolicy) assert policy.version == 1 assert "safety_health" in policy.scope assert "news" in policy.scope assert policy.self_declare_semantics == "additive_trigger_only" def test_subject_domains_count(policy): """plan 에서 정의한 9개 subject_domain 전부 존재.""" expected = { "safety_reference", "safety_operational", "msds", "hazard_specific", "incident_report", "health_record", "safety_video", "news_item", "news_digest_request", } assert set(policy.subject_domains.keys()) == expected def test_all_subject_domains_have_suggested_ui_category(policy): """storage_category → suggested_ui_category 리네임 확인. 모든 도메인이 실측 enum 에서만 값을 선택. """ valid = {"document", "library", "news", "memo", "audio", "video", "law"} for name, dom in policy.subject_domains.items(): assert dom.suggested_ui_category in valid, ( f"{name}.suggested_ui_category={dom.suggested_ui_category} not in enum" ) def test_fallback_domain_required(policy): """fallback_domain 필수 (INV-6).""" assert policy.fallback_domain.name == "generic" assert policy.fallback_domain.suggested_ui_category in { "document", "library", "news", "memo", "audio", "video", "law", } def test_risk_flags_cross_reference_ok(policy): """default_risk_flags 에 미정의 flag 참조 없음.""" known = set(policy.risk_flags.keys()) for name, dom in policy.subject_domains.items(): for flag in dom.default_risk_flags: assert flag in known, f"{name} references undefined flag {flag}" def test_forbidden_rules_reference_existing_domains(policy): """forbidden_for_4b.applies_when_subject_in 의 도메인이 subject_domains 에 존재.""" known = set(policy.subject_domains.keys()) for rule in policy.forbidden_for_4b: for dom in rule.applies_when_subject_in: assert dom in known, f"{rule.id} references undefined domain {dom}" def test_reject_unknown_flag_in_yaml(tmp_path, policy_yaml_path): """yaml 에 정의되지 않은 flag 를 subject_domain 이 참조하면 ValidationError.""" with open(policy_yaml_path, encoding="utf-8") as f: raw = yaml.safe_load(f) # 가짜 flag 주입 raw["subject_domains"]["safety_reference"]["default_risk_flags"] = [ "does_not_exist_flag" ] bad_yaml = tmp_path / "bad.yaml" bad_yaml.write_text(yaml.safe_dump(raw, allow_unicode=True)) policy_loader.clear_cache() with pytest.raises(ValidationError): policy_loader.load_policy(str(bad_yaml)) def test_reject_invalid_ui_category(tmp_path, policy_yaml_path): """suggested_ui_category 에 enum 외 값 들어가면 ValidationError.""" with open(policy_yaml_path, encoding="utf-8") as f: raw = yaml.safe_load(f) raw["subject_domains"]["safety_reference"]["suggested_ui_category"] = "nonexistent" bad_yaml = tmp_path / "bad_cat.yaml" bad_yaml.write_text(yaml.safe_dump(raw, allow_unicode=True)) policy_loader.clear_cache() with pytest.raises(ValidationError): policy_loader.load_policy(str(bad_yaml)) def test_reject_too_long_synthesis_directive(tmp_path, policy_yaml_path): """500 chars 초과 synthesis_directive 는 reject.""" with open(policy_yaml_path, encoding="utf-8") as f: raw = yaml.safe_load(f) raw["risk_flags"]["safety_legal_interpretation"]["synthesis_directive"] = "x" * 600 bad_yaml = tmp_path / "bad_dir.yaml" bad_yaml.write_text(yaml.safe_dump(raw, allow_unicode=True)) policy_loader.clear_cache() with pytest.raises(ValidationError): policy_loader.load_policy(str(bad_yaml))