"""Tests for tools/verify_adr_lang_pairs.py.""" from __future__ import annotations import sys from pathlib import Path _REPO_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(_REPO_ROOT / "tools")) import verify_adr_lang_pairs as v # noqa: E402 def _make_adr( path: Path, title_id: str, title_text: str = "Some Title", status: str = "Accepted", ) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text( f"# ADR-{title_id}: {title_text}\n\n" f"## Status\n\n{status}\n\n" f"## Context\n\nbody\n", encoding="utf-8", ) def test_complete_pairs_pass(tmp_path: Path) -> None: _make_adr(tmp_path / "docs/adr/ADR-0001-foo-bar.md", "0001", "Foo EN") _make_adr(tmp_path / "docs/adr-ko/ADR-0001-foo-bar.md", "0001", "Foo KO") assert v.verify(tmp_path) == [] def test_empty_dirs_pass(tmp_path: Path) -> None: assert v.verify(tmp_path) == [] def test_missing_ko_fails(tmp_path: Path) -> None: _make_adr(tmp_path / "docs/adr/ADR-0001-foo-bar.md", "0001") errs = v.verify(tmp_path) assert any("missing KO" in e and "ADR-0001-foo-bar.md" in e for e in errs) def test_orphan_ko_fails(tmp_path: Path) -> None: _make_adr(tmp_path / "docs/adr-ko/ADR-0001-foo-bar.md", "0001") errs = v.verify(tmp_path) assert any("orphan KO" in e and "ADR-0001-foo-bar.md" in e for e in errs) def test_status_mismatch_fails(tmp_path: Path) -> None: _make_adr(tmp_path / "docs/adr/ADR-0001-foo-bar.md", "0001", status="Accepted") _make_adr(tmp_path / "docs/adr-ko/ADR-0001-foo-bar.md", "0001", status="Proposed") errs = v.verify(tmp_path) assert any("Status keyword mismatch" in e for e in errs) def test_status_parenthetical_translation_passes(tmp_path: Path) -> None: """Parenthetical commentary in Status may be translated; only the lifecycle keyword needs to match across EN/KO.""" _make_adr( tmp_path / "docs/adr/ADR-0001-foo-bar.md", "0001", status="Accepted (Revision 2 - concrete bit layout)", ) _make_adr( tmp_path / "docs/adr-ko/ADR-0001-foo-bar.md", "0001", status="Accepted (Revision 2 - 비트 레이아웃 확정)", ) assert v.verify(tmp_path) == [] def test_status_list_after_keyword_passes(tmp_path: Path) -> None: """Trailing bullet list in Status may be translated; only first-line keyword is compared.""" _make_adr( tmp_path / "docs/adr/ADR-0011-foo-bar.md", "0011", status="Accepted.\n\n- VA model: implemented (default).", ) _make_adr( tmp_path / "docs/adr-ko/ADR-0011-foo-bar.md", "0011", status="Accepted.\n\n- VA 모델: 구현됨 (기본값).", ) assert v.verify(tmp_path) == [] def test_title_id_mismatch_fails(tmp_path: Path) -> None: _make_adr(tmp_path / "docs/adr/ADR-0001-foo-bar.md", "0002") _make_adr(tmp_path / "docs/adr-ko/ADR-0001-foo-bar.md", "0001") errs = v.verify(tmp_path) assert any("EN title ADR-ID" in e for e in errs) def test_multiline_status_with_parenthetical_passes(tmp_path: Path) -> None: """Real ADRs like ADR-0001 have multi-line Status with revision notes.""" multiline_status = ( "Accepted (Revision 2 - 2026-04-27: concrete bit layout,\n" "Supersedes ADR-0031.)" ) _make_adr( tmp_path / "docs/adr/ADR-0001-foo-bar.md", "0001", status=multiline_status ) _make_adr( tmp_path / "docs/adr-ko/ADR-0001-foo-bar.md", "0001", status=multiline_status ) assert v.verify(tmp_path) == [] def test_superseded_keyword_must_match(tmp_path: Path) -> None: """Multi-word lifecycle keywords like 'Superseded by ADR-NNNN' must match in full (the ADR ref is part of the keyword).""" _make_adr( tmp_path / "docs/adr/ADR-0001-foo-bar.md", "0001", status="Superseded by ADR-0031", ) _make_adr( tmp_path / "docs/adr-ko/ADR-0001-foo-bar.md", "0001", status="Superseded by ADR-0099", ) errs = v.verify(tmp_path) assert any("Status keyword mismatch" in e for e in errs) def test_crlf_normalization(tmp_path: Path) -> None: """KO has CRLF, EN has LF; Status content is otherwise identical -> pass.""" en = tmp_path / "docs/adr/ADR-0001-foo-bar.md" ko = tmp_path / "docs/adr-ko/ADR-0001-foo-bar.md" en.parent.mkdir(parents=True, exist_ok=True) ko.parent.mkdir(parents=True, exist_ok=True) en.write_bytes( b"# ADR-0001: Foo\n\n## Status\n\nAccepted\n\n## Context\n\nbody\n" ) ko.write_bytes( b"# ADR-0001: Foo\r\n\r\n## Status\r\n\r\nAccepted\r\n\r\n## Context\r\n\r\nbody\r\n" ) assert v.verify(tmp_path) == [] def test_em_dash_title_separator_recognized(tmp_path: Path) -> None: """ADR-0033 uses ' — ' instead of ': ' between ADR-NNNN and the title.""" en = tmp_path / "docs/adr/ADR-0033-foo-bar.md" ko = tmp_path / "docs/adr-ko/ADR-0033-foo-bar.md" en.parent.mkdir(parents=True, exist_ok=True) ko.parent.mkdir(parents=True, exist_ok=True) body = "## Status\n\nAccepted\n\n## Context\n\nbody\n" en.write_text("# ADR-0033 — Latency Model\n\n" + body, encoding="utf-8") ko.write_text("# ADR-0033 — Latency Model\n\n" + body, encoding="utf-8") assert v.verify(tmp_path) == [] def test_underscore_in_slug_recognized(tmp_path: Path) -> None: """Defensive: slug regex must accept underscores even though current ADRs use kebab-case (in case future ADRs reintroduce them).""" _make_adr(tmp_path / "docs/adr/ADR-0099-cat-foo_bar.md", "0099") _make_adr(tmp_path / "docs/adr-ko/ADR-0099-cat-foo_bar.md", "0099") assert v.verify(tmp_path) == [] def test_main_exit_codes(tmp_path: Path, capsys) -> None: assert v.main(["--root", str(tmp_path)]) == 0 _make_adr(tmp_path / "docs/adr/ADR-0001-foo-bar.md", "0001") assert v.main(["--root", str(tmp_path)]) == 1 out = capsys.readouterr().out assert "FAILED" in out