Files
Aislo/resources/tester/test_478_extract.py
T
eomsangdonandClaude Opus 5.5 9e315a8abb feat(478-1 문서 추출): 추출 스크립트 셋 + JSON — ts-morph · ast · app.openapi() · 한 명령 extract_all
- 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>
2026-10-19 18:53:44 +09:00

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