/* 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: () => ({}) }), lane: quiet, // 시설 줄 한 벌(462) 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); }, );