merge(478-2): origin/sub_laptop_2(478-1 추출) 합침 — package.json = ts-morph + typedoc 줄 합침 · README = 추출 + 사이트 절
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
"""PLAN 478-1 (요구 278) — 문서 추출 스크립트 셋(`docs/tools/extract_all.py`).
|
||||
|
||||
- 알려진 것 있음: computeMassHaul · 계산 등록부 항목 · POST /design/save · 세션 칸 몇 개.
|
||||
- 함수 id 중복 0 · calls · called_by 가 가리키는 id 모두 있음 · 등록부 TS 자리 모두 함수에 있음.
|
||||
- 다시 돌려도 같은 결과(정렬 고정).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
DATA = ROOT / "docs" / "_data"
|
||||
OUTPUTS = ["functions.json", "session_keys.json", "api.json", "api_unmatched.json", "registry.json"]
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
shutil.which("node") is None or not (ROOT / "docs/tools/node_modules/ts-morph").is_dir(),
|
||||
reason="node · ts-morph 없음(npm --prefix docs/tools install)",
|
||||
)
|
||||
|
||||
|
||||
def _run() -> dict[str, str]:
|
||||
subprocess.run( # noqa: S603 — 고정 명령
|
||||
[sys.executable, str(ROOT / "docs/tools/extract_all.py")],
|
||||
cwd=ROOT,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
timeout=300,
|
||||
)
|
||||
return {name: (DATA / name).read_text(encoding="utf-8") for name in OUTPUTS}
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def first() -> dict[str, str]:
|
||||
return _run()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def data(first) -> dict:
|
||||
return {name.removesuffix(".json"): json.loads(text) for name, text in first.items()}
|
||||
|
||||
|
||||
def test_known_function(data):
|
||||
f = {x["id"]: x for x in data["functions"]}
|
||||
mh = f["ts:common_util/common_util_mass_haul.ts#computeMassHaul"]
|
||||
assert mh["kind"] == "function" and mh["exported"] and mh["doc"]
|
||||
assert [p["name"] for p in mh["params"]][:2] == ["crossSections", "conversion"]
|
||||
assert mh["calls"] and mh["called_by"]
|
||||
assert any(x["lang"] == "py" for x in data["functions"])
|
||||
|
||||
|
||||
def test_ids_unique_and_links_exist(data):
|
||||
ids = [x["id"] for x in data["functions"]]
|
||||
assert len(ids) == len(set(ids))
|
||||
known = set(ids)
|
||||
for x in data["functions"]:
|
||||
assert set(x["calls"]) <= known, x["id"]
|
||||
assert set(x["called_by"]) <= known, x["id"]
|
||||
assert x.get("target") is None or x["target"] in known
|
||||
assert ids == sorted(ids)
|
||||
|
||||
|
||||
def test_registry(data):
|
||||
reg = {e["id"]: e for e in data["registry"]}
|
||||
assert {"route.polyline", "facility.follow"} <= set(reg)
|
||||
known = {x["id"] for x in data["functions"]}
|
||||
assert [e["ts"] for e in data["registry"] if e["ts"] and f"ts:{e['ts']}" not in known] == []
|
||||
|
||||
|
||||
def test_design_save_api(data):
|
||||
hit = [
|
||||
a
|
||||
for a in data["api"]
|
||||
if a["method"] == "POST" and a["path"] == "/api/projects/{project_id}/design/save"
|
||||
]
|
||||
assert len(hit) == 1
|
||||
assert hit[0]["handler"] == "py:B06_Section/B06_Section_Router_DesignSave.py#save_design"
|
||||
assert "ts:A00_Common/design/design_save_api.ts#postDesignSave" in hit[0]["callers"]
|
||||
assert set(data["api_unmatched"]) == {"ts_calls", "routes_without_caller"}
|
||||
|
||||
|
||||
def test_session_keys(data):
|
||||
keys = {k["key"]: k for k in data["session_keys"]}
|
||||
for key in ("section-context", "route-draft-hold", "masshaul-open"):
|
||||
assert keys[key]["reads"] or keys[key]["writes"], key
|
||||
assert list(keys) == sorted(keys)
|
||||
|
||||
|
||||
def test_rerun_same(first):
|
||||
assert _run() == first
|
||||
Reference in New Issue
Block a user