fix(472-5 남음): 암선 · 암 경사 · B05 상단측 메모리 맵 걷음 · 노선 저장 뒤 옛 노선 칸 · hold 비움
- 암 경계 · 암 절토 경사 저장소(`B06_Section_UI_Page_Persist`) = 세션 칸이 주인 — 읽을 때마다 측점 열쇠 칸(`readCells`) · 고칠 때 지금 칸을 읽어 그 측점만 바꿔 씀(`putCell`) · 메모리 맵 · load · 갈아끼움 다시 읽기(keyedStores · reloadKeyedStores) 걷음 · ↶ 뒤 다음 조작이 되돌림을 덮던 흠(N5) 고침 - 저장 재료 = `offsets()` · `ratios()`(새 Map) · ↶ ↷ 가 rockb · cutslope 칸을 바꾸면 조정창과 같은 onRestore 로 카드 · 면적 다시 - B05 상단측 메모리 맵(uphillOverrides · load/saveUphillOverrides) 걷음 — 읽는 곳 없이 저장 뒤 비우기만 하던 사본 · 값 주인 = 세션 칸 `uphill` · 상단측 차례 = 기본 조각 그대로 - 노선 바뀐 [저장] 성공 뒤(페이지 다시 세운 뒤) 옛 노선 번호의 노선 범위 초안(`clearRouteDrafts`) · `route-draft-hold` 비움 · 넘김 규칙 그대로 · 같은 번호면 노선 칸 안 지움 · 실패면 그대로 - 시험 test_472_5r_rock_owner 7(옛 코드 빨강 5) · 옛 단언 고침(176_3 · 271 · 443 상단측 조각 · 344_3 저장소 load) - 위키 암선 · 암 경사 · ↶ ↷ 규칙 · 노선 저장 뒤 정리 줄 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -119,19 +119,17 @@ async function run(parts) {
|
||||
seed(true);
|
||||
out.closed = await run(undefined);
|
||||
|
||||
// ② 종단 보기를 연 뒤 [저장] = 종단 보기 몫(상단측 차례) — 구조물 몫은 PUT 없음(가짜)
|
||||
// ② 종단 보기를 연 뒤 [저장] = 종단 보기 몫 — 상단측 차례는 기본 조각 그대로(값 주인 = 세션 칸 · 472-5)
|
||||
seed(true);
|
||||
const memory = new Map([["40.000", "right"]]);
|
||||
const parts = actions.profileSaveParts({
|
||||
projectId: P,
|
||||
latest: () => LATEST,
|
||||
confirmedSurface: () => ({ model_id: 7 }),
|
||||
routeReady: () => true,
|
||||
bridge: () => ({ structuresSavePart: async () => null }),
|
||||
uphillOverrides: memory,
|
||||
});
|
||||
out.open = await run({ uphill: parts.parts.uphill });
|
||||
out.openMemoryAfter = memory.size;
|
||||
out.openOwnUphill = typeof parts.parts.uphill;
|
||||
|
||||
// ③ 새로고침해 B06 로 바로 — 노선 캐시 없음 = 서버에서 한 번 읽고 같은 본문
|
||||
seed(false);
|
||||
|
||||
@@ -106,8 +106,7 @@ const rock = m.createRockBoundaryStore({
|
||||
});
|
||||
rock.control.adjust(20, -0.3);
|
||||
out.rockKeys = keysOf("rockb", R);
|
||||
rock.load();
|
||||
out.rockLoaded = Object.fromEntries(rock.offsets);
|
||||
out.rockLoaded = Object.fromEntries(rock.offsets());
|
||||
out.rockSession = m.readRockBoundarySession(P, R);
|
||||
|
||||
/* 3. 구조물 id 를 아직 모를 때(첫 열기) — 쓴 때 자리(@at)로 그 측점 고침을 읽음 */
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/* 472-5 남음 헬퍼 — node helper_472_5r_rock.mjs <묶음.mjs> · 표준출력 JSON.
|
||||
* ① 암 경계(▲▼ · 되돌림) · 암 절토 경사를 고침 → ↶ → 다른 측점을 고침 → ↶ ↷ — 세션 칸 · 카드 읽기 · 저장 재료가
|
||||
* 되돌린 값을 따르는가(옛 코드 = 메모리 맵이 옛 값을 쥐고 다음 조작이 칸을 통째로 다시 씀 · N5).
|
||||
* ② clearRouteDrafts — 그 노선 번호의 노선 범위 초안만 비움(다른 노선 · 프로젝트 칸 그대로). */
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const storage = () => {
|
||||
const m = new Map();
|
||||
return {
|
||||
getItem: (k) => (m.has(k) ? m.get(k) : null),
|
||||
setItem: (k, v) => m.set(k, String(v)),
|
||||
removeItem: (k) => m.delete(k),
|
||||
key: (i) => [...m.keys()][i] ?? null,
|
||||
get length() {
|
||||
return m.size;
|
||||
},
|
||||
clear: () => m.clear(),
|
||||
};
|
||||
};
|
||||
const any = new Proxy(function () {}, {
|
||||
get: (_t, k) => (k === Symbol.toPrimitive ? () => 0 : any),
|
||||
apply: () => any,
|
||||
construct: () => any,
|
||||
});
|
||||
globalThis.window = { sessionStorage: storage(), localStorage: storage() }; // 손길 듣기 없음 = 열 때 규칙 꺼짐
|
||||
globalThis.sessionStorage = window.sessionStorage;
|
||||
globalThis.localStorage = window.localStorage;
|
||||
globalThis.document = any;
|
||||
|
||||
const m = await import(pathToFileURL(process.argv[2]).href);
|
||||
const P = "p4725rock";
|
||||
const R = 7;
|
||||
localStorage.setItem("frd_current_project_id", P);
|
||||
const SAVED_ROCK = -0.8; // 저장분 암 경계(측점 100)
|
||||
const sections = [100, 120, 140].map((chainage_m) => ({
|
||||
chainage_m,
|
||||
station_id: `s${chainage_m}`,
|
||||
design: chainage_m === 100 ? { rock_boundary_offset_m: SAVED_ROCK } : {},
|
||||
}));
|
||||
const detail = { cross_sections: sections };
|
||||
const refreshed = [];
|
||||
const rock = m.createRockBoundaryStore({
|
||||
sessionKey: () => m.stateKey("rockb", P, R),
|
||||
detail: () => detail,
|
||||
refreshCard: (at) => refreshed.push(at),
|
||||
recompute: () => undefined,
|
||||
});
|
||||
rock.setDefaults(-0.5, 0.1);
|
||||
const cut = m.createCutSlopeStore({
|
||||
sessionKey: () => m.stateKey("cutslope", P, R),
|
||||
standard: () => ({ rock: { cut_slope_ratio: 0.4 }, soil: { cut_slope_ratio: 1.0 } }),
|
||||
refreshCard: () => undefined,
|
||||
recompute: () => undefined,
|
||||
});
|
||||
const cell = (name) => {
|
||||
const raw = m.readState(name, P, R) ?? {};
|
||||
// 측점 열쇠 칸 = 열쇠 → 값 · `@at`(쓴 때 누가거리) · `@seat` — 누가거리 → 값으로 풀어 봄
|
||||
const at = raw["@at"] ?? {};
|
||||
return Object.fromEntries(
|
||||
Object.entries(raw)
|
||||
.filter(([key]) => !key.startsWith("@"))
|
||||
.map(([key, value]) => [Number(at[key] ?? key).toFixed(2), value]),
|
||||
);
|
||||
};
|
||||
const cells = () => ({ rockb: cell("rockb"), cutslope: cell("cutslope") });
|
||||
const reads = () =>
|
||||
Object.fromEntries(
|
||||
sections.map((s) => [
|
||||
s.chainage_m,
|
||||
{ rock: rock.control.offsetFor(s), cut: cut.control.ratioFor(s) },
|
||||
]),
|
||||
);
|
||||
const out = {};
|
||||
|
||||
const history = m.createProfileHistory(() => undefined, 100);
|
||||
// 조작 A — 100 ▼(저장분 −0.8 → −0.9) · 120 ▲(기본 −0.5 → −0.4) · 140 암 경사 0.7
|
||||
rock.control.adjust(100, -0.1);
|
||||
rock.control.adjust(120, 0.1);
|
||||
cut.control.set(140, 0.7);
|
||||
history.record();
|
||||
out.afterA = cells();
|
||||
out.readsA = reads();
|
||||
|
||||
history.undo(); // ↶ = 세션 칸을 조작 전으로 되씀(저장소를 안 거침)
|
||||
out.afterUndo = cells();
|
||||
out.readsUndo = reads();
|
||||
|
||||
// 조작 B — 다른 측점(옛 코드 = 메모리 맵이 A 값을 쥐고 통째로 다시 씀)
|
||||
rock.control.adjust(140, -0.1);
|
||||
cut.control.set(100, 0.5);
|
||||
out.afterB = cells();
|
||||
out.payloadB = {
|
||||
rock: Object.fromEntries(typeof rock.offsets === "function" ? rock.offsets() : rock.offsets), // 옛 꼴 = Map
|
||||
cut: Object.fromEntries(typeof cut.ratios === "function" ? cut.ratios() : cut.ratios),
|
||||
};
|
||||
history.record();
|
||||
history.undo();
|
||||
out.afterUndo2 = cells();
|
||||
history.redo(); // ↷ = B 그대로
|
||||
out.afterRedo = cells();
|
||||
out.readsRedo = reads();
|
||||
// 되돌림(↺) — 암 경계 = 기본값 · 암 경사 = 0(표준 표시)
|
||||
rock.control.reset(140);
|
||||
cut.control.set(100, null);
|
||||
out.afterReset = cells();
|
||||
out.readsReset = reads();
|
||||
|
||||
// ② 노선 저장 뒤 옛 노선 칸만 비움
|
||||
m.writeState("rockb", { "grid:1": -0.7 }, P, 8);
|
||||
m.writeState("crossw", { "100.00": 6 }, P, R);
|
||||
m.writeState("uphill", { "100.000": "left" }, P);
|
||||
m.writeState("rockb", { "grid:1": -0.6 }, "other", R);
|
||||
const keys = () =>
|
||||
Array.from({ length: sessionStorage.length }, (_, i) => sessionStorage.key(i))
|
||||
.filter((key) => key.startsWith("aislo:draft:"))
|
||||
.sort();
|
||||
out.keysBefore = keys();
|
||||
m.clearRouteDrafts(P, R);
|
||||
out.keysAfter = keys();
|
||||
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -0,0 +1,7 @@
|
||||
/* 472-5 남음 헬퍼 — 암 경계 · 암 경사 값 주인 = 세션 칸(vite ssr) · test_472_5r_rock_owner.py 가 부름. */
|
||||
export { createProfileHistory } from "../../B05_Profile/B05_Profile_UI_Profile_History";
|
||||
export {
|
||||
createCutSlopeStore,
|
||||
createRockBoundaryStore,
|
||||
} from "../../B06_Section/B06_Section_UI_Page_Persist";
|
||||
export { clearRouteDrafts, readState, stateKey, writeState } from "../../A00_Common/b_page_state";
|
||||
@@ -0,0 +1,68 @@
|
||||
/* 472-5 남음 헬퍼 — 노선 바뀐 [저장] 뒤 옛 노선 세션 칸 · 초안 전 hold 비움(`design_save.ts` 그대로 · 서버 창구 ·
|
||||
* 세션 창구는 가짜). 짝 = `test_472_5r_rock_owner.py`. 표준출력 JSON. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
globalThis.window = { setTimeout, clearTimeout };
|
||||
|
||||
const anything = () =>
|
||||
new Proxy({}, { get: (_target, key) => (key === "__esModule" ? false : () => undefined) });
|
||||
|
||||
function load(file, stubs) {
|
||||
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
new Function("exports", "module", "require", js)(box.exports, box, (r) => stubs[r] ?? anything());
|
||||
return box.exports;
|
||||
}
|
||||
|
||||
async function once(newRouteId, fail) {
|
||||
const calls = [];
|
||||
const KEYS = ["settings", "pipes", "structures", "plusStations", "alignment"];
|
||||
const save = load(path.join(ROOT, "A00_Common", "design", "design_save.ts"), {
|
||||
"../b_page_state": {
|
||||
clearRouteDrafts: (p, r) => calls.push(`clearRouteDrafts:${p}:${r}`),
|
||||
clearState: (name, p) => calls.push(`clearState:${name}:${p}`),
|
||||
stateKey: () => null,
|
||||
},
|
||||
"./design_save_api": {
|
||||
SAVE_PART_KEYS: [...KEYS, "cross"],
|
||||
DesignSaveError: Error,
|
||||
postDesignSave: async () => {
|
||||
calls.push("post");
|
||||
if (fail) throw new Error("서버 500");
|
||||
return { waited: false, steps: { route: { route_id: newRouteId } } };
|
||||
},
|
||||
},
|
||||
"../../B05_Profile/B05_Profile_UI_RouteEdit_Apply": {
|
||||
pendingRouteSave: async () => ({ draft: 1 }),
|
||||
routeCarryPart: () => ({ carry: {}, cross: { body: {} } }),
|
||||
routeSaved: () => calls.push("routeSaved"),
|
||||
routeSaveSummary: () => "",
|
||||
rebuildAfterRouteSave: async () => calls.push("rebuild"),
|
||||
},
|
||||
});
|
||||
const done = await save.saveDesign({
|
||||
projectId: "p1",
|
||||
routeId: () => 3,
|
||||
crossPart: async () => null,
|
||||
failed: () => calls.push("failed"),
|
||||
});
|
||||
// 저장 뒤 정리와 무관한 칸(종류 캐시)은 빼고 봄
|
||||
return { done, calls: calls.filter((c) => !c.includes("structure-type-cache")) };
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const out = {
|
||||
moved: await once(9, false),
|
||||
same: await once(3, false),
|
||||
failed: await once(9, true),
|
||||
};
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
})().catch((error) => {
|
||||
process.stderr.write(String(error.stack || error));
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -28,10 +28,11 @@ def test_page_has_no_lamp_handler():
|
||||
|
||||
def test_save_path_and_merge_kept():
|
||||
# 저장 길(세션 → 저장 몸통)과 B06 이 부르는 종단 병합 함수는 그대로
|
||||
# 값 주인 = 세션 칸 `uphill` 하나 — 종단 보기 메모리 사본(uphillOverrides) 걷음(472-5)
|
||||
page = _read("A00_Common", "design", "design_view_profile.ts")
|
||||
assert "persistUphillOverrides" in page
|
||||
# 상단측 본문 = 기본 조각 한 벌(463 · 472-6 종단 보기 몫은 그 본문 그대로)
|
||||
assert "body: base.body," in _read("B05_Profile", "B05_Profile_UI_Page_Actions.ts")
|
||||
assert "uphillOverrides" not in page
|
||||
# 상단측 본문 = 기본 조각 한 벌(463 · 472-6) — 종단 보기 몫 없음(사본 걷음 · 472-5)
|
||||
assert "uphill:" not in _read("B05_Profile", "B05_Profile_UI_Page_Actions.ts")
|
||||
assert "body: await routePartsBody(projectId)," in _read(
|
||||
"A00_Common", "design", "design_save.ts"
|
||||
)
|
||||
|
||||
@@ -62,7 +62,8 @@ def test_종단_보기를_열든_안_열든_같은_요청(got):
|
||||
}
|
||||
# 상단측 세션 · 종단 보기 메모리 둘 다 비움(다음 [저장]이 옛 값을 다시 안 보냄)
|
||||
assert got["closed"]["uphillAfter"] is None and got["open"]["uphillAfter"] is None
|
||||
assert got["openMemoryAfter"] == 0
|
||||
# 종단 보기 몫에 상단측 사본(메모리 맵) 없음 — 기본 조각 그대로(472-5)
|
||||
assert got["openOwnUphill"] == "undefined"
|
||||
|
||||
|
||||
def test_노선_캐시_없어도_같은_본문(got):
|
||||
@@ -80,9 +81,9 @@ def test_서버는_그_본문이면_비정규_측점_횡단을_맞춤(got):
|
||||
def test_종단_보기_몫은_기본_창구_함수를_부름():
|
||||
actions = (ROOT / "B05_Profile" / "B05_Profile_UI_Page_Actions.ts").read_text("utf-8")
|
||||
save = (ROOT / "A00_Common" / "design" / "design_save.ts").read_text("utf-8")
|
||||
# 472-6 — 종단 보기 몫은 기본 조각 본문 그대로(메모리만 더 비움) · 노선 바뀐 저장도 한 본문(6-B ·
|
||||
# 옛 창구 `flushRouteParts` 걷음 · 노선 바뀌면 손 상단측 안 보냄 = 요구 274)
|
||||
assert "uphill: (base) =>" in actions and "body: base.body," in actions
|
||||
# 472-6 — 상단측 = 기본 조각 한 벌(종단 보기 몫 없음 · 메모리 사본 걷음 · 472-5) · 노선 바뀐 저장도 한
|
||||
# 본문(6-B · 옛 창구 `flushRouteParts` 걷음 · 노선 바뀌면 손 상단측 안 보냄 = 요구 274)
|
||||
assert "uphill:" not in actions
|
||||
assert "saveRouteParts(" not in actions and "flushRouteParts" not in actions
|
||||
assert "body: await routePartsBody(projectId)," in save
|
||||
assert "flushRouteParts" not in save and "if (moved) return null;" in save
|
||||
|
||||
@@ -81,8 +81,8 @@ def test_확정_하위_실패면_던짐(out):
|
||||
|
||||
def test_종단_보기_상단측_차례도_같은_창구():
|
||||
actions = (ROOT / "B05_Profile" / "B05_Profile_UI_Page_Actions.ts").read_text("utf-8")
|
||||
# 기본 창구와 같은 본문(463 · 472-6 기본 조각) — 상단측은 세션에서 읽음(메모리 옛 값 안 보냄)
|
||||
assert "body: base.body," in actions and "flushRouteParts" not in actions
|
||||
# 기본 조각 한 벌(463 · 472-6 · 종단 보기 몫 없음 · 472-5) — 상단측은 세션에서 읽음(메모리 사본 없음)
|
||||
assert "uphill:" not in actions and "flushRouteParts" not in actions
|
||||
fetch = (ROOT / "B05_Profile" / "B05_Profile_Api_Fetch.ts").read_text("utf-8")
|
||||
assert "uphill_overrides: uphillOverridesOf(projectId)," in fetch
|
||||
assert "confirmRoute(" not in actions
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
"""PLAN 472-5 남음 (요구 272 · 273) — 암 경계 · 암 절토 경사 값 주인 = 세션 칸 하나 · 메모리 맵 걷음(N5) ·
|
||||
노선 저장 뒤 옛 노선 칸만 비움.
|
||||
|
||||
못박는 것
|
||||
- 암 경계(▲▼ · ↺) · 암 경사를 고친 뒤 ↶ 하고 다른 측점을 고쳐도 되돌린 값이 되살아나지 않음 — 옛 코드는 메모리
|
||||
맵(`offsets` · `ratios`)이 옛 값을 쥐고 다음 조작이 칸을 통째로 다시 써 ↶ 를 덮었음(옛 코드 실패).
|
||||
- ↶ 뒤 카드 읽기 = 조작 전 값(저장분 그대로) · ↷ = 다시 그 값 · 저장 재료(`offsets()` · `ratios()`)도 세션 칸 그대로.
|
||||
- `clearRouteDrafts` = 그 노선 번호의 노선 범위 초안만 비움(다른 노선 · 다른 프로젝트 · 프로젝트 칸 그대로).
|
||||
- 노선 바뀐 [저장] 성공 뒤 옛 노선 칸 · 초안 전 hold 비움 · 실패면 그대로(`design_save.ts`).
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_472_5r_rock_owner.py -q
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not (ROOT / "config" / "node_modules" / "vite").is_dir(),
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
P = "p4725rock"
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def out(tmp_path_factory) -> dict:
|
||||
work = tmp_path_factory.mktemp("s4725r")
|
||||
subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
[
|
||||
"node",
|
||||
str(ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"),
|
||||
"build",
|
||||
"--configLoader",
|
||||
"runner",
|
||||
"--ssr",
|
||||
"../resources/tester/helper_472_5r_rock_entry.ts",
|
||||
"--outDir",
|
||||
str(work),
|
||||
"--logLevel",
|
||||
"error",
|
||||
],
|
||||
cwd=ROOT,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
timeout=300,
|
||||
)
|
||||
bundle = work / "bundle.mjs"
|
||||
shutil.copy(work / "helper_472_5r_rock_entry.js", bundle)
|
||||
proc = subprocess.run( # noqa: S603
|
||||
["node", str(HERE / "helper_472_5r_rock.mjs"), str(bundle)],
|
||||
cwd=ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=120,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def test_조작_A_는_세션_칸에(out) -> None:
|
||||
a = out["afterA"]
|
||||
assert a["rockb"] == {"100.00": -0.9, "120.00": -0.4}
|
||||
assert a["cutslope"] == {"140.00": 0.7}
|
||||
r = out["readsA"]
|
||||
assert r["100"]["rock"] == -0.9 and r["120"]["rock"] == -0.4 and r["140"]["cut"] == 0.7
|
||||
|
||||
|
||||
def test_되돌리면_칸이_비고_카드도_조작_전(out) -> None:
|
||||
assert out["afterUndo"] == {"rockb": {}, "cutslope": {}}
|
||||
r = out["readsUndo"]
|
||||
assert r["100"] == {"rock": -0.8, "cut": 0.4} # 저장분 그대로
|
||||
assert r["120"] == {"rock": -0.5, "cut": 0.4}
|
||||
assert r["140"] == {"rock": -0.5, "cut": 0.4}
|
||||
|
||||
|
||||
def test_되돌린_뒤_다른_조작이_되돌림을_안_덮음(out) -> None:
|
||||
"""N5 — 옛 코드는 메모리 맵이 A 값을 쥐고 B 를 쓸 때 통째로 다시 씀(옛 코드 실패)."""
|
||||
assert out["afterB"] == {"rockb": {"140.00": -0.6}, "cutslope": {"100.00": 0.5}}
|
||||
assert out["payloadB"] == {"rock": {"140.00": -0.6}, "cut": {"100.00": 0.5}}
|
||||
|
||||
|
||||
def test_다시하기_뒤_값_같음(out) -> None:
|
||||
assert out["afterUndo2"] == {"rockb": {}, "cutslope": {}}
|
||||
assert out["afterRedo"] == out["afterB"]
|
||||
r = out["readsRedo"]
|
||||
assert r["100"] == {"rock": -0.8, "cut": 0.5}
|
||||
assert r["140"] == {"rock": -0.6, "cut": 0.4}
|
||||
|
||||
|
||||
def test_되돌림_단추_기본값_표준(out) -> None:
|
||||
assert out["afterReset"] == {"rockb": {"140.00": -0.5}, "cutslope": {"100.00": 0}}
|
||||
r = out["readsReset"]
|
||||
assert r["140"]["rock"] == -0.5 and r["100"]["cut"] == 0.4
|
||||
|
||||
|
||||
def test_옛_노선_칸만_비움(out) -> None:
|
||||
before, after = set(out["keysBefore"]), set(out["keysAfter"])
|
||||
gone = before - after
|
||||
assert gone == {
|
||||
f"aislo:draft:rockb:{P}:7",
|
||||
f"aislo:draft:cutslope:{P}:7",
|
||||
f"aislo:draft:crossw:{P}:7",
|
||||
}
|
||||
assert f"aislo:draft:rockb:{P}:8" in after
|
||||
assert "aislo:draft:rockb:other:7" in after
|
||||
assert f"aislo:draft:uphill:{P}" in after
|
||||
|
||||
|
||||
def test_노선_저장_뒤_옛_노선_칸_비움(tmp_path) -> None:
|
||||
proc = subprocess.run( # noqa: S603
|
||||
["node", str(HERE / "helper_472_5r_route_save.cjs")],
|
||||
cwd=ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=120,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
got = json.loads(proc.stdout)
|
||||
# 새 노선 번호(9) ≠ 옛(3) — 다 저장하고 페이지를 다시 세운 뒤 옛 노선 칸 · hold 비움
|
||||
assert got["moved"]["done"] is True
|
||||
assert got["moved"]["calls"][-3:] == [
|
||||
"rebuild",
|
||||
"clearRouteDrafts:p1:3",
|
||||
"clearState:route-draft-hold:p1",
|
||||
]
|
||||
# 같은 번호면 노선 칸은 안 지움(새 노선 칸일 수 있음) · hold 만
|
||||
assert "clearRouteDrafts:p1:3" not in got["same"]["calls"]
|
||||
assert got["same"]["calls"][-1] == "clearState:route-draft-hold:p1"
|
||||
# 서버 실패 = 초안 그대로
|
||||
assert got["failed"]["done"] is False
|
||||
assert not [c for c in got["failed"]["calls"] if c.startswith("clear")]
|
||||
Reference in New Issue
Block a user