Files
Aislo/resources/tester/helper_449_save_pipes.cjs
T
eomsangdonandClaude Opus 5.5 4b6800e387 feat(472-6 6-B 노선 바뀐 [저장] 한 본문): 노선 저장 · 관 · 구조물 다시 얹기 · 횡단 고침 넘김을 서버 한 창구로 — 전부 아니면 없음
- POST /design/save 에 route(노선 초안) · carry(옛 노선 세션 칸) 칸 · 서버 = 노선 저장 → Node facility.rebase(rebasePipeDraft · rebaseStructureDraft) → 6-A 차례(새 노선) → Node stations.carry(routeCarryPatches) → 횡단 저장
- 되돌림 범위 = 노선 행 · 자식 표 · 단계 · 배수 폴더 · 노선 파일 · 지표면 확정(설계 지형 바꿈)
- 손 상단측은 안 넘김(요구 274) · 노선 바뀐 [확정] = [저장](새 노선 본 뒤 다시)
- 브라우저 = 노선 초안 + 사용자 칸만 한 본문 · 옛 차례 길(saveRouteDraft 뒤 flush · carryAfterRouteSave · keepDraftsOnNewRoute) · 부름 0 된 flush 창구 여섯 · saveCachedCrossPatches 걷음
- 등록부 R9 stations.carry · F10 facility.rebase · 옛 차례 단언 시험 한 본문 꼴로 · test_472_6_route_save(옛 길 = 새 길 · 실패 되돌림)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:07:12 +09:00

205 lines
7.5 KiB
JavaScript

/* PLAN 449 헬퍼 — [저장] 관 차례 · 노선 저장 뒤 실패(433-7 · 433-12). 실제 `design_save.ts` · 관 초안 창구 · 가게를
* Node 로 · 서버 창구 · 계획고 초안 · 노선 편집 창구만 가짜. 짝 = `test_449_save_pipes.py`. */
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const CATALOG = require("./helper_322_catalog_module.cjs");
const ROOT = path.join(__dirname, "..", "..");
function memoryStorage() {
const map = new Map();
return {
getItem: (key) => (map.has(key) ? map.get(key) : null),
setItem: (key, value) => map.set(key, String(value)),
removeItem: (key) => map.delete(key),
key: (index) => [...map.keys()][index] ?? null,
get length() {
return map.size;
},
};
}
globalThis.window = {
sessionStorage: memoryStorage(),
localStorage: memoryStorage(),
setTimeout,
clearTimeout,
};
const STUBS = {};
const cache = new Map();
function loadTs(full) {
if (cache.has(full)) return cache.get(full);
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
cache.set(full, box.exports);
const localRequire = (request) => {
if (CATALOG.isCatalogRequest(request)) return CATALOG;
const key = Object.keys(STUBS).find((name) => request.endsWith(name));
if (key) return STUBS[key];
if (request.startsWith("@config/")) return { API_BASE_URL: "http://fake" };
if (request.startsWith("@util/"))
return loadTs(path.join(ROOT, "common_util", `${request.slice(6)}.ts`));
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
return require(request);
};
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
return box.exports;
}
const load = (...parts) => loadTs(path.join(ROOT, ...parts));
let calls = [];
let failAt = null; // 이 차례에서 throw
let routeBody = null;
const alignment = new Map(); // 노선 칸 → 계획고 초안
STUBS["ui_template_elements"] = {
showLoadingOverlay: () => undefined,
hideLoadingOverlay: () => undefined,
showToast: () => undefined,
};
const failing = (name) => {
if (failAt === name) throw new Error(`${name} 409`);
};
STUBS["B04_PreProcess_Api_Fetch"] = {
fetchDetailPipePoints: async () => (calls.push(["GET"]), { pipe_points: [] }),
saveDetailPipePoints: async (_projectId, points, recompute) => {
calls.push(["PUT", points ?? null, !!recompute]);
return {};
},
};
STUBS["B05_Profile_Api_Structures"] = {
flushPendingStructures: async () => failing("structures"),
structuresPart: async () => null,
readPendingStructures: () => null, // 구조물 초안 없음 — 노선 저장 뒤 얹기(459) 안 돎
};
STUBS["B05_Profile_Api_PlusStations"] = {
outsideNotice: () => "",
plusStationsPart: () => ({ body: null }),
};
STUBS["B05_Profile_Api_Fetch"] = {
flushUphillOverrides: async () => undefined,
flushRouteParts: async () => undefined, // 상단측 차례(463)
routePartsBody: async () => ({}),
saveProfileAlignment: async (_projectId, routeId, draft) => {
calls.push(["ALIGN", routeId, draft]);
return {};
},
};
STUBS["B05_Profile_UI_Profile_Edit"] = {
pendingAlignment: (routeId) => alignment.get(routeId) ?? null,
markAlignmentSaved: (routeId) => alignment.delete(routeId),
writeAlignmentDraft: (routeId, edits) => alignment.set(routeId, edits),
};
STUBS["B06_Section_Api_Culvert_Options"] = {
flushCulvertOptions: async () => undefined,
culvertOptionsPart: () => null,
};
// 472-6 한 본문 창구 — 관 칸 · 계획고 칸만 적음(옛 PUT 줄과 같은 꼴)
STUBS["design_save_api"] = {
SAVE_PART_KEYS: [
"settings",
"pipes",
"structures",
"plusStations",
"alignment",
"culvertOptions",
"uphill",
"cross",
],
DesignSaveError: class extends Error {
constructor(message, step, restored) {
super(message);
this.step = step;
this.restored = restored;
}
},
postDesignSave: async (_projectId, routeId, confirm, parts, extra = {}) => {
const pipes = parts.pipes?.body;
calls.push([
"SAVE",
pipes === undefined || pipes === null ? "none" : (pipes.points ?? null),
confirm,
...(extra.route ? ["ROUTE"] : []),
]);
const align = parts.alignment?.body;
if (align) {
const { route_id: _id, ...draft } = align;
calls.push(["ALIGN", routeId, draft]);
}
// 472-6 6-B — 서버가 그 차례에서 실패 = 노선까지 되돌림(전부 아니면 없음)
const Err = STUBS["design_save_api"].DesignSaveError;
if (failAt) throw new Err(`${failAt} 409`, failAt, true);
return { waited: false, steps: extra.route ? { route: { route_id: 8 } } : {} };
},
};
STUBS["B05_Profile_UI_RouteEdit_Apply"] = {
pendingRouteSave: async () => routeBody,
routeCarryPart: (_projectId, from) => ({ carry: { from }, cross: { body: {} } }),
routeSaved: () => void calls.push(["ROUTE_SAVED"]),
routeSaveSummary: () => "",
rebuildAfterRouteSave: async () => void calls.push(["REBUILD"]),
};
const save = load("A00_Common", "design", "design_save.ts");
const drafts = load("B05_Profile", "B05_Profile_Api_Pipes_Draft.ts");
const P = "proj-449";
async function once({ route = false, draft = null, confirm = false, fail = null, routeId = 7 }) {
calls = [];
failAt = fail;
routeBody = route ? { draft: {} } : null;
drafts.writePendingPipes(P, draft);
let failure = null;
const ok = await save.saveDesign(
{
projectId: P,
routeId: () => routeId,
// 화면 몫 = 배수판 다시 받기(PUT 없음) — 433-7 뒤 design_shared 와 같음
parts: {
pipes: (base) => ({
body: base?.body ?? null,
after: async (response) => {
await base?.after?.(response);
calls.push(["RELOAD"]);
},
}),
},
crossPart: async () => ({ body: {} }),
failed: (error) => (failure = String(error && error.message ? error.message : error)),
},
{ confirm },
);
return { ok, failure, calls, left: drafts.readPendingPipes(P) };
}
async function run() {
window.localStorage.setItem("frd_current_project_id", P);
const fifteen = [{ chainage_m: 15, source: "user", id: "p1" }];
const out = {};
out.draft = await once({ draft: fifteen }); // B06 고침 직후 [저장] = 초안 값
out.noDraft = await once({}); // 판 안 섬 · 초안 없음 = PUT 없음
out.confirmNoDraft = await once({ confirm: true }); // [확정] = 칸 없이(서버 저장분)
out.emptyDraft = await once({ draft: [] }); // 사용자가 다 지움 = 빈 목록
// 433-12 — 노선 저장 뒤 구조물 실패 → 0 칸 계획고가 새 노선 칸에 · 다음 [저장]이 보냄
alignment.clear();
alignment.set(0, { pvi: { 120: 1.5 } });
out.routeFail = await once({ route: true, fail: "structures" });
out.alignAfterFail = Object.fromEntries(alignment);
out.nextSave = await once({ route: true }); // 다음 [저장] = 같은 초안 그대로 다시
// 472-6 6-B — 횡단 넘김(서버 Node) 실패도 노선까지 되돌림 · 화면은 그대로
alignment.clear();
out.carryFail = await once({ route: true, fail: "cross" });
return out;
}
run()
.then((out) => {
process.stdout.write(JSON.stringify(out));
process.exit(0);
})
.catch((error) => {
process.stderr.write(String(error && error.stack ? error.stack : error));
process.exit(1);
});