- extract_ts.mjs — 함수(kind · exported · doc · params · returns · calls · called_by · 다시 내보내기) · 세션 칸 · 서버 주소 글자 · 계산 등록부 그대로 - extract_py.py — def 전부 · docstring 첫 줄 · 부르는 함수 · FastAPI 라우트 데코레이터 - extract_api.py — 서버 안 켜고 openapi 주소 → handler · TS 부르는 곳 짝 · 짝 없는 것 따로 - 범위 = scope.json 한 곳 · 세는 규칙 = README · 시험 test_478_extract(6) - 수치 = 함수 5746 · 세션 칸 45 · 서버 주소 41 · 등록부 45 · 14.3 s Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
3.2 KiB
Python
97 lines
3.2 KiB
Python
"""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
|