const assert = require("node:assert/strict"); const fs = require("node:fs"); const path = require("node:path"); const { test } = require("node:test"); const { loadTypeScript } = require("./load-typescript.cjs"); const root = path.resolve(__dirname, ".."); // 제거된 서버 alias가 실제 SN 호출 코드·URL 주석에 다시 들어오지 않도록 검증한다. const retiredSegments = [ "gis-upload", "ppl-analysis", "flood-dngr-map", "sbmrg-dngr-map", "rain-anls", "depth-map", "grid-map", "aged-status-by-ctpv", "swage-prcs-zones", "repair-histories", "savewithfile", "fcltppl", "fcltmhl", "fcltpump", "fcltstofcbr", ]; function sourceFiles(directory) { return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { const filename = path.join(directory, entry.name); return entry.isDirectory() ? sourceFiles(filename) : /\.(?:tsx?|jsx?)$/.test(entry.name) ? [filename] : []; }); } test("SN 실서비스와 business_components 호출은 단일 API 경로만 사용", () => { const violations = []; const sources = []; for (const filename of [ ...sourceFiles(path.join(root, "app")), ...sourceFiles(path.join(root, "src")), ...sourceFiles( path.resolve(root, "../../packages/common/business_components/src"), ), ]) { const source = fs.readFileSync(filename, "utf8"); sources.push(source); for (const segment of retiredSegments) { if (source.includes(`/${segment}`)) violations.push(`${path.relative(root, filename)}: ${segment}`); } } assert.deepEqual(violations, []); const allSources = sources.join("\n"); for (const segment of [ "pplAnalysis", "floodDngrMap", "sbmrgDngrMap", "rainAnls", "depthMap", "gridMap", "agedStatusByCtpv", "saveWithFile", "fcltPpl", "fcltMhl", "fcltPump", "fcltStofcbr", ]) { assert.ok( allSources.includes(`/${segment}`), `canonical API missing: ${segment}`, ); } }); test("침수 상세 API는 rainAnls/depthMap을 호출하고 기존 응답 필드를 보존", async () => { const calls = []; const client = loadTypeScript( "app/(map-layout)/map/components/fldngHstry/fldngHstryClient.ts", { "@/components/lib/api": { getData: async (url) => { calls.push(url); return url.includes("/rainAnls/") ? { rain_bgng_tm: "2026-09-11 01:00", rain_tot: 0 } : { depthMaps: [{ fid: "depth" }], waterLevelMaps: [{ fid: "level" }], }; }, }, }, ); const result = await client.loadFldngHstryDetail("F/1"); assert.deepEqual(calls, [ "v1/fldng/hstry/rainAnls/F%2F1", "v1/fldng/hstry/depthMap/F%2F1", ]); assert.equal(result.rainAnalysis.rain_bgng_tm, "2026-09-11 01:00"); assert.equal(result.rainAnalysis.rainBgngTm, "2026-09-11 01:00"); assert.equal(result.rainAnalysis.rain_tot, 0); assert.equal(result.rainAnalysis.rainTot, 0); assert.deepEqual(result.fldngHstryDepthRows, [{ fid: "depth" }]); assert.deepEqual(result.fldngHstryWtlvRows, [{ fid: "level" }]); assert.equal(result.complete, true); }); test("SN proxy는 gisUpload 단일 route이며 BE GET 목록·POST·하위 작업을 구분", () => { const screen = fs.readFileSync( path.join( root, "app/(map-layout)/map/components/gisManagement/GisManagementPopupPage.tsx", ), "utf8", ); const proxy = fs.readFileSync( path.join(root, "app/api/gisUpload/route.ts"), "utf8", ); assert.ok(screen.includes("v1/map/gisUploadHistories?")); assert.ok(screen.includes('"/api/gisUpload"')); assert.ok(!screen.includes('"/api/gis-upload"')); assert.equal(fs.existsSync(path.join(root, "app/api/gis-upload/route.ts")), false); assert.ok(!screen.includes("v1/map/gisUploads?")); assert.ok(screen.includes("v1/map/gisUploads/swagePpl/downloadJobs?")); assert.ok(proxy.includes('"/sewerage-service/api/v1/map/gisUploads"')); assert.ok(proxy.includes("export async function POST")); }); test("서비스 식별자·CMS 계약은 보존되고 fclt+suffix 동적 조합이 없다", () => { const api = fs.readFileSync( path.join(root, "app/components/lib/api/index.ts"), "utf8", ); const helpers = fs.readFileSync( path.join(root, "app/components/lib/common/apiHelpers.ts"), "utf8", ); assert.ok(api.includes("/sewerage-service/api")); assert.ok(api.includes("/cms-service")); assert.ok(api.includes("/geoserver")); assert.ok(helpers.includes("/api/v1/code/cache/")); assert.doesNotMatch(helpers, /fclt\$\{|["']fclt["']\s*\+/); });