"""B-1 PR② — 매핑·시리즈·payload 순수 단위 테스트 (plan safety-library-1). 법령명 매핑 단위 테스트 = R8-B1 동반 계약 (검증(PR③) 전에 스윕이 도는 만큼 매핑은 코드 레벨 선고정). 실 title 표본 = 2026-06-13 prod documents 실측 형태. """ import gzip import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent / "app")) from workers.statute_adapters.kr import parse_service_payloads # noqa: E402 from workers.statute_collector import ( # noqa: E402 legacy_law_name, normalize_law_name, series_suffix, ) FIX = Path(__file__).parent / "fixtures" / "statute_kr" # ─── 법령명 매핑 (실 title 표본) ─── def test_legacy_law_name_extraction(): # prod 실측 형태: '법령명 (YYYYMMDD) 섹션' assert legacy_law_name("건설기술 진흥법 시행규칙 (20260611) 제6장_보칙") == "건설기술 진흥법 시행규칙" assert legacy_law_name("산업안전보건법 (20260219) 전문") == "산업안전보건법" assert legacy_law_name("패턴 불일치 제목") is None def test_mapping_equality_not_prefix(): """prefix 비교 금지 — '산업안전보건법' family 가 시행령 레거시를 오폭하면 안 됨.""" name = legacy_law_name("산업안전보건법 시행령 (20260324) 제1장") assert name == "산업안전보건법 시행령" assert normalize_law_name(name) != normalize_law_name("산업안전보건법") assert normalize_law_name(name) == normalize_law_name("산업안전보건법 시행령") def test_mapping_absorbs_middle_dot_and_space(): """가운뎃점·공백 변형 흡수 — 유해ㆍ위험작업(정식) vs 유해위험작업(law_monitor 표기).""" assert (normalize_law_name("유해ㆍ위험작업의 취업 제한에 관한 규칙") == normalize_law_name("유해위험작업의 취업 제한에 관한 규칙")) assert (normalize_law_name("산업안전보건기준에 관한 규칙") == normalize_law_name("산업안전보건기준에관한규칙")) # ─── 버전 시리즈 식별자 (R7-B1 a) ─── def test_series_suffix(): assert series_suffix("283449") is None # primary assert series_suffix("273603|별표0001-00") == "별표0001-00" # annex (구분 차원 포함) assert series_suffix("273603|서식0003-00") == "서식0003-00" # ─── fetch_version payload (fixture — R4-M4 리스트 계약) ─── def _read_gz(name: str) -> str: return gzip.decompress((FIX / name).read_bytes()).decode("utf-8") def test_parse_service_payloads_rule_with_annexes(): payloads = parse_service_payloads( _read_gz("lawservice_rule.xml.gz"), "산업안전보건기준에 관한 규칙", "273603") assert payloads[0].law_doc_kind == "primary" assert payloads[0].version_key == "273603" assert len(payloads[0].content) > 100_000 # 853조 본문 annexes = [p for p in payloads if p.law_doc_kind == "annex"] # 별표단위 23 중 삭제 tombstone 3 skip(별표10 '삭제 <2023.11.14>'·서식1·2 '삭제 <2012.3.5>') # — KR 별표/서식 삭제 = absence 아닌 명시 tombstone (R7-M3 absence 추론 불요의 fixture 증거) assert len(annexes) == 20 keys = [p.version_key for p in annexes] assert len(keys) == len(set(keys)), "annex version_key 유일성 (uq_legal_meta_version 전제)" assert all(k.startswith("273603|") for k in keys) # 구분 차원 — 별표1 vs 서식N 공존 (fixture 실측: (번호,가지)만으로는 4건 충돌) assert any("별표" in k for k in keys) and any("서식" in k for k in keys) def test_parse_service_payloads_sanab_no_annex(): payloads = parse_service_payloads( _read_gz("lawservice_sanab.xml.gz"), "산업안전보건법", "283449") assert len(payloads) == 1 # 별표 없는 법령 = primary 단독 p = payloads[0] assert p.promulgation_date == "20260219" assert p.effective_date == "20260601" assert "제2조(정의)" in p.content # 조문내용 보존 assert p.content.startswith("# 산업안전보건법")