NODEDC_DESIGN_GUIDELINE/scripts/smoke-platform-settings.mjs

184 lines
7.7 KiB
JavaScript

import assert from "node:assert/strict";
import { once } from "node:events";
import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises";
import { createServer as createHttpServer } from "node:http";
import { createServer } from "node:net";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { spawn } from "node:child_process";
const foundryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const gatewayRoot = resolve(foundryRoot, "../platform/services/map-gateway");
const root = await mkdtemp(join(tmpdir(), "nodedc-foundry-platform-settings-smoke-"));
const gatewayPort = await freePort();
const foundryPort = await freePort();
const providerPort = await freePort();
const internalSecret = "foundry-gateway-admin-smoke-secret";
const providerToken = "cesium-private-token-must-not-reach-browser";
const ionReferer = "https://foundry.example.test/page-library/";
const adminSecretFile = join(root, "map-gateway-admin-secret");
let gateway;
let foundry;
let provider;
try {
await writeFile(adminSecretFile, `${"a".repeat(64)}\n`, { mode: 0o640 });
await chmod(adminSecretFile, 0o640);
provider = createCesiumIonMock(providerToken);
provider.listen(providerPort, "127.0.0.1");
await once(provider, "listening");
gateway = spawn(process.execPath, ["src/server.mjs"], {
cwd: gatewayRoot,
env: {
...process.env,
PORT: String(gatewayPort),
MAP_CACHE_DIR: join(root, "gateway-cache"),
MAP_GATEWAY_ALLOW_ANONYMOUS: "true",
MAP_CACHE_MODE: "readwrite",
NODE_ENV: "test",
CESIUM_ION_TOKEN: "",
CESIUM_ION_API_BASE_URL: `http://127.0.0.1:${providerPort}/`,
NODEDC_MAP_GATEWAY_ADMIN_SECRET_FILE: adminSecretFile,
},
stdio: ["ignore", "pipe", "pipe"],
});
await waitForService(gatewayPort, gateway, "gateway");
foundry = spawn(process.execPath, ["server/catalog-server.mjs"], {
cwd: foundryRoot,
env: {
...process.env,
NODE_ENV: "development",
HOST: "127.0.0.1",
PORT: String(foundryPort),
FOUNDRY_RUNTIME_DIR: join(root, "foundry-runtime"),
NODEDC_FOUNDRY_AUTH_REQUIRED: "false",
NODEDC_MAP_GATEWAY_INTERNAL_URL: `http://127.0.0.1:${gatewayPort}`,
NODEDC_INTERNAL_ACCESS_TOKEN: internalSecret,
NODEDC_MAP_GATEWAY_ADMIN_SECRET_FILE: adminSecretFile,
FOUNDRY_PUBLIC_URL: "https://foundry.example.test",
},
stdio: ["ignore", "pipe", "pipe"],
});
await waitForService(foundryPort, foundry, "foundry");
const profile = await fetch(`http://127.0.0.1:${foundryPort}/api/session/profile`).then((response) => response.json());
assert.equal(profile.access.role, "admin");
const initial = await fetch(`http://127.0.0.1:${foundryPort}/api/platform-settings/cesium-ion`, { headers: { referer: ionReferer } });
assert.equal(initial.status, 200);
assert.equal((await initial.json()).configured, false);
const saved = await fetch(`http://127.0.0.1:${foundryPort}/api/platform-settings/cesium-ion`, {
method: "PUT",
headers: { "content-type": "application/json", referer: ionReferer },
body: JSON.stringify({ token: providerToken }),
});
const savedRaw = await saved.text();
assert.equal(saved.status, 200);
assert.equal(savedRaw.includes(providerToken), false);
assert.equal(JSON.parse(savedRaw).configured, true);
assert.equal(JSON.parse(savedRaw).verification, "verified");
const status = await fetch(`http://127.0.0.1:${foundryPort}/api/platform-settings/cesium-ion`, { headers: { referer: ionReferer } });
const statusRaw = await status.text();
assert.equal(status.status, 200);
assert.equal(statusRaw.includes(providerToken), false);
assert.equal(JSON.parse(statusRaw).configured, true);
assert.equal(JSON.parse(statusRaw).verification, "verified");
const runtime = await fetch(`http://127.0.0.1:${foundryPort}/api/map/runtime-config`);
assert.equal(runtime.status, 200);
const runtimeBody = await runtime.json();
assert.equal(runtimeBody.gatewayReady, true);
assert.equal(runtimeBody.assetEndpointBase, "/api/map-gateway/api/map/ion/assets");
const terrain = await fetch(`http://127.0.0.1:${foundryPort}/api/map-gateway/api/map/ion/assets/1/endpoint`, { headers: { referer: ionReferer } });
const terrainRaw = await terrain.text();
assert.equal(terrain.status, 200);
assert.equal(terrainRaw.includes(providerToken), false);
assert.equal(JSON.parse(terrainRaw).credentialMode, "gateway");
const buildings = await fetch(`http://127.0.0.1:${foundryPort}/api/map-gateway/api/map/ion/assets/96188/endpoint`, { headers: { referer: ionReferer } });
const buildingsRaw = await buildings.text();
assert.equal(buildings.status, 200);
assert.equal(buildingsRaw.includes(providerToken), false);
assert.equal(JSON.parse(buildingsRaw).credentialMode, "gateway");
const health = await fetch(`http://127.0.0.1:${foundryPort}/api/map-gateway/healthz`);
assert.equal(health.status, 200);
assert.equal((await health.json()).cache.persistent, true);
console.log("ok: Foundry admin settings use the private signed Gateway path without returning the token");
} finally {
await stop(foundry);
await stop(gateway);
if (provider?.listening) {
provider.close();
await once(provider, "close");
}
await rm(root, { recursive: true, force: true });
}
function createCesiumIonMock(expectedToken) {
return createHttpServer((request, response) => {
if (request.headers.authorization !== `Bearer ${expectedToken}`) {
response.writeHead(401, { "content-type": "application/json" });
response.end(JSON.stringify({ error: "unauthorized" }));
return;
}
if (request.headers.referer !== ionReferer) {
response.writeHead(401, { "content-type": "application/json" });
response.end(JSON.stringify({ error: "referer_required" }));
return;
}
const assetId = new URL(request.url || "/", "http://127.0.0.1").pathname.match(/^\/v1\/assets\/(\d+)\/endpoint$/)?.[1];
const body = assetId === "1"
? { type: "TERRAIN", url: "https://assets.ion.cesium.com/1/", accessToken: "terrain-private-endpoint-key" }
: assetId === "2"
? { type: "IMAGERY", externalType: "BING", options: { url: "https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial", key: "bing-private-endpoint-key", mapStyle: "Aerial" } }
: assetId === "96188"
? { type: "3DTILES", url: "https://assets.ion.cesium.com/96188/", accessToken: "buildings-private-endpoint-key" }
: null;
if (!body) {
response.writeHead(404, { "content-type": "application/json" });
response.end(JSON.stringify({ error: "not_found" }));
return;
}
response.writeHead(200, { "content-type": "application/json" });
response.end(JSON.stringify(body));
});
}
async function freePort() {
const server = createServer();
server.listen(0, "127.0.0.1");
await once(server, "listening");
const address = server.address();
assert(address && typeof address === "object");
const port = address.port;
server.close();
await once(server, "close");
return port;
}
async function waitForService(port, child, label) {
let output = "";
child.stderr.on("data", (chunk) => { output += String(chunk); });
for (let attempt = 0; attempt < 80; attempt += 1) {
if (child.exitCode !== null) throw new Error(`${label}_exited:${child.exitCode}:${output}`);
try {
const response = await fetch(`http://127.0.0.1:${port}/healthz`);
if (response.ok) return;
} catch { /* service is still starting */ }
await new Promise((resolve) => setTimeout(resolve, 50));
}
throw new Error(`${label}_start_timeout:${output}`);
}
async function stop(child) {
if (child && !child.killed) {
child.kill("SIGTERM");
await once(child, "exit").catch(() => undefined);
}
}