"""앵커가 실재하는 절을 가리키는지, 그림 대장이 실물과 맞는지 보는 검사. `verify-tech-log-tree.py` 가 오래 못 보던 두 자리다 — 앵커는 SSOT 경로를 포함하는지만 봤고, `assetLedger` 는 아무 스크립트도 읽지 않았다. """ import importlib.util import os import sys import unittest ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, os.path.join(ROOT, "scripts")) _spec = importlib.util.spec_from_file_location( "verify_tech_log_tree", os.path.join(ROOT, "scripts", "verify-tech-log-tree.py")) V = importlib.util.module_from_spec(_spec) _spec.loader.exec_module(V) class Headings(unittest.TestCase): def test_제목을_슬러그로_바꾼다(self): import tempfile with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False, encoding="utf-8") as fh: fh.write("# 제목\n\n## 검토한 선택지와 막힌 지점\n\n### AP1 완주: code가 되기까지\n") path = fh.name try: heads = V._headings(path) finally: os.unlink(path) self.assertEqual([h[0] for h in heads], [2, 3]) self.assertEqual(heads[0][2], "검토한-선택지와-막힌-지점") self.assertEqual(heads[1][2], "ap1-완주-code가-되기까지") def test_없는_파일은_빈_목록이다(self): self.assertEqual(V._headings("/없는/경로.md"), []) class AnchorBase(unittest.TestCase): SLUGS = ["검토한-선택지와-막힌-지점", "결정이-지켜지는지-확인하는-방법", "ap1-완주"] def test_절_제목과_같으면_그것이_바탕이다(self): self.assertEqual(V._anchor_base("검토한-선택지와-막힌-지점", self.SLUGS), "검토한-선택지와-막힌-지점") def test_구분자가_붙어도_바탕을_찾는다(self): self.assertEqual(V._anchor_base("검토한-선택지와-막힌-지점-ap1", self.SLUGS), "검토한-선택지와-막힌-지점") def test_가장_긴_것을_고른다(self): slugs = ["ap1", "ap1-완주"] self.assertEqual(V._anchor_base("ap1-완주-code", slugs), "ap1-완주") def test_어느_절과도_안_맞으면_없다(self): self.assertIsNone(V._anchor_base("§1.1", self.SLUGS)) self.assertIsNone(V._anchor_base("a18", self.SLUGS)) def test_슬러그의_앞부분만_같은_것은_바탕이_아니다(self): """`검토한-선택지` 로 시작한다고 `검토한-선택지와-막힌-지점` 이 되지는 않는다.""" self.assertIsNone(V._anchor_base("검토한-선택", self.SLUGS)) class LedgerNames(unittest.TestCase): def test_이름만_적은_칸(self): self.assertEqual(V._ledger_names(["a", "b"]), {"a", "b"}) def test_사유를_함께_적은_칸(self): entries = [{"asset": ["a", "b"], "reason": "쓸 자리가 없다"}] self.assertEqual(V._ledger_names(entries), {"a", "b"}) def test_두_모양이_섞여도_편다(self): self.assertEqual(V._ledger_names(["a", {"asset": ["b"]}]), {"a", "b"}) def test_비면_빈_집합이다(self): self.assertEqual(V._ledger_names(None), set()) class BareAnchor(unittest.TestCase): def test_그대로인_것은_그대로다(self): self.assertEqual(V._bare_anchor("final/document.md#가-나"), "final/document.md#가-나") def test_백틱을_걷는다(self): self.assertEqual(V._bare_anchor("`final/document.md#10-3`"), "final/document.md#10-3") def test_뒤에_붙은_절_번호를_걷는다(self): self.assertEqual(V._bare_anchor("`final/document.md#a05` §14.1"), "final/document.md#a05") def test_빈_것은_빈_것이다(self): self.assertEqual(V._bare_anchor(" "), "") class RecordSources(unittest.TestCase): def test_frontmatter_의_source_를_읽는다(self): import tempfile body = ("---\n" "kind: CASE\n" "source:\n" " - final/document.md#가\n" " - final/document.md#나\n" "---\n\n# 제목\n") with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False, encoding="utf-8") as fh: fh.write(body) path = fh.name try: self.assertEqual(V._record_sources(path), ["final/document.md#가", "final/document.md#나"]) finally: os.unlink(path) def test_source_가_없으면_빈_목록(self): import tempfile with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False, encoding="utf-8") as fh: fh.write("---\nkind: CASE\n---\n") path = fh.name try: self.assertEqual(V._record_sources(path), []) finally: os.unlink(path) class RealProjects(unittest.TestCase): """실물 프로젝트에 돌려 error 가 0 인지 본다. warn 은 편집 판단이라 세지 않는다.""" def test_keycloak_이_error_0(self): """이 작업이 증명 대상으로 삼은 프로젝트. 다른 프로젝트의 미해결은 따로 센다.""" rep = V.verify("keycloak") self.assertEqual(rep.error_count, 0, dict(rep.errors)) if __name__ == "__main__": unittest.main()