Files
hyungi_document_server/tests/test_statute_lifecycle_units.py
hyungi bacb36924b feat(safety): B-1 PR② — fetch_version(payload 리스트) + ingest 4축 + 생애주기 잡 통째 + 부트스트랩
plan safety-library-1 B-1 PR② (R8-B1: 승격·supersede·스윕·repeal = 잡 코드 통째 배포):
- kr.fetch_version: 전문 1콜 → primary+annex payload 리스트 (R4-M4)
  ★fixture 가 잡은 결함 2: 별표구분(별표/서식) 차원 누락 시 (번호,가지) 4건 충돌
  → version_key='MST|{구분}{번호}-{가지}' / 삭제 tombstone 3건(별표10·서식1·2) skip
  — KR 별표 삭제 = absence 아닌 명시 tombstone (R7-M3 absence 추론 불요 확정)
- ingest: 전 버전 pending 적재 + 4축(law/KR/COALESCE날짜/public_domain) + backfill 마커
- 생애주기 잡: 버전 시리즈 단위 승격·supersede(R7-B1) + 상태 기반 레거시 스윕(primary
  current 보유 한정) + repeal(레거시 매핑분 포함, R7-M2) — 단일 트랜잭션·KST
- 법령명 매핑: 정규화 동등 비교(prefix 금지 — 시행령 오폭 차단), 가운뎃점·공백 흡수
- 워터마크 = 파싱 검증 통과 후에만 / 스케줄 daily 07:00 KST (law_monitor 슬롯 승계)
- 테스트 14/14 (매핑 표본·시리즈 키·payload fixture)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 09:37:51 +09:00

88 lines
4.1 KiB
Python

"""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("# 산업안전보건법")