Files
NODEDC_DESIGN_GUIDELINE/server/map-grid-persistence.test.mjs

167 lines
6.2 KiB
JavaScript

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("Robot2B legacy radius migrates per LOD without hiding the application", () => {
const legacyRobot2BSettings = {
gridHeightMeters: 500,
gridLod1MaxHeightKm: 10,
gridLod1StepKm: 1,
gridLod2MaxHeightKm: 50,
gridLod2StepKm: 5,
gridLod3StepKm: 25,
gridRadiusKm: 1_000,
gridLineWidth: 4,
gridColor: "#f5f5f5",
gridOpacity: 12,
gridDotsEnabled: true,
gridDotsSize: 7,
gridDotsColor: "#ffffff",
gridDotsOpacity: 58,
};
const migrated = structuredClone(serverGrid.validateGridLodProfiles(
serverGrid.promoteLegacyGridLodProfiles(legacyRobot2BSettings),
));
assert.deepEqual(migrated.map((profile) => profile.radiusKm), [50, 1_000, 1_000, 1_000, 100]);
assert.ok(migrated.every((profile) => profile.radiusKm / profile.stepKm <= 512));
assert.ok(migrated.every((profile) => profile.graticuleLineWidthPx === 3));
});
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}`),
);
}
});