|
|
|
@@ -16,6 +16,7 @@
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
import json
|
|
|
|
|
import sys
|
|
|
|
|
from pathlib import Path
|
|
|
|
@@ -319,3 +320,200 @@ def test_화면_서버_노선_화면_셈과_같음(screens, case):
|
|
|
|
|
assert both["calls"] == []
|
|
|
|
|
# 저장 노선(초안 없음) = 서버 안 부름 · 초안 = server 판만 서버 노선
|
|
|
|
|
assert bool(server["calls"]) is (case == "draft")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── follow 「서버」 — 창구 맥락 · Node route · follow 두 단계 ─────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_창구_follow_맥락_브라우저_정본칸_버림(monkeypatch):
|
|
|
|
|
from B06_Section import B06_Section_Router_Calc_Context as ctx
|
|
|
|
|
|
|
|
|
|
async def context(_pid):
|
|
|
|
|
return {"plan": {"planned": "서버"}, "saved_pipes": ["서버"], "longitudinal": None}
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(ctx, "follow_context", context)
|
|
|
|
|
inputs = {
|
|
|
|
|
"stages": {
|
|
|
|
|
"until": "follow",
|
|
|
|
|
"input": {"route": 1},
|
|
|
|
|
"saved_edits": None,
|
|
|
|
|
"plan": {"planned": "브라우저"},
|
|
|
|
|
"saved_pipes": ["브라우저"],
|
|
|
|
|
"grid": {"path": "C:/x"},
|
|
|
|
|
"detail": {"x": 1},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
assert asyncio.run(ctx.attach_follow_context("p", inputs)) is None
|
|
|
|
|
assert inputs["stages"] == {
|
|
|
|
|
"until": "follow",
|
|
|
|
|
"input": {"route": 1},
|
|
|
|
|
"saved_edits": None,
|
|
|
|
|
"plan": {"planned": "서버"},
|
|
|
|
|
"saved_pipes": ["서버"],
|
|
|
|
|
"longitudinal": None,
|
|
|
|
|
}
|
|
|
|
|
other = {"stages": {"plan": "브라우저"}} # until 없음 = 그대로(B-1 단계 길)
|
|
|
|
|
assert asyncio.run(ctx.attach_follow_context("p", other)) is None and other["stages"] == {
|
|
|
|
|
"plan": "브라우저"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _golden_stages() -> dict:
|
|
|
|
|
from common_util.common_util_surface_sampler import dtm_grid_bytes
|
|
|
|
|
|
|
|
|
|
def fixture(name):
|
|
|
|
|
return json.loads((FIXTURE / name).read_text(encoding="utf-8"))
|
|
|
|
|
|
|
|
|
|
plan = fixture("plan.json")
|
|
|
|
|
nodes = [[n["x"], n["y"]] for n in plan["nodes"]]
|
|
|
|
|
at = len(nodes) // 2
|
|
|
|
|
nodes[at] = [nodes[at][0] + 6, nodes[at][1] + 4] # 교각점 하나 옮긴 노선 초안
|
|
|
|
|
draft = {
|
|
|
|
|
"nodes": nodes,
|
|
|
|
|
"curveOn": [n["radius_m"] is not None for n in plan["nodes"]],
|
|
|
|
|
"curveRadius": [None] * len(nodes),
|
|
|
|
|
"curveLock": [None] * len(nodes),
|
|
|
|
|
"curveArc": [None] * len(nodes),
|
|
|
|
|
"stationSnap": bool(plan.get("station_snap")),
|
|
|
|
|
"stationSnapStep": plan.get("station_snap_step"),
|
|
|
|
|
}
|
|
|
|
|
grid = ROOT / "tmp" / "511b5_grid.bin"
|
|
|
|
|
if not grid.is_file():
|
|
|
|
|
grid.write_bytes(
|
|
|
|
|
dtm_grid_bytes(FIXTURE / "project/B04_PreProcess/models/dtm_classification_smooth.npz")
|
|
|
|
|
)
|
|
|
|
|
return {
|
|
|
|
|
"plan": plan,
|
|
|
|
|
"detail": fixture("detail.json"),
|
|
|
|
|
"types": fixture("structure_types.json")["types"],
|
|
|
|
|
"saved_pipes": [
|
|
|
|
|
{k: v for k, v in p.items() if k != "lonlat"}
|
|
|
|
|
for p in fixture("pipes.json")["pipe_points"]
|
|
|
|
|
],
|
|
|
|
|
"saved_structures": fixture("structures.json")["structures"],
|
|
|
|
|
"interval_m": 20,
|
|
|
|
|
"grid": {"path": str(grid), "design": "las"},
|
|
|
|
|
"input": {"route": draft, "plusStations": fixture("plus_stations.json")["chainages"]},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_Node_follow_두_단계_묶음_첫_바퀴와_같음():
|
|
|
|
|
from common_util import common_util_calc_runner as runner
|
|
|
|
|
|
|
|
|
|
stages = _golden_stages()
|
|
|
|
|
full = runner.run_calc_served(["stage.route", "stage.follow"], {"stages": stages})
|
|
|
|
|
only = runner.run_calc_served(
|
|
|
|
|
["stage.route", "stage.follow"], {"stages": {**stages, "until": "follow", "grid": None}}
|
|
|
|
|
)
|
|
|
|
|
assert full and only and "error" not in full and "error" not in only, (full, only)
|
|
|
|
|
assert only["results"]["stage.route"] == full["results"]["stage.route"]
|
|
|
|
|
got = only["results"]["stage.follow"]
|
|
|
|
|
assert got["patch"] == full["results"]["stage.follow"]
|
|
|
|
|
assert got["patch"]["pipes"] and got["followed"] # 관 따라감 · 따라가기만 한 자리 곁 자료
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── 화면 — follow 「서버」 끌기 여러 걸음 뒤 놓음 = 화면 셈(검증_sub3 · Playwright · 저장 안 함) ──
|
|
|
|
|
|
|
|
|
|
JS_DRAG = r"""async (steps) => {
|
|
|
|
|
const urls = performance.getEntriesByType('resource').map((e) => e.name);
|
|
|
|
|
const mod = async (re) => import(urls.find((n) => re.test(n)));
|
|
|
|
|
const page = await mod(/design_pipeline_page\.ts/);
|
|
|
|
|
const pid = localStorage.getItem('frd_current_project_id');
|
|
|
|
|
const plan = await (await mod(/B05_Profile_Api_Replan\.ts/)).fetchRoutePlan(pid);
|
|
|
|
|
const flat = (await mod(/B05_Profile_UI_RouteEdit_Edits\.ts/))
|
|
|
|
|
.flattenServerPlan(plan.nodes, plan.curves ?? [], plan.node_edits);
|
|
|
|
|
const bridge = page.routeDraftBridge(pid, null);
|
|
|
|
|
const at = Math.floor(flat.planned.length / 2);
|
|
|
|
|
for (let k = 1; k <= steps; k += 1) { // 끌기 걸음 — 걸음마다 묶음이 돎(잠잠 안 기다림)
|
|
|
|
|
const nodes = flat.planned.map(([x, y]) => [x, y]);
|
|
|
|
|
nodes[at] = [nodes[at][0] + 2 * k, nodes[at][1] + 1.5 * k];
|
|
|
|
|
bridge.edited({
|
|
|
|
|
nodes,
|
|
|
|
|
curveOn: flat.curveOn.map((on) => on !== false),
|
|
|
|
|
curveRadius: flat.curveRadius.map((r) => r ?? null),
|
|
|
|
|
curveLock: flat.curveLock ?? nodes.map(() => null),
|
|
|
|
|
curveArc: flat.curveArc ?? nodes.map(() => null),
|
|
|
|
|
stationSnap: plan.station_snap ?? false,
|
|
|
|
|
stationSnapStep: plan.station_snap_step ?? null,
|
|
|
|
|
});
|
|
|
|
|
await new Promise((r) => setTimeout(r, 60));
|
|
|
|
|
}
|
|
|
|
|
await page.pagePipeline(pid).settled();
|
|
|
|
|
}"""
|
|
|
|
|
|
|
|
|
|
JS_FOLLOW = r"""async () => {
|
|
|
|
|
const urls = performance.getEntriesByType('resource').map((e) => e.name);
|
|
|
|
|
const page = await import(urls.find((n) => /design_pipeline_page\.ts/.test(n)));
|
|
|
|
|
const pid = localStorage.getItem('frd_current_project_id');
|
|
|
|
|
const pipe = page.pagePipeline(pid);
|
|
|
|
|
await page.reconcilePagePipeline(pid);
|
|
|
|
|
await pipe.settled();
|
|
|
|
|
const plain = (v) => JSON.parse(JSON.stringify(v ?? null));
|
|
|
|
|
const calls = (window.__aisloCalcServer?.calls ?? []).filter((c) => c.id === 'stage.follow');
|
|
|
|
|
const planline = pipe.output('planline');
|
|
|
|
|
return {
|
|
|
|
|
follow: plain(pipe.output('follow')),
|
|
|
|
|
inputs: plain(['pipes', 'structures', 'plusStations', 'alignment']
|
|
|
|
|
.map((k) => [k, pipe.input(k)])),
|
|
|
|
|
base_pvi: plain(planline?.basePvi), pvi: plain(pipe.output('profile')?.pvi),
|
|
|
|
|
calls: calls.map((c) => ({ round: c.roundMs, node: c.nodeMs, bytes: c.bytes })),
|
|
|
|
|
runs: (window.__aisloPipeline?.runs ?? []).slice(-1).map((r) => r.outcome),
|
|
|
|
|
};
|
|
|
|
|
}"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
|
|
|
def drags():
|
|
|
|
|
from test_511_b1_calc_node_stages import _UP, BASE, _started
|
|
|
|
|
|
|
|
|
|
if not BASE or _UP is None or _UP != _started(BASE.replace("5178", "8005")):
|
|
|
|
|
pytest.skip("이 폴더 서버 없음")
|
|
|
|
|
sync = pytest.importorskip("playwright.sync_api")
|
|
|
|
|
cred = json.loads((ROOT / "tmp" / "aislo_cred.json").read_text(encoding="utf-8"))
|
|
|
|
|
shots = {}
|
|
|
|
|
with sync.sync_playwright() as play:
|
|
|
|
|
chrome = play.chromium.launch(channel="msedge", headless=True)
|
|
|
|
|
try:
|
|
|
|
|
for mode in ("both", "server"):
|
|
|
|
|
context = chrome.new_context(viewport={"width": 1600, "height": 1000})
|
|
|
|
|
login = {k: cred[k] for k in ("email", "password")}
|
|
|
|
|
r = context.request.post(
|
|
|
|
|
f"{BASE}/api/auth/login/request", data=login, timeout=60000
|
|
|
|
|
)
|
|
|
|
|
assert r.ok, f"로그인 실패 {r.status}" # 비밀번호는 안 찍음
|
|
|
|
|
if mode == "server": # 판은 안 고침 — 이 창이 받는 판만 follow = server
|
|
|
|
|
|
|
|
|
|
def board(route):
|
|
|
|
|
body = route.fetch().json()
|
|
|
|
|
body["sites"]["follow"] = "server"
|
|
|
|
|
route.fulfill(json=body)
|
|
|
|
|
|
|
|
|
|
context.route("**/api/dashboard/calc-sites", board)
|
|
|
|
|
page = context.new_page()
|
|
|
|
|
page.add_init_script(
|
|
|
|
|
f"localStorage.setItem('frd_current_project_id', '{VERIFY}');"
|
|
|
|
|
" performance.setResourceTimingBufferSize(20000);"
|
|
|
|
|
)
|
|
|
|
|
page.goto(f"{BASE}/#/b05-profile", wait_until="domcontentloaded")
|
|
|
|
|
page.wait_for_function("() => !!window.__aisloPipeline", timeout=90000)
|
|
|
|
|
page.wait_for_timeout(9000)
|
|
|
|
|
page.evaluate(JS_DRAG, 4) # 끌기 네 걸음(세션 초안 · 창과 함께 버림)
|
|
|
|
|
page.wait_for_timeout(4000) # 잠잠 400 ms · 서버 · 다시 돎
|
|
|
|
|
shots[mode] = page.evaluate(JS_FOLLOW)
|
|
|
|
|
context.close()
|
|
|
|
|
finally:
|
|
|
|
|
chrome.close()
|
|
|
|
|
(ROOT / "tmp" / "511b5_drags.json").write_text(json.dumps(shots), encoding="utf-8")
|
|
|
|
|
return shots
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_화면_follow_서버_끌기_뒤_화면_셈과_같음(drags):
|
|
|
|
|
both, server = drags["both"], drags["server"]
|
|
|
|
|
assert both["runs"] == server["runs"] == ["done"]
|
|
|
|
|
assert both["calls"] == [] and server["calls"] # server 판만 놓은 뒤 서버 follow
|
|
|
|
|
# 계획고 옮김 = 끌기 중 화면 셈이 입력에 옮겨 둔 값 그대로(서버 follow 는 저장분에서 새로 셈 · 옮김
|
|
|
|
|
# 기준(앞 노선 · 앞 첫 계획선) 없음 → patch 에 alignment 안 냄) — 입력 · 종단은 같아야 함
|
|
|
|
|
drop = lambda patch: {k: v for k, v in patch.items() if k != "alignment"} # noqa: E731
|
|
|
|
|
assert drop(server["follow"]) == drop(both["follow"])
|
|
|
|
|
for key in ("inputs", "base_pvi", "pvi"):
|
|
|
|
|
assert server[key] == both[key], key
|
|
|
|
|