Files
Aislo/resources/tester/helper_443_save_stops.cjs
T
eomsangdonandClaude Opus 5.5 f2338bd5c8 fix(443 B05 저장): 구조물 저장 실패 = 초안 남기고 그 차례에서 멈춤 · 상단측 차례 성공 뒤 세션 비움 · 확정 하위 실패 알림
- 구조물(H6) — 409 · 그 밖 실패를 토스트로 삼키지 않고 던짐 · 저장 줄이 앞 실패에 막히지 않음
- 상단측(M3) — 차례 성공하면 uphill 세션 비움 · 종단 보기도 세션에서 읽음
- 확정 하위(M6) — 응답 failed_parts(비정규 측점 횡단 · 상단측 병합 · 종단 없음) · 화면이 던짐
- 시험 test_443_save_stops.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
2026-10-18 18:35:14 +09:00

111 lines
4.3 KiB
JavaScript

/* PLAN 443 단위검증 헬퍼 — 저장 누락 ① (H6 · M3 · M6).
* H6 구조물 저장 실패(409 · 그 밖) = 던짐 · 초안 남음 · 다음 [저장]이 줄에 안 막힘
* M3 상단측 차례 성공 = uphill 세션 비움 · 실패 = 그대로
* M6 확정 응답 failed_parts = 던짐(그 차례에서 멈춤)
* TS 를 그 자리에서 트랜스파일해 Node 로 돌림 · 서버 창구는 가짜. 짝 = `test_443_save_stops.py`. */
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;
}
const settle = (promise) =>
promise.then(
() => "ok",
(error) => String(error.message),
);
async function run() {
const out = {};
// ── H6 구조물 ──
class StructureConflictError extends Error {}
let pending = [{ structure_id: "s1" }];
let mode = "conflict";
const puts = [];
const bridgeMod = load(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Page_Structures.ts"), {
"./B05_Profile_Api_Structures": {
StructureConflictError,
fetchStructures: async () => ({ revision: 3, structures: [] }),
saveStructures: async (_p, revision, list) => {
puts.push(revision);
if (mode === "conflict") throw new StructureConflictError("409");
if (mode === "boom") throw new Error("서버 500");
return { revision: revision + 1 };
},
readPendingStructures: () => pending,
writePendingStructures: (_p, next) => (pending = next),
definitionStale: () => false,
staleDefinitionNotice: () => null,
},
"@ui/ui_template_elements": { showToast: () => undefined },
});
const quiet = new Proxy({}, { get: () => () => undefined });
const bridge = bridgeMod.createStructuresBridge({
projectId: "p1",
panel: () => ({ structures: quiet, values: () => ({}) }),
profilePanel: () => quiet,
renderStationLines: () => undefined,
isRestoring: () => false,
});
out.conflict = await settle(bridge.saveStructuresIfDirty());
out.pendingAfterConflict = pending;
mode = "boom";
out.boom = await settle(bridge.saveStructuresIfDirty()); // 앞 실패에 줄이 안 막힘
out.pendingAfterBoom = pending;
mode = "ok";
out.ok = await settle(bridge.saveStructuresIfDirty());
out.pendingAfterOk = pending;
out.puts = puts;
// ── M3 · M6 상단측 · 확정 하위 ──
const state = new Map();
let reply = {};
const bodies = [];
globalThis.fetch = async (_url, init) => {
bodies.push(JSON.parse(init.body));
return { ok: true, json: async () => reply };
};
const api = load(path.join(ROOT, "B05_Profile", "B05_Profile_Api_Fetch.ts"), {
"../A00_Common/b_page_state": {
readState: (name, p) => state.get(`${name}:${p}`) ?? null,
writeState: (name, value, p) => state.set(`${name}:${p}`, value),
clearState: (name, p) => state.delete(`${name}:${p}`),
},
"@config/config_frontend": { API_BASE_URL: "", API_TIMEOUT_MS: 1000 },
});
state.set("uphill:p1", { "40.000": "right" });
reply = { failed_parts: ["상단측 병합"] };
out.flushFailed = await settle(api.flushUphillOverrides("p1"));
out.uphillAfterFail = state.get("uphill:p1") ?? null;
reply = { failed_parts: [] };
out.flushOk = await settle(api.flushUphillOverrides("p1"));
out.uphillAfterOk = state.get("uphill:p1") ?? null;
out.sentAgain = await settle(api.flushUphillOverrides("p1")); // 비었으니 안 보냄
out.bodies = bodies;
reply = { failed_parts: ["비정규 측점 횡단"] };
out.partsFailed = await settle(api.saveRouteParts("p1", { irregular_stations: [] }));
return out;
}
run().then(
(out) => process.stdout.write(JSON.stringify(out)),
(error) => {
process.stderr.write(String(error.stack ?? error));
process.exit(1);
},
);