feat: 가상화 문서들 추가
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
"""새 규칙이 **어긴 것을 잡는지** 본다.
|
||||
|
||||
헬퍼 함수가 맞는지는 `test_tree_anchors.py` 가 본다. 여기서는 프로젝트 사본을 만들어
|
||||
한 군데씩 어기고 검사기가 그것을 세는지 본다 — 검사기의 값은 통과시키는 것이 아니라
|
||||
안 지킨 것을 잡는 것이다.
|
||||
"""
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
SRC = os.path.join(ROOT, "docs", "keycloak")
|
||||
|
||||
|
||||
def _load(name, filename, root):
|
||||
"""검사기를 그 저장소 루트를 보도록 다시 읽는다."""
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
name, os.path.join(ROOT, "scripts", filename))
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
sys.path.insert(0, os.path.join(ROOT, "scripts"))
|
||||
spec.loader.exec_module(mod)
|
||||
mod.ROOT = root
|
||||
return mod
|
||||
|
||||
|
||||
@unittest.skipUnless(os.path.isdir(SRC), "keycloak 프로젝트가 없다")
|
||||
class Sandbox(unittest.TestCase):
|
||||
"""keycloak 사본을 만들어 한 군데씩 어긴다. 원본은 건드리지 않는다."""
|
||||
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.tmp.cleanup)
|
||||
self.root = self.tmp.name
|
||||
os.makedirs(os.path.join(self.root, "docs"))
|
||||
shutil.copytree(SRC, os.path.join(self.root, "docs", "keycloak"),
|
||||
symlinks=True)
|
||||
self.tree_path = os.path.join(
|
||||
self.root, "docs/keycloak/tech-log-studio/tech-log-tree.json")
|
||||
|
||||
def tree(self):
|
||||
return json.load(open(self.tree_path, encoding="utf-8"))
|
||||
|
||||
def save(self, data):
|
||||
json.dump(data, open(self.tree_path, "w", encoding="utf-8"),
|
||||
ensure_ascii=False, indent=2)
|
||||
|
||||
def run_tree(self):
|
||||
return _load("vt", "verify-tech-log-tree.py", self.root).verify("keycloak")
|
||||
|
||||
def run_layout(self):
|
||||
return _load("vl", "verify-project-layout.py", self.root).verify("keycloak")
|
||||
|
||||
# ── 사본이 깨끗한지 먼저 ────────────────────────────────────────
|
||||
def test_사본은_깨끗하다(self):
|
||||
rep = self.run_tree()
|
||||
self.assertEqual(rep.error_count, 0, dict(rep.errors))
|
||||
|
||||
# ── 규칙 1 · 앵커가 실재하는 절을 가리키나 ─────────────────────
|
||||
def test_없는_절을_가리키는_앵커를_잡는다(self):
|
||||
t = self.tree()
|
||||
t["candidates"][0]["sourceRefs"] = ["final/document.md#이런-절은-없다-정말로"]
|
||||
self.save(t)
|
||||
rep = self.run_tree()
|
||||
self.assertIn("SSOT 에 없는 절을 가리키는 앵커", rep.errors)
|
||||
|
||||
# ── 규칙 2 · 계약과 기록의 source 가 같은가 ────────────────────
|
||||
def test_계약과_기록의_source_가_다르면_잡는다(self):
|
||||
t = self.tree()
|
||||
for kind, items in t["topics"]["oauth-oidc-auth-boundary"]["kinds"].items():
|
||||
for node in items:
|
||||
if node.get("file") and node.get("source"):
|
||||
node["source"] = node["source"][:1]
|
||||
break
|
||||
else:
|
||||
continue
|
||||
break
|
||||
self.save(t)
|
||||
rep = self.run_tree()
|
||||
self.assertIn("계약과 기록의 source 가 다르다", rep.errors)
|
||||
|
||||
# ── 규칙 3 · 범위 안인데 아무 후보도 안 짚은 절 ────────────────
|
||||
def test_아무_후보도_안_짚은_절을_센다(self):
|
||||
# 전부 비우면 앵커 형식 판별이 꺼진다. 한 h2 를 가리키는 앵커만 걷어낸다
|
||||
gone = "선택이-코드와-흐름에-반영되는-방식"
|
||||
t = self.tree()
|
||||
for c in t["candidates"]:
|
||||
c["sourceRefs"] = [r for r in (c.get("sourceRefs") or []) if gone not in r]
|
||||
for _, items in t["topics"]["oauth-oidc-auth-boundary"]["kinds"].items():
|
||||
for node in items:
|
||||
node["source"] = [r for r in (node.get("source") or []) if gone not in r]
|
||||
self.save(t)
|
||||
rep = self.run_tree()
|
||||
self.assertIn("범위 안인데 아무 후보도 가리키지 않는 절", rep.warns)
|
||||
|
||||
# ── 규칙 4 · assetLedger 가 실물과 맞나 ────────────────────────
|
||||
def test_없는_그림을_배정했다고_적으면_잡는다(self):
|
||||
t = self.tree()
|
||||
t["assetLedger"]["assigned"] = list(t["assetLedger"]["assigned"]) + ["없는-그림"]
|
||||
self.save(t)
|
||||
rep = self.run_tree()
|
||||
self.assertIn("assetLedger 가 없는 그림을 배정했다고 적었다", rep.errors)
|
||||
|
||||
def test_그림이_대장에_없으면_잡는다(self):
|
||||
t = self.tree()
|
||||
t["assetLedger"]["assigned"] = list(t["assetLedger"]["assigned"])[1:]
|
||||
self.save(t)
|
||||
rep = self.run_tree()
|
||||
self.assertIn("그림이 assetLedger 에 없다", rep.errors)
|
||||
|
||||
# ── 규칙 5 · 그림의 근거가 SSOT 인가 ───────────────────────────
|
||||
def test_그림의_근거가_SSOT_가_아니면_잡는다(self):
|
||||
spec_path = os.path.join(
|
||||
self.root, "docs/keycloak/final/.techviz/ap4-edge-trust-architecture/spec.json")
|
||||
spec = json.load(open(spec_path, encoding="utf-8"))
|
||||
spec["source_context"]["document"] = "case-ap4-identity-header-trust.md"
|
||||
json.dump(spec, open(spec_path, "w", encoding="utf-8"), ensure_ascii=False)
|
||||
rep = self.run_layout()
|
||||
self.assertIn("그림의 근거가 SSOT 가 아니다", rep.errors)
|
||||
|
||||
# ── 규칙 6 · 그림 안에서 상자와 라벨이 겹치나 ──────────────────
|
||||
def test_겹친_그림을_배치_검사가_잡는다(self):
|
||||
"""`check-figure-overlap.py` 가 `verify-project-layout.py` 의 관문인지."""
|
||||
before = os.path.join(
|
||||
ROOT, "runs/keycloak/2026-09-08-0030/stage/S4/before/assets",
|
||||
"ap4-edge-trust-architecture/ap4-edge-trust-architecture.svg")
|
||||
if not os.path.exists(before):
|
||||
self.skipTest("고치기 전 그림 사본이 없다")
|
||||
target = os.path.join(
|
||||
self.root, "docs/keycloak/final/assets",
|
||||
"ap4-edge-trust-architecture/ap4-edge-trust-architecture.svg")
|
||||
shutil.copyfile(before, target)
|
||||
rep = self.run_layout()
|
||||
self.assertIn("그림 안에서 상자와 라벨이 겹친다", rep.errors)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user