import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import test from "node:test"; import vm from "node:vm"; import { DEFAULT_GRID_LOD_PROFILES } from "../apps/catalog/src/mapGridPolicy.mjs"; const seed = JSON.parse(await readFile(new URL("../runtime-seed/page-layouts/map.json", import.meta.url), "utf8")); const serverSource = await readFile(new URL("./catalog-server.mjs", import.meta.url), "utf8"); const mcpSource = await readFile(new URL("./foundry-mcp.mjs", import.meta.url), "utf8"); const extensionKeys = [ "majorLinesEnabled", "majorLabelsEnabled", "majorLineWidthMultiplier", "volumeEnabled", "volumeMinimumHeightMeters", "volumeMaximumHeightMeters", "volumeBandHeightMeters", "selectionFillColor", "selectionFillOpacityPercent", "selectionOutlineColor", "selectionOutlineWidthPx", "selectionOutlineOpacityPercent", ]; function applicationError(code, statusCode = 400) { const error = new Error(code); error.statusCode = statusCode; return error; } const validators = { applicationError, isObject: (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value), requireNumber(value, minimum, maximum, code) { if (typeof value !== "number" || !Number.isFinite(value) || value < minimum || value > maximum) { throw applicationError(code); } return value; }, requireBoolean(value, code) { if (typeof value !== "boolean") throw applicationError(code); return value; }, requireHex(value, code) { const normalized = String(value || ""); if (!/^#[0-9a-f]{6}$/i.test(normalized)) throw applicationError(code); return normalized.toLowerCase(); }, }; function loadServerGridContract() { const start = serverSource.indexOf("const GRID_LOD_PROFILE_KEYS"); const end = serverSource.indexOf("function validateMapPageSettingsPatch"); assert.ok(start >= 0 && end > start, "server grid contract source boundaries"); const context = vm.createContext({ ...validators }); const source = `${serverSource.slice(start, end)}\n;globalThis.gridContract = { DEFAULT_GRID_LOD_PROFILES, promoteLegacyGridLodProfiles, validateGridLodProfiles };`; new vm.Script(source, { filename: "catalog-server.grid-contract.mjs" }).runInContext(context); return context.gridContract; } const serverGrid = loadServerGridContract(); test("seed, client defaults and server promotion keep the sector-v2 profile contract identical", () => { assert.deepEqual(seed.settings.gridLodProfiles, DEFAULT_GRID_LOD_PROFILES); const legacyProfiles = seed.settings.gridLodProfiles.map((profile) => { const legacy = { ...profile }; for (const key of extensionKeys) delete legacy[key]; return legacy; }); assert.deepEqual( structuredClone(serverGrid.validateGridLodProfiles(legacyProfiles)), DEFAULT_GRID_LOD_PROFILES, ); }); test("flat layouts migrate mode-aware extensions while explicit invalid hierarchy is rejected", () => { const flatSettings = { ...seed.settings, gridLodProfiles: undefined, gridLod1StepKm: 3, gridTileSizeKm: 10, gridLod2Mode: "graticule", }; const migrated = structuredClone(serverGrid.validateGridLodProfiles( serverGrid.promoteLegacyGridLodProfiles(flatSettings), )); assert.equal(migrated[0].majorLinesEnabled, false); assert.equal(migrated[0].majorLabelsEnabled, false); assert.equal(migrated[1].mode, "graticule"); assert.equal(migrated[1].volumeEnabled, false); const invalidExplicit = structuredClone(DEFAULT_GRID_LOD_PROFILES); invalidExplicit[0].stepKm = 3; invalidExplicit[0].tileSizeKm = 10; assert.throws( () => serverGrid.validateGridLodProfiles(invalidExplicit), /invalid_map_grid_lod_profile_1_tileSizeKm_step_ratio/, ); }); test("Foundry MCP exposes every persisted sector-v2 profile field", () => { for (const key of extensionKeys) assert.match(mcpSource, new RegExp(`\\b${key}: \\{`)); }); test("server persists selected-sector visuals and accepts a 1,000 m address band inside its range", () => { const profiles = structuredClone(DEFAULT_GRID_LOD_PROFILES); Object.assign(profiles[0], { volumeMaximumHeightMeters: 1_000, volumeBandHeightMeters: 1_000, selectionFillColor: "#A0B1C2", selectionFillOpacityPercent: 34, selectionOutlineColor: "#C3D4E5", selectionOutlineWidthPx: 7.5, selectionOutlineOpacityPercent: 81, }); const validated = structuredClone(serverGrid.validateGridLodProfiles(profiles)); assert.equal(validated[0].volumeBandHeightMeters, 1_000); assert.equal(validated[0].selectionFillColor, "#a0b1c2"); assert.equal(validated[0].selectionFillOpacityPercent, 34); assert.equal(validated[0].selectionOutlineColor, "#c3d4e5"); assert.equal(validated[0].selectionOutlineWidthPx, 7.5); assert.equal(validated[0].selectionOutlineOpacityPercent, 81); }); test("server rejects selected-sector visual values outside the canonical bounds", () => { for (const [field, value] of [ ["selectionFillColor", "cyan"], ["selectionFillOpacityPercent", 101], ["selectionOutlineColor", "#ffff"], ["selectionOutlineWidthPx", 12.01], ["selectionOutlineOpacityPercent", -1], ]) { const profiles = structuredClone(DEFAULT_GRID_LOD_PROFILES); profiles[0][field] = value; assert.throws( () => serverGrid.validateGridLodProfiles(profiles), new RegExp(`invalid_map_grid_lod_profile_1_${field}`), ); } });