/* 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 }, }); // 472-6 — 상단측 차례 = 한 본문 `uphill` 칸(본문은 세션 · 비우기는 서버가 다 저장한 뒤 조각 정리 · // 하위 차례 실패 = 서버가 그 차례에서 되돌림 · 옛 창구 `flushUphillOverrides` · `saveRouteParts` 걷음) state.set("uphill:p1", { "40.000": "right" }); out.exported = ["flushUphillOverrides", "saveRouteParts", "routePartsBody"].filter( (name) => typeof api[name] === "function", ); out.uphillAfterBody = state.get("uphill:p1") ?? null; out.bodies = bodies; return out; } run().then( (out) => process.stdout.write(JSON.stringify(out)), (error) => { process.stderr.write(String(error.stack ?? error)); process.exit(1); }, );