Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_4
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
/* 472-3 헬퍼 — TS import 도달 집합(472-0 g472/graph.mjs 꼴 · 읽기만).
|
||||
* node helper_472_3_graph.mjs <저장소 뿌리> <입구.ts> [<입구.ts> …]
|
||||
* 입구마다 도달 파일(뿌리 상대 · `/`)을 JSON 한 벌로 표준출력 — 정적 import · export from · 동적
|
||||
* import() · new URL(…, import.meta.url)(워커) · `import type` · `export type` 은 뺌(지워짐). */
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const [root, ...entries] = process.argv.slice(2);
|
||||
const alias = { "@ui/": "ui_template/", "@config/": "config/", "@util/": "common_util/" };
|
||||
const rel = (p) => path.relative(root, p).split(path.sep).join("/");
|
||||
const exists = (p) => {
|
||||
for (const c of [p, `${p}.ts`, `${p}/index.ts`])
|
||||
if (fs.existsSync(c) && fs.statSync(c).isFile()) return c;
|
||||
return null;
|
||||
};
|
||||
function resolve(from, spec) {
|
||||
let base;
|
||||
if (spec.startsWith(".")) base = path.resolve(path.dirname(from), spec);
|
||||
else {
|
||||
const a = Object.keys(alias).find((k) => spec.startsWith(k));
|
||||
if (!a) return null;
|
||||
base = path.resolve(root, alias[a] + spec.slice(a.length));
|
||||
}
|
||||
return exists(base.replace(/\.js$/, ""));
|
||||
}
|
||||
const re =
|
||||
/(?:import|export)\s+(?:type\s+)?(?:[^;"'`]*?\sfrom\s+)?["']([^"']+)["']|import\(\s*["']([^"']+)["']\s*\)|new URL\(\s*["']([^"']+)["']\s*,\s*import\.meta\.url/g;
|
||||
function deps(file) {
|
||||
if (!/\.(ts|mts)$/.test(file)) return [];
|
||||
const src = fs.readFileSync(file, "utf8");
|
||||
const out = [];
|
||||
for (const m of src.matchAll(re)) {
|
||||
if (/^(?:import|export)\s+type\s/.test(m[0])) continue;
|
||||
const target = resolve(file, m[1] || m[2] || m[3]);
|
||||
if (target) out.push(target);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
const result = {};
|
||||
for (const entry of entries) {
|
||||
const seen = new Set();
|
||||
const stack = [path.resolve(root, entry)];
|
||||
while (stack.length) {
|
||||
const file = stack.pop();
|
||||
if (seen.has(file)) continue;
|
||||
seen.add(file);
|
||||
stack.push(...deps(file));
|
||||
}
|
||||
result[entry] = [...seen].map(rel).sort();
|
||||
}
|
||||
process.stdout.write(JSON.stringify(result));
|
||||
@@ -0,0 +1,7 @@
|
||||
/* 472-3 헬퍼 — 등록부와 묶음 단계 기댐을 한 묶음으로(vite ssr) · test_472_3_calc_registry.py 가 JSON 으로 받음. */
|
||||
export { CALC_REGISTRY } from "../../A00_Common/design/logic/design_calc_registry";
|
||||
export {
|
||||
AFTER_STAGES,
|
||||
BROWSER_STAGES,
|
||||
STAGE_DEPS,
|
||||
} from "../../A00_Common/design/logic/design_pipeline";
|
||||
@@ -33,6 +33,8 @@ POPOUT_FILES = [
|
||||
"B05_Profile/B05_Profile_UI_Drainage_Panel.ts",
|
||||
"B05_Profile/B05_Profile_UI_Structures_Marks.ts",
|
||||
"common_util/common_util_mass_haul_view.ts",
|
||||
"common_util/common_util_mass_haul_callout.ts",
|
||||
"common_util/common_util_mass_haul_legend.ts",
|
||||
]
|
||||
# 메인 창 전역 틀 · 시계 — `x.requestAnimationFrame` 처럼 창을 짚은 것은 제외
|
||||
MAIN_WINDOW_CLOCK = re.compile(
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
"""PLAN 472-3 (요구 273) — 계산 등록부 · 서버 Node 입구 · 경계 시험.
|
||||
|
||||
등록부 = `A00_Common/design/logic/design_calc_registry.ts`(순수 자료 · 목록 1.1 표 # 하나 = id 하나)
|
||||
Node 입구 = `A00_Common/design/logic/design_calc_node.ts`(`build:design-calc` · id → 함수 표).
|
||||
|
||||
못박는 것
|
||||
- id · # 하나씩 · 1.1 표 # 전부(R1 – N1 · 43).
|
||||
- id 마다 실행기 있음 — `node:` 는 입구 표에 · `py:` · `browser:` · ts · 짝 이름은 그 파일에 ·
|
||||
거울 시험 파일 있음.
|
||||
- 묶음 `STAGE_DEPS`(`design_pipeline.ts`) = 등록부 입력에서 만든 기댐(같은지만 · 묶음 안 고침).
|
||||
- 서버 쪽(server_canon · server_only) TS 가 브라우저 import 그래프에 없음 · 등록부는 안 끔.
|
||||
- Node 입구 도달 집합 = logic · common_util · 자료만 + 「알려진 끌림」(`KNOWN_PULLS`) — 그 밖은
|
||||
실패 · 안 나타난 알려진 끌림도 실패(목록에서 지움 — 472-2 2-B · 472-4 가 하나씩 걷어 빈 목록).
|
||||
- Node 입구 값 = 기존 묶음 값(고정 자료 `fixtures/golden_472` · server-calc 면적 · 다단 벽 ·
|
||||
누가토량 · 배분 · corridor 3D) · 터파기 = 파이썬 짝.
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_472_3_calc_registry.py -q
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
FIXTURE = HERE / "fixtures" / "golden_472"
|
||||
REGISTRY = "A00_Common/design/logic/design_calc_registry.ts"
|
||||
NODE_ENTRY = "A00_Common/design/logic/design_calc_node.ts"
|
||||
BROWSER_ENTRY = "A00_Common/main.ts"
|
||||
BUNDLE = ROOT / "config" / "design_calc_node" / "design_calc_node.js"
|
||||
NODE_IDS = {
|
||||
"structure.layouts",
|
||||
"masshaul.cumulative",
|
||||
"haul.plan",
|
||||
"excavation.trench",
|
||||
"corridor.mesh",
|
||||
}
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not (ROOT / "config" / "node_modules" / "vite").is_dir(),
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
# 화면 · 세션 끌림(목록 1.3) — 472-2 2-B 가 정리(PIPE_DISPLAY_NAME → common_util · Api_Fetch 세션 ·
|
||||
# 구조물 그림이 고른 것 읽기 · 사진첩).
|
||||
SESSION_PULLS = (
|
||||
"A00_Common/b_page_state.ts",
|
||||
"A00_Common/design/design_pick_match.ts",
|
||||
"A00_Common/design/design_pipe_ids.ts",
|
||||
"A00_Common/design/design_selection.ts",
|
||||
"A00_Common/design/design_store.ts",
|
||||
"B05_Profile/B05_Profile_UI_IrregularStations.ts",
|
||||
"B05_Profile/B05_Profile_UI_Profile_History.ts",
|
||||
"B06_Section/B06_Section_Api_Fetch.ts",
|
||||
"B06_Section/B06_Section_UI_Section_Common.ts",
|
||||
"config/config_frontend.ts",
|
||||
"ui_template/ui_template_elements.ts",
|
||||
"ui_template/ui_template_elements_base.ts",
|
||||
"ui_template/ui_template_elements_chart.ts",
|
||||
"ui_template/ui_template_elements_styles.ts",
|
||||
"ui_template/ui_template_locale.ts",
|
||||
"ui_template/ui_template_locale_a.ts",
|
||||
"ui_template/ui_template_locale_b1.ts",
|
||||
"ui_template/ui_template_locale_b2.ts",
|
||||
"ui_template/ui_template_locale_b3.ts",
|
||||
"ui_template/ui_template_locale_b4.ts",
|
||||
"ui_template/ui_template_locale_common.ts",
|
||||
"ui_template/ui_template_locale_m1.ts",
|
||||
"ui_template/ui_template_locale_m2.ts",
|
||||
)
|
||||
# 기하가 logic 밖(구조물 그림 · 3D 빌더 파일 안) — 472-4 가 logic · common_util 로.
|
||||
GEOMETRY_PULLS = (
|
||||
"B05_Profile/B05_Profile_UI_Corridor_Build.ts",
|
||||
"B05_Profile/B05_Profile_UI_Corridor_Envelope.ts",
|
||||
"B05_Profile/B05_Profile_UI_Corridor_Station.ts",
|
||||
"B05_Profile/B05_Profile_UI_Corridor_Station_Structure.ts",
|
||||
"B05_Profile/B05_Profile_UI_Corridor_Structures.ts",
|
||||
"B05_Profile/B05_Profile_UI_Corridor_Structures_Box.ts",
|
||||
"B05_Profile/B05_Profile_UI_Corridor_Structures_Chute.ts",
|
||||
"B05_Profile/B05_Profile_UI_Corridor_Structures_Path.ts",
|
||||
"B06_Section/B06_Section_Structure_Layouts.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Body_Guard.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Box.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Box_Geom.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Culvert.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Culvert_Tiers.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Culvert_Types.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Culvert_Wire.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Design_Surface.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Excavation.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Ford.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Ford_Geom.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Ford_Pavement.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Revetment.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Wall.ts",
|
||||
"B06_Section/B06_Section_UI_Cross_Wall_Hatch.ts",
|
||||
)
|
||||
KNOWN_PULLS = set(SESSION_PULLS) | set(GEOMETRY_PULLS)
|
||||
|
||||
|
||||
def _node(*args: str, timeout: int = 120) -> str:
|
||||
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", *args], cwd=ROOT, capture_output=True, text=True, encoding="utf-8", timeout=timeout
|
||||
)
|
||||
if proc.returncode:
|
||||
raise RuntimeError(proc.stderr)
|
||||
return proc.stdout
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def loaded(tmp_path_factory) -> dict:
|
||||
"""vite ssr 로 등록부 + 묶음 단계 기댐을 묶어 JSON 으로(472-1 꼴)."""
|
||||
work = tmp_path_factory.mktemp("reg")
|
||||
_node(
|
||||
str(ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"),
|
||||
"build",
|
||||
"--configLoader",
|
||||
"runner",
|
||||
"--ssr",
|
||||
"../resources/tester/helper_472_3_registry_entry.ts",
|
||||
"--outDir",
|
||||
str(work),
|
||||
"--logLevel",
|
||||
"error",
|
||||
timeout=300,
|
||||
)
|
||||
bundle = work / "bundle.mjs"
|
||||
shutil.copy(work / "helper_472_3_registry_entry.js", bundle)
|
||||
url = bundle.as_uri()
|
||||
out = _node(
|
||||
"--input-type=module",
|
||||
"-e",
|
||||
f"const m = await import({json.dumps(url)}); console.log(JSON.stringify("
|
||||
"{registry: m.CALC_REGISTRY, deps: m.STAGE_DEPS, "
|
||||
"stages: [...m.BROWSER_STAGES, ...m.AFTER_STAGES]}))",
|
||||
)
|
||||
return json.loads(out)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def reach() -> dict[str, list[str]]:
|
||||
out = _node(
|
||||
str(HERE / "helper_472_3_graph.mjs"), str(ROOT), NODE_ENTRY, REGISTRY, BROWSER_ENTRY
|
||||
)
|
||||
return json.loads(out)
|
||||
|
||||
|
||||
def _run(payload: dict) -> dict | None:
|
||||
from common_util.common_util_node_bundle import run_bundle_json
|
||||
|
||||
return run_bundle_json(BUNDLE, "build:design-calc", payload)
|
||||
|
||||
|
||||
def _fixture(name: str):
|
||||
return json.loads((FIXTURE / name).read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _context() -> dict:
|
||||
ctx = _fixture("context.json")
|
||||
keys = ("earthwork_conversion", "haul_equipment_limits", "natural_spoil_min_ground_slope")
|
||||
return {k: ctx[k] for k in keys}
|
||||
|
||||
|
||||
def _has_name(ref: str) -> bool:
|
||||
"""`<경로>#<이름>` — 파일 있음 · 그 이름이 정의됨(py def · ts export)."""
|
||||
path, name = ref.split("#")
|
||||
file = ROOT / path
|
||||
if not file.is_file():
|
||||
return False
|
||||
src = file.read_text(encoding="utf-8")
|
||||
if path.endswith(".py"):
|
||||
return re.search(rf"^\s*(?:async\s+)?def {re.escape(name)}\(", src, re.M) is not None
|
||||
pattern = rf"^export\s+(?:async\s+)?(?:function\*?|const|let|class)\s+{re.escape(name)}\b"
|
||||
return re.search(pattern, src, re.M) is not None
|
||||
|
||||
|
||||
# ── 등록부 자료 ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_id_하나씩_표_전부(loaded) -> None:
|
||||
rows = loaded["registry"]
|
||||
ids = [row["id"] for row in rows]
|
||||
nos = [row["no"] for row in rows]
|
||||
assert len(ids) == len(set(ids))
|
||||
assert len(nos) == len(set(nos))
|
||||
want = {f"{k}{i}" for k, n in (("R", 8), ("G", 4), ("P", 8), ("C", 7)) for i in range(1, n + 1)}
|
||||
want |= {f"F{i}" for i in range(1, 10)} | {"E1", "E2"} | {f"D{i}" for i in range(1, 5)} | {"N1"}
|
||||
assert set(nos) == want
|
||||
|
||||
|
||||
def test_id_마다_실행기_있음(loaded) -> None:
|
||||
bad = []
|
||||
for row in loaded["registry"]:
|
||||
kind, _, ref = row["runner"].partition(":")
|
||||
if row["site"] == "browser_only":
|
||||
ok = kind == "browser" and _has_name(ref)
|
||||
elif kind == "node":
|
||||
ok = ref == row["id"] and ref in NODE_IDS
|
||||
else:
|
||||
ok = kind == "py" and _has_name(ref)
|
||||
ok = ok and ("until" not in row or kind == "py")
|
||||
ok = ok and all(_has_name(r) for r in (row["ts"], row["pair"]) if r)
|
||||
ok = ok and all((HERE / f"{name}.py").is_file() for name in row["mirror"])
|
||||
if not ok:
|
||||
bad.append(row["id"])
|
||||
assert bad == []
|
||||
node_rows = {row["id"] for row in loaded["registry"] if row["runner"].startswith("node:")}
|
||||
assert node_rows == NODE_IDS
|
||||
|
||||
|
||||
def test_묶음_STAGE_DEPS_등록부_입력과_같음(loaded) -> None:
|
||||
"""같은 단계 줄들의 inputs 중 `input.*` 은 그대로 · 다른 단계 id 는 그 단계 이름."""
|
||||
rows = loaded["registry"]
|
||||
stage_of = {row["id"]: row["stage"] for row in rows}
|
||||
derived: dict[str, set[str]] = {name: set() for name in loaded["stages"]}
|
||||
for row in rows:
|
||||
if not row["stage"]:
|
||||
continue
|
||||
for item in row["inputs"]:
|
||||
if item.startswith("input."):
|
||||
derived[row["stage"]].add(item)
|
||||
elif stage_of.get(item) and stage_of[item] != row["stage"]:
|
||||
derived[row["stage"]].add(stage_of[item])
|
||||
assert {k: set(v) for k, v in loaded["deps"].items()} == derived
|
||||
# 앞 id 는 등록부에 있음(이름 틀림 막기)
|
||||
ids = set(stage_of)
|
||||
prefixes = ("input.", "file:", "session:", "context.", "store.")
|
||||
stray = [
|
||||
i for row in rows for i in row["inputs"] if not i.startswith(prefixes) and i not in ids
|
||||
]
|
||||
assert stray == []
|
||||
|
||||
|
||||
# ── import 경계 ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_서버_쪽_코드_브라우저_밖(loaded, reach) -> None:
|
||||
browser = set(reach[BROWSER_ENTRY])
|
||||
server = [
|
||||
row["ts"].split("#")[0]
|
||||
for row in loaded["registry"]
|
||||
if row["site"] in ("server_canon", "server_only") and row["ts"]
|
||||
]
|
||||
assert server # 유토 배분(common_util_mass_haul_balance)
|
||||
assert [path for path in server if path in browser] == []
|
||||
assert NODE_ENTRY not in browser
|
||||
assert reach[REGISTRY] == [REGISTRY] # 순수 자료 — 아무것도 안 끔
|
||||
|
||||
|
||||
def test_Node_입구_도달_경계(reach) -> None:
|
||||
files = set(reach[NODE_ENTRY])
|
||||
own = {
|
||||
f for f in files if "/logic/" in f or f.startswith("common_util/") or f.endswith(".json")
|
||||
}
|
||||
outside = files - own
|
||||
assert sorted(outside - KNOWN_PULLS) == [] # 새 끌림
|
||||
assert sorted(KNOWN_PULLS - outside) == [] # 걷힌 끌림 — KNOWN_PULLS 에서 지울 것
|
||||
|
||||
|
||||
# ── Node 입구 값 ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def golden_run() -> dict:
|
||||
out = _run(
|
||||
{
|
||||
"run": ["haul.plan", "structure.layouts", "masshaul.cumulative"],
|
||||
"inputs": {"detail": _fixture("detail.json"), "context": _context()},
|
||||
}
|
||||
)
|
||||
assert isinstance(out, dict)
|
||||
assert set(out["available"]) == NODE_IDS
|
||||
return out["results"]
|
||||
|
||||
|
||||
def test_server_calc_묶음과_같음(golden_run) -> None:
|
||||
from B06_Section.B06_Section_Server_Calc_Prebuild import _NPM_SCRIPT
|
||||
from B06_Section.B06_Section_Server_Calc_Prebuild import BUNDLE as SC_BUNDLE
|
||||
from common_util.common_util_node_bundle import run_bundle_json
|
||||
|
||||
sc = run_bundle_json(
|
||||
SC_BUNDLE, _NPM_SCRIPT, {"detail": _fixture("detail.json"), "context": _context()}
|
||||
)
|
||||
layouts = golden_run["structure.layouts"]
|
||||
assert layouts["areas"] == sc["areas"] and sc["areas"]
|
||||
assert layouts["extra_walls"] == sc["extra_walls"]
|
||||
merged = {**golden_run["masshaul.cumulative"], **golden_run["haul.plan"]}
|
||||
assert merged == sc["mass_haul"]
|
||||
assert merged["final_cumulative_m3"] == -17570.7 # 472-1 화면 참값
|
||||
|
||||
|
||||
def test_corridor_묶음과_같음() -> None:
|
||||
from B05_Profile.B05_Profile_Corridor_Prebuild import BUNDLE as CO_BUNDLE
|
||||
from common_util.common_util_node_bundle import run_bundle_json
|
||||
|
||||
detail = _fixture("detail.json")
|
||||
points = [
|
||||
{"x": x, "y": y, "order": i} for i, (x, y) in enumerate(_fixture("plan.json")["planned"])
|
||||
]
|
||||
co = run_bundle_json(CO_BUNDLE, "build:corridor", {"detail": detail, "route_points": points})
|
||||
out = _run({"run": ["corridor.mesh"], "inputs": {"detail": detail, "route_points": points}})
|
||||
assert co and out["results"]["corridor.mesh"] == co
|
||||
|
||||
|
||||
def test_터파기_파이썬_짝과_같음() -> None:
|
||||
from common_util import common_util_excavation as py
|
||||
|
||||
pipes = [{"diameter_mm": d} for d in (300, 800, 1500, 250, None)]
|
||||
walls = [
|
||||
{"height_m": 2.0, "average_thickness_m": 0.5, "has_foundation": True},
|
||||
{"height_m": 1.2, "average_thickness_m": 0.35, "has_foundation": False},
|
||||
{"height_m": None, "average_thickness_m": 0.4, "has_foundation": True},
|
||||
]
|
||||
out = _run(
|
||||
{"run": ["excavation.trench"], "inputs": {"excavation": {"pipes": pipes, "walls": walls}}}
|
||||
)
|
||||
got = out["results"]["excavation.trench"]
|
||||
assert [p["width_m"] for p in got["pipes"]] == [
|
||||
py.pipe_trench_width_m(p["diameter_mm"]) for p in pipes
|
||||
]
|
||||
for row, wall in zip(got["walls"], walls, strict=True):
|
||||
assert row["width_m"] == py.wall_trench_width_m(wall["average_thickness_m"])
|
||||
want = py.wall_trench_area_m2(
|
||||
wall["height_m"], wall["average_thickness_m"], has_foundation=wall["has_foundation"]
|
||||
)
|
||||
assert row["area_m2"] == pytest.approx(want) if want is not None else row["area_m2"] is None
|
||||
|
||||
|
||||
def test_모르는_id_는_실패() -> None:
|
||||
assert _run({"run": ["stations.list"], "inputs": {}}) is None
|
||||
@@ -0,0 +1,70 @@
|
||||
"""PLAN 472-3 3-B (요구 273) — 파이썬 실행기 `common_util_calc_runner` → Node 입구 → 기존 묶음 값.
|
||||
|
||||
못박는 것
|
||||
- `NODE_IDS` = 등록부 `node:` 줄 = 입구 표(`available`).
|
||||
- 고정 자료 `fixtures/golden_472` 로 `run_calc` 값 = [저장] 재계산이 부르는 server-calc 묶음 값
|
||||
(`Server_Calc_Prebuild` BUNDLE · 면적 · 다단 벽 · 누가토량 · 배분 · 끝값 −17,570.7).
|
||||
- Node 실행기 아닌 id(파이썬 · 브라우저 실행기)는 ValueError.
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_472_3_calc_runner.py -q
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from common_util import common_util_calc_runner as runner
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
FIXTURE = HERE / "fixtures" / "golden_472"
|
||||
REGISTRY = ROOT / "A00_Common" / "design" / "logic" / "design_calc_registry.ts"
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not (ROOT / "config" / "node_modules" / "vite").is_dir(),
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
|
||||
def _fixture(name: str):
|
||||
return json.loads((FIXTURE / name).read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _context() -> dict:
|
||||
ctx = _fixture("context.json")
|
||||
keys = ("earthwork_conversion", "haul_equipment_limits", "natural_spoil_min_ground_slope")
|
||||
return {k: ctx[k] for k in keys}
|
||||
|
||||
|
||||
def test_NODE_IDS_등록부_입구와_같음() -> None:
|
||||
src = REGISTRY.read_text(encoding="utf-8")
|
||||
assert set(re.findall(r'runner: "node:([a-z_.]+)"', src)) == runner.NODE_IDS
|
||||
out = runner.run_bundle_json(runner.BUNDLE, runner.NPM_SCRIPT, {"run": [], "inputs": {}})
|
||||
assert set(out["available"]) == runner.NODE_IDS
|
||||
|
||||
|
||||
def test_실행기_값_server_calc_와_같음() -> None:
|
||||
from B06_Section.B06_Section_Server_Calc_Prebuild import _NPM_SCRIPT, BUNDLE
|
||||
from common_util.common_util_node_bundle import run_bundle_json
|
||||
|
||||
sc = run_bundle_json(
|
||||
BUNDLE, _NPM_SCRIPT, {"detail": _fixture("detail.json"), "context": _context()}
|
||||
)
|
||||
got = runner.run_calc(
|
||||
["structure.layouts", "masshaul.cumulative", "haul.plan"],
|
||||
{"detail": _fixture("detail.json"), "context": _context()},
|
||||
)
|
||||
assert got["structure.layouts"]["areas"] == sc["areas"] and sc["areas"]
|
||||
assert got["structure.layouts"]["extra_walls"] == sc["extra_walls"]
|
||||
merged = {**got["masshaul.cumulative"], **got["haul.plan"]}
|
||||
assert merged == sc["mass_haul"]
|
||||
assert merged["final_cumulative_m3"] == -17570.7
|
||||
|
||||
|
||||
def test_Node_실행기_아닌_id_는_ValueError() -> None:
|
||||
with pytest.raises(ValueError, match="stations.list"):
|
||||
runner.run_calc(["masshaul.cumulative", "stations.list"], {})
|
||||
Reference in New Issue
Block a user