mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
Merge remote-tracking branch 'origin/main' into codex/overpass-admission-review-fixes
# Conflicts: # README.md # SECURITY.md # scripts/format-scope.json # server/providers/overpass.js
This commit is contained in:
commit
0ac32f82ba
545 files changed
+29995
-2293
No files matched your search
@@ -98,7 +98,7 @@ test('standalone composition mounts every extracted provider exactly once withou
|
||||
assert.equal(plugins.filter((p) => p.name === factory().name).length, 1);
|
||||
});
|
||||
|
||||
test('terrain middleware chunks missing points and reconstructs repeated/reordered requests from cache', async (t) => {
|
||||
test('terrain middleware bounds missing points and reconstructs repeated/reordered requests from cache', async (t) => {
|
||||
isolate(t);
|
||||
let calls = 0;
|
||||
const sizes = [];
|
||||
@@ -116,25 +116,23 @@ test('terrain middleware chunks missing points and reconstructs repeated/reorder
|
||||
});
|
||||
});
|
||||
const request = install(terrainHeightsProxy());
|
||||
const points = Array.from({ length: 257 }, (_, i) => `${i / 100},1`);
|
||||
const points = Array.from({ length: 64 }, (_, i) => `${i / 100},1`);
|
||||
const res = await request('/?points=' + points.join(';'));
|
||||
assert.equal(res.status, 200);
|
||||
// Tracks UPSTREAM_CHUNK in server/providers/terrain.js (64): 257 points
|
||||
// split into four full chunks and a remainder.
|
||||
assert.deepEqual(sizes, [64, 64, 64, 64, 1]);
|
||||
const reordered = await request('/?points=2.56,1;0,1;2.56,1');
|
||||
assert.deepEqual(sizes, [64]);
|
||||
const reordered = await request('/?points=0.63,1;0,1;0.63,1');
|
||||
assert.deepEqual(json(reordered), {
|
||||
results: [{ ellipsoid: 102.56 }, { ellipsoid: 100 }, { ellipsoid: 102.56 }],
|
||||
results: [{ ellipsoid: 100.63 }, { ellipsoid: 100 }, { ellipsoid: 100.63 }],
|
||||
});
|
||||
// Five chunks for the first batch; the reordered request is fully cached.
|
||||
assert.equal(calls, 5);
|
||||
// One bounded batch; the reordered request is fully cached.
|
||||
assert.equal(calls, 1);
|
||||
assert.equal((await request('/?points=invalid')).status, 400);
|
||||
assert.equal(
|
||||
(await request('/?points=' + Array(2001).fill('0,1').join(';'))).status,
|
||||
500,
|
||||
413,
|
||||
);
|
||||
// Rejected requests add no upstream calls: still the five from the first batch.
|
||||
assert.equal(calls, 5);
|
||||
// Rejected requests add no upstream calls.
|
||||
assert.equal(calls, 1);
|
||||
});
|
||||
|
||||
test('terrain middleware migrates valid legacy disk points without fabricating omitted heights', async (t) => {
|
||||
@@ -160,6 +158,7 @@ test('terrain middleware migrates valid legacy disk points without fabricating o
|
||||
test('traffic middleware preserves keyless mode, caching, stale budget fallback and UTC rollover', async (t) => {
|
||||
isolate(t, { TOMTOM_API_KEY: '', TOMTOM_DAILY_TILE_BUDGET: '1' });
|
||||
let now = Date.UTC(2026, 8, 12, 12);
|
||||
const fetchedAt = String(now);
|
||||
t.mock.method(Date, 'now', () => now);
|
||||
let calls = 0;
|
||||
t.mock.method(globalThis, 'fetch', async (raw) => {
|
||||
@@ -184,10 +183,10 @@ test('traffic middleware preserves keyless mode, caching, stale budget fallback
|
||||
assert.equal(json(await request('/status')).dailyCount, 1);
|
||||
assert.equal(calls, 1);
|
||||
now += 120001;
|
||||
assert.equal(
|
||||
(await request('/flow/8/1/1.pbf')).headers['x-tomtom-cache'],
|
||||
'STALE-BUDGET',
|
||||
);
|
||||
const staleTile = await request('/flow/8/1/1.pbf');
|
||||
assert.equal(staleTile.headers['x-tomtom-cache'], 'STALE-BUDGET');
|
||||
// The stale copy still says when TomTom produced it.
|
||||
assert.equal(staleTile.headers['x-tomtom-fetched-at'], fetchedAt);
|
||||
assert.equal((await request('/flow/8/2/1.pbf')).status, 429);
|
||||
assert.equal(calls, 1);
|
||||
now += 86400000;
|
||||
@@ -388,21 +387,26 @@ test('terrain middleware retains successful chunks around a failure and retries
|
||||
});
|
||||
const request = install(terrainHeightsProxy());
|
||||
const points = Array.from({ length: 130 }, (_, i) => `${i / 100},1`);
|
||||
const url = '/?points=' + points.join(';');
|
||||
assert.equal((await request(url)).status, 502);
|
||||
const urls = [0, 64, 128].map(
|
||||
(start) => '/?points=' + points.slice(start, start + 64).join(';'),
|
||||
);
|
||||
assert.deepEqual(
|
||||
requests.map((p) => p.length),
|
||||
[64, 64, 2],
|
||||
await Promise.all(urls.map(async (url) => (await request(url)).status)),
|
||||
[200, 502, 200],
|
||||
);
|
||||
assert.deepEqual(
|
||||
requests.map((p) => p.length).sort((a, b) => a - b),
|
||||
[2, 64, 64],
|
||||
);
|
||||
fail = false;
|
||||
const recovered = await request(url);
|
||||
assert.equal(recovered.status, 200);
|
||||
const recovered = await Promise.all(urls.map((url) => request(url)));
|
||||
assert.ok(recovered.every((response) => response.status === 200));
|
||||
assert.deepEqual(
|
||||
requests[3],
|
||||
Array.from({ length: 64 }, (_, i) => [(i + 64) / 100, 1]),
|
||||
);
|
||||
assert.deepEqual(
|
||||
json(recovered).results,
|
||||
recovered.flatMap((response) => json(response).results),
|
||||
Array.from({ length: 130 }, (_, i) => ({ ellipsoid: i / 100 + 100 })),
|
||||
);
|
||||
assert.equal(requests.length, 4);
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createServer } from 'node:http';
|
||||
import { once } from 'node:events';
|
||||
import { createGoogleTokenSource } from '../maps/googleTokens.js';
|
||||
import { googleTokenUnavailablePlugin } from '../../server/standalone/google-token-unavailable.js';
|
||||
|
||||
async function withServer({ beforeFallback } = {}, check) {
|
||||
const middlewares = [];
|
||||
const app = {
|
||||
middlewares: {
|
||||
use: (handler) => middlewares.push(handler),
|
||||
},
|
||||
};
|
||||
if (beforeFallback) app.middlewares.use(beforeFallback);
|
||||
googleTokenUnavailablePlugin().configureServer(app);
|
||||
app.middlewares.use((_req, res) => {
|
||||
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Unknown API route' }));
|
||||
});
|
||||
|
||||
const server = createServer((req, res) => {
|
||||
let index = 0;
|
||||
const next = () => middlewares[index++]?.(req, res, next);
|
||||
next();
|
||||
});
|
||||
server.listen(0, '127.0.0.1');
|
||||
await once(server, 'listening');
|
||||
const address = server.address();
|
||||
const url = 'http://127.0.0.1:' + address.port;
|
||||
try {
|
||||
await check(url);
|
||||
} finally {
|
||||
server.close();
|
||||
await once(server, 'close');
|
||||
}
|
||||
}
|
||||
|
||||
test('keyless GET and HEAD return uncached 204 instead of 404', async () => {
|
||||
await withServer({}, async (url) => {
|
||||
for (const method of ['GET', 'HEAD']) {
|
||||
const response = await fetch(url + '/api/google/tiles-token?probe=1', {
|
||||
method,
|
||||
});
|
||||
assert.equal(response.status, 204);
|
||||
assert.equal(response.headers.get('cache-control'), 'no-store');
|
||||
assert.equal(await response.text(), '');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('the existing client treats unavailable tokens as null', async () => {
|
||||
await withServer({}, async (url) => {
|
||||
const source = createGoogleTokenSource({
|
||||
fetchImpl: (path, options) => fetch(new URL(path, url), options),
|
||||
});
|
||||
assert.equal(await source.token(), null);
|
||||
});
|
||||
});
|
||||
|
||||
test('unrelated API paths and unsupported methods remain 404', async () => {
|
||||
await withServer({}, async (url) => {
|
||||
for (const [path, method] of [
|
||||
['/api/google/tiles-token/extra', 'GET'],
|
||||
['/api/not-a-token', 'GET'],
|
||||
['/api/google/tiles-token', 'POST'],
|
||||
]) {
|
||||
const response = await fetch(new URL(path, url), { method });
|
||||
assert.equal(response.status, 404);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('a real token provider registered first still wins', async () => {
|
||||
await withServer(
|
||||
{
|
||||
beforeFallback: (req, res, next) => {
|
||||
if (req.url !== '/api/google/tiles-token') return next();
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
accessToken: 'server-token',
|
||||
expiresAt: 10000000000000,
|
||||
}),
|
||||
);
|
||||
},
|
||||
},
|
||||
async (url) => {
|
||||
const response = await fetch(url + '/api/google/tiles-token');
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal((await response.json()).accessToken, 'server-token');
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -156,13 +156,18 @@ test('self package imports cannot evade portable ownership', (t) => {
|
||||
assert.throws(() => checkImportDirections(root), /Source imports rendering/);
|
||||
});
|
||||
|
||||
test('compatibility exceptions are limited to the two existing composition entries', (t) => {
|
||||
test('the compatibility exception is limited to the existing composition entry', (t) => {
|
||||
const { root, write } = fixture(t);
|
||||
write('src/ui.js', "import './standalone/catalog.js';");
|
||||
write('src/standalone/catalog.js');
|
||||
write('server/providers/local.js', "import '../standalone/key-setup.js';");
|
||||
write('server/standalone/key-setup.js');
|
||||
assert.doesNotThrow(() => checkImportDirections(root));
|
||||
write('src/ui.js', "import './standalone/catalog.js';");
|
||||
assert.throws(
|
||||
() => checkImportDirections(root),
|
||||
/Reusable module imports standalone/,
|
||||
);
|
||||
write('src/ui.js');
|
||||
write('src/ui/new.js', "import '../standalone/catalog.js';");
|
||||
assert.throws(
|
||||
() => checkImportDirections(root),
|
||||
|
||||
@@ -583,3 +583,205 @@ test('track backfill proxy returns 502 on an oversized upstream body and caches
|
||||
assert.equal(res2.statusCode, 502);
|
||||
assert.equal(callCount, 1);
|
||||
});
|
||||
|
||||
test('OpenSky Basic mode no longer sends username and password: it asks anonymously and warns once', async (t) => {
|
||||
environment(t, {
|
||||
OPENSKY_CLIENT_ID: undefined,
|
||||
OPENSKY_CLIENT_SECRET: undefined,
|
||||
OPENSKY_CREDENTIALS_FILE: undefined,
|
||||
OPENSKY_AUTH_MODE: 'basic',
|
||||
OPENSKY_USERNAME: 'fixture-user',
|
||||
OPENSKY_PASSWORD: 'fixture-password',
|
||||
});
|
||||
const warnings = [];
|
||||
t.mock.method(console, 'warn', (message) => warnings.push(message));
|
||||
const authorizations = [];
|
||||
t.mock.method(globalThis, 'fetch', async (url, options) => {
|
||||
if (url.includes('/states/')) {
|
||||
authorizations.push(options.headers.Authorization);
|
||||
return Response.json({ time: Math.floor(Date.now() / 1000), states: [] });
|
||||
}
|
||||
throw Error(`Unexpected URL: ${url}`);
|
||||
});
|
||||
const fresh = await import(
|
||||
`../../server/providers/aircraft/opensky.js?basic=${Math.random()}`
|
||||
);
|
||||
const flights = install(fresh.openSkyProxy());
|
||||
const first = await flights('/api/flights');
|
||||
assert.equal(first.statusCode, 200);
|
||||
assert.equal(first.headers['x-opensky-auth'], 'anon');
|
||||
assert.equal(first.headers['x-opensky-auth-reason'], 'basic_retired');
|
||||
assert.deepEqual(authorizations, [undefined]);
|
||||
assert.equal(warnings.length, 1);
|
||||
assert.match(warnings[0], /retired username\/password sign-in/);
|
||||
assert.ok(!warnings[0].includes('fixture-password'));
|
||||
});
|
||||
|
||||
test('OpenSky auto mode never falls back to Basic, even when OAuth is rejected', async (t) => {
|
||||
environment(t, {
|
||||
OPENSKY_CLIENT_ID: 'fixture-client',
|
||||
OPENSKY_CLIENT_SECRET: 'fixture-secret',
|
||||
OPENSKY_CREDENTIALS_FILE: undefined,
|
||||
OPENSKY_AUTH_MODE: 'auto',
|
||||
OPENSKY_USERNAME: 'fixture-user',
|
||||
OPENSKY_PASSWORD: 'fixture-password',
|
||||
});
|
||||
t.mock.method(console, 'warn', () => {});
|
||||
t.mock.method(console, 'log', () => {});
|
||||
t.mock.method(console, 'error', () => {});
|
||||
const authorizations = [];
|
||||
t.mock.method(globalThis, 'fetch', async (url, options) => {
|
||||
if (url.includes('/token'))
|
||||
return Response.json({ access_token: 'fixture-token', expires_in: 1800 });
|
||||
if (url.includes('/states/')) {
|
||||
authorizations.push(options.headers.Authorization);
|
||||
return new Response('', { status: 401 });
|
||||
}
|
||||
if (url.includes('/lat/')) return regionalFallback();
|
||||
throw Error(`Unexpected URL: ${url}`);
|
||||
});
|
||||
const fresh = await import(
|
||||
`../../server/providers/aircraft/opensky.js?auto=${Math.random()}`
|
||||
);
|
||||
await install(fresh.openSkyProxy())('/api/flights', '?lat=30&lon=-97');
|
||||
assert.deepEqual(authorizations, ['Bearer fixture-token']);
|
||||
});
|
||||
|
||||
test('retired military history refuses without upstream I/O while validating identifiers', async (t) => {
|
||||
t.mock.method(globalThis, 'fetch', async () => {
|
||||
assert.fail('retired history must not contact the upstream');
|
||||
});
|
||||
const route = install(providers.trackBackfillProxies());
|
||||
for (const hex of ['abc123', 'abc456']) {
|
||||
const response = await route('/api/military/track', '?hex=' + hex);
|
||||
assert.equal(response.statusCode, 503);
|
||||
assert.equal(JSON.parse(response.body).code, 'history_unavailable');
|
||||
assert.equal(response.headers['retry-after'], '1800');
|
||||
assert.equal(response.headers['cache-control'], 'no-store');
|
||||
}
|
||||
assert.equal(
|
||||
(await route('/api/military/track', '?hex=invalid')).statusCode,
|
||||
400,
|
||||
);
|
||||
});
|
||||
|
||||
test('global aircraft snapshots coalesce cold and expired callers and clear rejected flights', async (t) => {
|
||||
environment(t, { OPENSKY_AUTH_MODE: 'anon' });
|
||||
t.mock.method(console, 'warn', () => {});
|
||||
t.mock.method(console, 'error', () => {});
|
||||
let now = Date.now();
|
||||
t.mock.method(Date, 'now', () => now);
|
||||
for (const kind of ['opensky', 'military']) {
|
||||
const module =
|
||||
kind === 'opensky'
|
||||
? await import(
|
||||
`../../server/providers/aircraft/opensky.js?coalesce=${Math.random()}`
|
||||
)
|
||||
: await import('../../server/providers/aircraft/adsb-lol.js');
|
||||
const request = install(
|
||||
kind === 'opensky' ? module.openSkyProxy() : module.adsbLolProxy(),
|
||||
);
|
||||
const route = kind === 'opensky' ? '/api/flights' : '/api/military';
|
||||
let calls = 0;
|
||||
let release;
|
||||
let blocked;
|
||||
let fail = false;
|
||||
t.mock.method(globalThis, 'fetch', async () => {
|
||||
calls++;
|
||||
await blocked;
|
||||
if (fail) throw new Error('fixture connection failed');
|
||||
return Response.json(
|
||||
{ time: now / 1000, states: [], ac: [] },
|
||||
{ headers: { 'x-rate-limit-remaining': '5000' } },
|
||||
);
|
||||
});
|
||||
for (const phase of ['cold', 'expired', 'failed', 'recovered']) {
|
||||
now += 13000;
|
||||
fail = phase === 'failed';
|
||||
blocked = new Promise((resolve) => {
|
||||
release = resolve;
|
||||
});
|
||||
const before = calls;
|
||||
const pending = Array.from({ length: 5 }, () => request(route));
|
||||
await new Promise(setImmediate);
|
||||
assert.equal(calls - before, 1, `${kind} ${phase}: one refresh starts`);
|
||||
release();
|
||||
const responses = await Promise.all(pending);
|
||||
assert.equal(calls - before, fail && kind === 'opensky' ? 2 : 1);
|
||||
assert.ok(responses.every((res) => res.statusCode === 200));
|
||||
assert.ok(responses.every((res) => res.body === responses[0].body));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('OpenSky 5xx retains dated last-good data, cancels error bodies and cools down before recovery', async (t) => {
|
||||
environment(t, { OPENSKY_AUTH_MODE: 'anon' });
|
||||
let now = Date.now();
|
||||
t.mock.method(Date, 'now', () => now);
|
||||
for (const status of [502, 503, 504]) {
|
||||
const module = await import(
|
||||
`../../server/providers/aircraft/opensky.js?server-error=${status}`
|
||||
);
|
||||
const request = install(module.openSkyProxy());
|
||||
let fail = false;
|
||||
let calls = 0;
|
||||
let cancelled = false;
|
||||
t.mock.method(globalThis, 'fetch', async () => {
|
||||
calls++;
|
||||
if (fail)
|
||||
return new Response(
|
||||
new ReadableStream({
|
||||
cancel() {
|
||||
cancelled = true;
|
||||
},
|
||||
}),
|
||||
{ status },
|
||||
);
|
||||
return Response.json(
|
||||
{ time: now / 1000, states: [] },
|
||||
{ headers: { 'x-rate-limit-remaining': '5000' } },
|
||||
);
|
||||
});
|
||||
const good = await request('/api/flights');
|
||||
now += 10000;
|
||||
fail = true;
|
||||
const stale = await request('/api/flights');
|
||||
assert.equal(stale.statusCode, 200);
|
||||
assert.equal(stale.body, good.body);
|
||||
assert.equal(stale.headers['x-opensky-cache'], 'STALE');
|
||||
assert.equal(
|
||||
stale.headers['x-opensky-auth-reason'],
|
||||
`upstream_http_${status}_serving_stale`,
|
||||
);
|
||||
assert.equal(stale.headers['x-opensky-stale-seconds'], '10');
|
||||
assert.equal(cancelled, true);
|
||||
const cooling = await request('/api/flights');
|
||||
assert.equal(cooling.body, good.body);
|
||||
assert.equal(
|
||||
cooling.headers['x-opensky-auth-reason'],
|
||||
`upstream_http_${status}_serving_stale`,
|
||||
);
|
||||
assert.equal(calls, 2);
|
||||
now += 16000;
|
||||
fail = false;
|
||||
assert.equal(
|
||||
(await request('/api/flights')).headers['x-opensky-cache'],
|
||||
'MISS',
|
||||
);
|
||||
assert.equal(calls, 3);
|
||||
}
|
||||
const coldModule =
|
||||
await import('../../server/providers/aircraft/opensky.js?cold-server-error');
|
||||
let calls = 0;
|
||||
t.mock.method(globalThis, 'fetch', async () => {
|
||||
calls++;
|
||||
return new Response('<html>failure</html>', { status: 503 });
|
||||
});
|
||||
const cold = install(coldModule.openSkyProxy());
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const response = await cold('/api/flights');
|
||||
assert.equal(response.statusCode, 503);
|
||||
assert.match(JSON.parse(response.body).error, /OpenSky/);
|
||||
}
|
||||
assert.equal(calls, 1);
|
||||
});
|
||||
@@ -81,6 +81,24 @@ test('nearby labels rank landmarks, deduplicate names/addresses and bound the pr
|
||||
assert.deepEqual(projectNearbyPlaces({}, 0, 0), []);
|
||||
});
|
||||
|
||||
test('nearby place distances take the short way round 180°', () => {
|
||||
const at = (name, longitude) => ({
|
||||
id: name,
|
||||
displayName: { text: name },
|
||||
location: { latitude: -16.8, longitude },
|
||||
types: ['point_of_interest'],
|
||||
});
|
||||
// From Taveuni at 179.98: 2 km east across 180°, 4 km west on this side.
|
||||
const result = projectNearbyPlaces(
|
||||
{ places: [at('West', 179.942), at('East', -179.999)] },
|
||||
-16.8,
|
||||
179.98,
|
||||
);
|
||||
const byName = Object.fromEntries(result.map((p) => [p.name, p.distanceM]));
|
||||
assert.ok(byName.East > 1500 && byName.East < 2500, String(byName.East));
|
||||
assert.ok(byName.West > 3500 && byName.West < 4500, String(byName.West));
|
||||
});
|
||||
|
||||
test('text search preserves bounds, rejects malformed bounds and tolerates absent locations', () => {
|
||||
const viewport = {
|
||||
low: { latitude: 1, longitude: 2 },
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import sharp from 'sharp';
|
||||
import {
|
||||
CUBE_FACES,
|
||||
FACE_FILE_SUFFIX,
|
||||
PLANETS,
|
||||
cubeFaceDirection,
|
||||
icrfToGalactic,
|
||||
} from '../../scripts/build-milky-way.mjs';
|
||||
import { raDecToUnitVector } from '../sky/stars.js';
|
||||
|
||||
const SKY = new URL('../data/local_data/sky/', import.meta.url);
|
||||
// Allowed excess of the painted disc over the surrounding sky, in 8-bit
|
||||
// levels of the encoded face: the blur and dither move values by a few.
|
||||
const TOLERANCE = 4;
|
||||
|
||||
test('painted-out planets leave no disc brighter than the sky around them', async () => {
|
||||
const centres = PLANETS.map((planet) => ({
|
||||
planet,
|
||||
vector: raDecToUnitVector(planet.l, planet.b),
|
||||
disc: [],
|
||||
ring: [],
|
||||
}));
|
||||
for (const face of CUBE_FACES) {
|
||||
const { data, info } = await sharp(
|
||||
new URL(`milkyway_${FACE_FILE_SUFFIX[face]}.webp`, SKY).pathname,
|
||||
)
|
||||
.raw()
|
||||
.toBuffer({ resolveWithObject: true });
|
||||
const size = info.width;
|
||||
for (let j = 0; j < size; j++) {
|
||||
for (let i = 0; i < size; i++) {
|
||||
const d = cubeFaceDirection(face, (i + 0.5) / size, (j + 0.5) / size);
|
||||
const n = Math.hypot(...d);
|
||||
const g = icrfToGalactic(d.map((x) => x / n));
|
||||
for (const centre of centres) {
|
||||
const cos =
|
||||
g[0] * centre.vector[0] +
|
||||
g[1] * centre.vector[1] +
|
||||
g[2] * centre.vector[2];
|
||||
const degrees = (Math.acos(Math.min(1, cos)) * 180) / Math.PI;
|
||||
const k = (j * size + i) * info.channels;
|
||||
const luma = (data[k] + data[k + 1] + data[k + 2]) / 3;
|
||||
if (degrees <= centre.planet.radiusDeg - 0.5) centre.disc.push(luma);
|
||||
else if (
|
||||
degrees >= centre.planet.radiusDeg + 1 &&
|
||||
degrees <= centre.planet.radiusDeg + 3.5
|
||||
)
|
||||
centre.ring.push(luma);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const median = (values) => values.sort((a, b) => a - b)[values.length >> 1];
|
||||
for (const { planet, disc, ring } of centres) {
|
||||
const discMean = disc.reduce((s, v) => s + v, 0) / disc.length;
|
||||
const ringMedian = median(ring);
|
||||
assert.ok(
|
||||
discMean <= ringMedian + TOLERANCE,
|
||||
`l=${planet.l} b=${planet.b}: disc mean ${discMean.toFixed(1)} vs surrounding sky ${ringMedian.toFixed(1)}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
buildStarPack,
|
||||
parseBscRecord,
|
||||
} from '../../scripts/build-bright-stars.mjs';
|
||||
import {
|
||||
CUBE_FACES,
|
||||
cubeFaceDirection,
|
||||
directionToPlateCarree,
|
||||
icrfToGalactic,
|
||||
smoothstep,
|
||||
softBlack,
|
||||
} from '../../scripts/build-milky-way.mjs';
|
||||
import { raDecToUnitVector } from '../sky/stars.js';
|
||||
|
||||
// Verbatim CDS V/50 records: HR 1, Sirius (HR 2491) and a nova with no J2000
|
||||
// position. Columns matter, so the padding is part of the fixture.
|
||||
const HR_1 =
|
||||
' 1 BD+44 4550 3 36042 46 000001.1+444022000509.9+451345114.44-16.88 6.70 +0.07 +0.08 A1Vn -0.012-0.018 -018 195 4.2 21.6AC 3';
|
||||
const SIRIUS =
|
||||
'2491 9Alp CMaBD-16 1591 48915151881 257I 5423 064044.6-163444064508.9-164258227.22-08.88-1.46 0.00 -0.05 -0.03 A1Vm -0.553-1.205 +.375-008SBO 13 10.3 11.2AB 4*';
|
||||
const NOVA =
|
||||
' 92 NOVA 1572 B Cas *';
|
||||
|
||||
const close = (actual, expected, epsilon = 1e-9) =>
|
||||
assert.ok(
|
||||
Math.abs(actual - expected) <= epsilon,
|
||||
`${actual} is not within ${epsilon} of ${expected}`,
|
||||
);
|
||||
|
||||
test('BSC5 records parse J2000 position, V magnitude and B−V', () => {
|
||||
const sirius = parseBscRecord(SIRIUS);
|
||||
assert.equal(sirius.hr, 2491);
|
||||
close(sirius.raDeg, (6 + 45 / 60 + 8.9 / 3600) * 15);
|
||||
close(sirius.decDeg, -(16 + 42 / 60 + 58 / 3600));
|
||||
assert.equal(sirius.vmag, -1.46);
|
||||
assert.equal(sirius.bv, 0);
|
||||
const first = parseBscRecord(HR_1);
|
||||
close(first.raDeg, (5 / 60 + 9.9 / 3600) * 15);
|
||||
close(first.decDeg, 45 + 13 / 60 + 45 / 3600);
|
||||
assert.equal(first.vmag, 6.7);
|
||||
assert.equal(first.bv, 0.07);
|
||||
assert.equal(parseBscRecord(NOVA), null);
|
||||
});
|
||||
|
||||
test('the star pack is a flat, rounded column array that counts its drops', () => {
|
||||
const pack = buildStarPack([HR_1, NOVA, SIRIUS, ''].join('\n'), {
|
||||
fetched: '2026-10-19',
|
||||
});
|
||||
assert.deepEqual(
|
||||
pack.stars,
|
||||
[1.291, 45.229, 6.7, 0.07, 101.287, -16.716, -1.46, 0],
|
||||
);
|
||||
assert.equal(pack.meta.count, 2);
|
||||
assert.equal(pack.meta.dropped, 1);
|
||||
assert.equal(pack.meta.fetched, '2026-10-19');
|
||||
});
|
||||
|
||||
test('cube faces use the WebGL major-axis layout', () => {
|
||||
// Each face centre looks down its own axis …
|
||||
const axes = {
|
||||
positiveX: [1, 0, 0],
|
||||
negativeX: [-1, 0, 0],
|
||||
positiveY: [0, 1, 0],
|
||||
negativeY: [0, -1, 0],
|
||||
positiveZ: [0, 0, 1],
|
||||
negativeZ: [0, 0, -1],
|
||||
};
|
||||
for (const face of CUBE_FACES)
|
||||
assert.deepEqual(
|
||||
cubeFaceDirection(face, 0.5, 0.5).map((v) => v + 0),
|
||||
axes[face],
|
||||
);
|
||||
// … and the first texel row and column follow OpenGL ES 3.0 §3.8.10:
|
||||
// on +X, s grows towards −Z and t towards −Y.
|
||||
assert.deepEqual(cubeFaceDirection('positiveX', 0, 0), [1, 1, 1]);
|
||||
assert.deepEqual(cubeFaceDirection('positiveZ', 1, 1), [1, -1, 1]);
|
||||
assert.deepEqual(cubeFaceDirection('negativeY', 0, 1), [-1, -1, -1]);
|
||||
assert.throws(() => cubeFaceDirection('up', 0, 0), RangeError);
|
||||
});
|
||||
|
||||
test('plate carrée is centred on longitude 0, growing to the left, north up', () => {
|
||||
assert.deepEqual(directionToPlateCarree(1, 0, 0), [0.5, 0.5]);
|
||||
const sixHours = directionToPlateCarree(0, 1, 0);
|
||||
close(sixHours[0], 0.25);
|
||||
const eighteenHours = directionToPlateCarree(0, -1, 0);
|
||||
close(eighteenHours[0], 0.75);
|
||||
close(directionToPlateCarree(0, 0, 1)[1], 0);
|
||||
close(directionToPlateCarree(0, 0, -1)[1], 1);
|
||||
const twelveHours = directionToPlateCarree(-1, 0, 0);
|
||||
assert.ok(twelveHours[0] === 0 || twelveHours[0] === 1);
|
||||
});
|
||||
|
||||
test('the galactic rotation puts the Galactic Centre and pole where the IAU does', () => {
|
||||
// Galactic origin l = b = 0 at RA 266.405°, Dec −28.936° (J2000).
|
||||
const centre = icrfToGalactic(raDecToUnitVector(266.40499, -28.93617));
|
||||
close(centre[0], 1, 1e-6);
|
||||
// North galactic pole at RA 192.85948°, Dec +27.12825°.
|
||||
const pole = icrfToGalactic(raDecToUnitVector(192.85948, 27.12825));
|
||||
close(pole[2], 1, 1e-6);
|
||||
// ESO's panorama: the Large Magellanic Cloud (l ≈ 280.5°, b ≈ −32.9°) sits
|
||||
// right of and below the centre.
|
||||
const [u, v] = directionToPlateCarree(
|
||||
...icrfToGalactic(raDecToUnitVector(80.894, -69.756)),
|
||||
);
|
||||
close(u, 0.5 + (360 - 280.47) / 360, 2e-3);
|
||||
close(v, 0.5 + 32.89 / 180, 2e-3);
|
||||
});
|
||||
|
||||
test('the black level eases in instead of cutting the faint sky into contours', () => {
|
||||
assert.equal(softBlack(-0.2), 0);
|
||||
assert.equal(softBlack(0), 0);
|
||||
// Continuous, rising, with no step at the black level.
|
||||
let previous = 0;
|
||||
for (let x = 0.001; x <= 1; x += 0.001) {
|
||||
const y = softBlack(x);
|
||||
assert.ok(y > previous && y - previous < 0.0011, `step at ${x.toFixed(3)}`);
|
||||
previous = y;
|
||||
}
|
||||
// Starts flat, then joins the straight line just below it.
|
||||
assert.ok(softBlack(0.003) < 0.0003);
|
||||
close(softBlack(1), 1 - 0.03, 1e-3);
|
||||
});
|
||||
|
||||
test('the faint-glow blend weight eases between its edges', () => {
|
||||
assert.equal(smoothstep(0.1, 0.2, 0), 0);
|
||||
assert.equal(smoothstep(0.1, 0.2, 0.1), 0);
|
||||
close(smoothstep(0.1, 0.2, 0.15), 0.5, 1e-9);
|
||||
assert.equal(smoothstep(0.1, 0.2, 0.2), 1);
|
||||
assert.equal(smoothstep(0.1, 0.2, 9), 1);
|
||||
});
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
launchLibraryRecentUrl,
|
||||
} from 'gods-eye-view/sources/space';
|
||||
import * as compatibility from '../../server/providers/local.js';
|
||||
import { createLaunchSource } from 'gods-eye-view/layers/launches/source';
|
||||
|
||||
function install(plugin, preview = false) {
|
||||
const routes = new Map();
|
||||
@@ -22,17 +23,23 @@ function install(plugin, preview = false) {
|
||||
},
|
||||
},
|
||||
});
|
||||
return async (route, url = '/', method = 'GET') => {
|
||||
return async (route, url = '/', method = 'GET', headers = {}) => {
|
||||
const res = {
|
||||
headersSent: false,
|
||||
headers: {},
|
||||
writeHead(status, headers) {
|
||||
Object.assign(this, { status, headers, headersSent: true });
|
||||
},
|
||||
// The same-site gates answer 403 through statusCode + setHeader.
|
||||
setHeader(name, value) {
|
||||
this.headers[name] = value;
|
||||
},
|
||||
end(body) {
|
||||
this.body = body;
|
||||
this.status ??= this.statusCode;
|
||||
},
|
||||
};
|
||||
await routes.get(route)({ url, method }, res);
|
||||
await routes.get(route)({ url, method, headers, socket: {} }, res);
|
||||
return res;
|
||||
};
|
||||
}
|
||||
@@ -161,7 +168,11 @@ test('CelesTrak answers 502 to an oversized TLE body without caching it', async
|
||||
assert.ok(bodies[0].pulled <= 17, 'reading stops at the 16 MiB cap');
|
||||
assert.equal(fsp.writeFile.mock.callCount(), 0);
|
||||
assert.equal((await request('/api/celestrak', '/stations')).status, 502);
|
||||
assert.equal(bodies.length, 2, 'nothing was cached in memory');
|
||||
assert.equal(
|
||||
bodies.length,
|
||||
1,
|
||||
'failed bodies are not cached but a cooldown bounds retry work',
|
||||
);
|
||||
});
|
||||
|
||||
for (const preview of [false, true])
|
||||
@@ -190,3 +201,194 @@ for (const preview of [false, true])
|
||||
assert.equal(hit.body, first.body);
|
||||
assert.equal(calls, 1);
|
||||
});
|
||||
|
||||
test('space providers honor Retry-After, gate groups, preserve stale dates and recover', async (t) => {
|
||||
isolateDisk(t);
|
||||
t.mock.method(console, 'warn', () => {});
|
||||
let now = Date.now();
|
||||
t.mock.method(Date, 'now', () => now);
|
||||
for (const kind of ['tle', 'launches']) {
|
||||
for (const retryAfter of ['90', new Date(now + 90000).toUTCString()]) {
|
||||
let calls = 0;
|
||||
let fail = false;
|
||||
let cancelled = false;
|
||||
t.mock.method(globalThis, 'fetch', async () => {
|
||||
calls++;
|
||||
if (fail)
|
||||
return new Response(
|
||||
new ReadableStream({
|
||||
cancel() {
|
||||
cancelled = true;
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: kind === 'tle' ? 403 : 429,
|
||||
headers: { 'Retry-After': retryAfter },
|
||||
},
|
||||
);
|
||||
return new Response(
|
||||
kind === 'tle'
|
||||
? 'ISS\n1 25544U fixture\n2 25544 fixture\n'
|
||||
: '{"results":[]}',
|
||||
);
|
||||
});
|
||||
const request = install(
|
||||
kind === 'tle' ? celestrakProxy() : rocketLaunchesProxy(),
|
||||
);
|
||||
const route = kind === 'tle' ? '/api/celestrak' : '/api/launches';
|
||||
const url = kind === 'tle' ? '/stations' : '/';
|
||||
if (kind === 'tle') {
|
||||
assert.equal(
|
||||
(await request(route, '/not-a-supported-group')).status,
|
||||
400,
|
||||
);
|
||||
assert.equal(calls, 0);
|
||||
}
|
||||
const first = await request(route, url);
|
||||
now += 7 * 3600000;
|
||||
// Recompute the HTTP-date relative to the clock at the failed refresh.
|
||||
const originalFetch = globalThis.fetch;
|
||||
t.mock.method(globalThis, 'fetch', async (...args) => {
|
||||
const response = await originalFetch(...args);
|
||||
if (fail && !/^\d+$/.test(retryAfter))
|
||||
response.headers.set(
|
||||
'Retry-After',
|
||||
new Date(now + 90000).toUTCString(),
|
||||
);
|
||||
return response;
|
||||
});
|
||||
fail = true;
|
||||
const stale = await request(route, url);
|
||||
assert.equal(stale.status, 200);
|
||||
assert.equal(stale.body, first.body);
|
||||
assert.equal(stale.headers['Retry-After'], '90');
|
||||
assert.ok(
|
||||
stale.headers[kind === 'tle' ? 'X-TLE-Cached-At' : 'X-GEV-Cached-At'],
|
||||
);
|
||||
assert.equal(cancelled, true);
|
||||
await request(route, url);
|
||||
if (kind === 'tle')
|
||||
assert.equal((await request(route, '/visual')).status, 502);
|
||||
assert.equal(calls, 2, 'cooldown applies across TLE groups');
|
||||
now += 91000;
|
||||
fail = false;
|
||||
assert.equal((await request(route, url)).status, 200);
|
||||
assert.equal(calls, 3);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('Space Missions reads the active catalog through the real proxy while unknown groups stay rejected', async (t) => {
|
||||
isolateDisk(t);
|
||||
const tle = 'ISS\n1 25544U fixture\n2 25544 fixture\n';
|
||||
const fetched = [];
|
||||
t.mock.method(globalThis, 'fetch', async (url) => {
|
||||
fetched.push(new URL(url).searchParams.get('GROUP'));
|
||||
return new Response(tle);
|
||||
});
|
||||
const request = install(celestrakProxy());
|
||||
// Route the browser source's real request into the middleware.
|
||||
const source = createLaunchSource({
|
||||
fetchImpl: async (url) => {
|
||||
const prefix = '/api/celestrak';
|
||||
assert.ok(url.startsWith(`${prefix}/`));
|
||||
const res = await request(prefix, url.slice(prefix.length));
|
||||
return new Response(res.body, { status: res.status });
|
||||
},
|
||||
});
|
||||
assert.equal(await source.getActiveTle(), tle);
|
||||
assert.deepEqual(fetched, ['active']);
|
||||
for (const group of ['/not-a-group', '/active-extra', '/../active'])
|
||||
assert.equal((await request('/api/celestrak', group)).status, 400);
|
||||
assert.deepEqual(fetched, ['active'], 'rejected groups never reach upstream');
|
||||
});
|
||||
|
||||
test('CelesTrak admits a same-origin request behind the documented reverse proxy and refuses foreign origins', async (t) => {
|
||||
isolateDisk(t);
|
||||
let calls = 0;
|
||||
t.mock.method(globalThis, 'fetch', async () => {
|
||||
calls++;
|
||||
return new Response('ISS\n1 25544U fixture\n2 25544 fixture\n');
|
||||
});
|
||||
const request = install(celestrakProxy());
|
||||
const proxied = {
|
||||
host: 'gev.example.test',
|
||||
'x-forwarded-for': '203.0.113.7',
|
||||
'x-forwarded-host': 'gev.example.test',
|
||||
'x-forwarded-proto': 'https',
|
||||
via: '1.1 proxy',
|
||||
};
|
||||
const app = await request('/api/celestrak', '/stations', 'GET', {
|
||||
...proxied,
|
||||
origin: 'https://gev.example.test',
|
||||
'sec-fetch-site': 'same-origin',
|
||||
});
|
||||
assert.equal(app.status, 200);
|
||||
assert.equal(app.headers['x-tle-cache'], 'MISS');
|
||||
assert.equal(calls, 1);
|
||||
for (const headers of [
|
||||
{ ...proxied, origin: 'https://unrelated.example' },
|
||||
{ ...proxied, origin: 'null' },
|
||||
{ ...proxied, 'sec-fetch-site': 'cross-site' },
|
||||
]) {
|
||||
const refused = await request('/api/celestrak', '/visual', 'GET', headers);
|
||||
assert.equal(refused.status, 403);
|
||||
}
|
||||
assert.equal(calls, 1, 'refused requests do no provider or cache work');
|
||||
});
|
||||
|
||||
for (const longFirst of [true, false])
|
||||
test(`CelesTrak keeps the longest cooldown when concurrent failures finish ${longFirst ? 'long then short' : 'short then long'}`, async (t) => {
|
||||
isolateDisk(t);
|
||||
t.mock.method(console, 'warn', () => {});
|
||||
let now = Date.now();
|
||||
t.mock.method(Date, 'now', () => now);
|
||||
const pending = new Map();
|
||||
let calls = 0;
|
||||
t.mock.method(globalThis, 'fetch', async (url) => {
|
||||
calls++;
|
||||
const group = new URL(url).searchParams.get('GROUP');
|
||||
await new Promise((resolve) => pending.set(group, resolve));
|
||||
return group === 'stations'
|
||||
? new Response('slow down', {
|
||||
status: 429,
|
||||
headers: { 'Retry-After': '1800' },
|
||||
})
|
||||
: new Response('unavailable', { status: 503 });
|
||||
});
|
||||
const request = install(celestrakProxy());
|
||||
const long = request('/api/celestrak', '/stations');
|
||||
const short = request('/api/celestrak', '/visual');
|
||||
while (pending.size < 2) await new Promise(setImmediate);
|
||||
const order = longFirst
|
||||
? [
|
||||
['stations', long],
|
||||
['visual', short],
|
||||
]
|
||||
: [
|
||||
['visual', short],
|
||||
['stations', long],
|
||||
];
|
||||
for (const [group, response] of order) {
|
||||
pending.get(group)();
|
||||
assert.equal((await response).status, 502);
|
||||
}
|
||||
assert.equal(calls, 2);
|
||||
const cooling = await request('/api/celestrak', '/geo');
|
||||
assert.equal(cooling.status, 502);
|
||||
assert.equal(cooling.headers['Retry-After'], '1800');
|
||||
now += 61_000;
|
||||
assert.equal((await request('/api/celestrak', '/geo')).status, 502);
|
||||
assert.equal(
|
||||
calls,
|
||||
2,
|
||||
'the 60 s fallback did not replace the 30 min deadline',
|
||||
);
|
||||
now += 1800_000;
|
||||
t.mock.method(
|
||||
globalThis,
|
||||
'fetch',
|
||||
async () => new Response('ISS\n1 25544U fixture\n2 25544 fixture\n'),
|
||||
);
|
||||
assert.equal((await request('/api/celestrak', '/geo')).status, 200);
|
||||
});
|
||||
@@ -0,0 +1,170 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
trafficFixtureResponse,
|
||||
trafficFixtureTile,
|
||||
} from '../../scripts/traffic-fixtures.mjs';
|
||||
import { decodeOpenFreeMapTile } from '../sources/openFreeMap.js';
|
||||
import { isDrivableFlowSegment } from '../layers/traffic/roadModes.js';
|
||||
import { decodeFlowTile } from '../layers/traffic/flowDecode.js';
|
||||
|
||||
test('traffic fixtures provide aligned current road tiles and partial flow coverage', () => {
|
||||
for (const z of [12, 14]) {
|
||||
const roads = decodeOpenFreeMapTile(
|
||||
trafficFixtureTile(z),
|
||||
z,
|
||||
935,
|
||||
1686,
|
||||
).roads;
|
||||
const flow = decodeFlowTile(trafficFixtureTile(z, true), z, 935, 1686);
|
||||
assert.ok(roads.length > flow.length && flow.length > 0);
|
||||
for (const segment of flow) {
|
||||
assert.equal(segment.trafficLevel, 0.6);
|
||||
assert.equal(isDrivableFlowSegment(segment), true);
|
||||
assert.ok(
|
||||
roads.some(
|
||||
(road) =>
|
||||
JSON.stringify(road.coordinates) === JSON.stringify(segment.coords),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('fixture TileJSON and every road/flow request stay on intercepted sources', () => {
|
||||
const request = (url) => trafficFixtureResponse({ url: () => url });
|
||||
const tileJson = request('https://tiles.openfreemap.org/planet');
|
||||
assert.equal(tileJson.headers['access-control-allow-origin'], '*');
|
||||
const metadata = JSON.parse(tileJson.body);
|
||||
const tileUrl = metadata.tiles[0]
|
||||
.replace('{z}', '14')
|
||||
.replace('{x}', '935')
|
||||
.replace('{y}', '1686');
|
||||
assert.ok(
|
||||
decodeOpenFreeMapTile(request(tileUrl).body, 14, 935, 1686).roads.length,
|
||||
);
|
||||
assert.ok(
|
||||
decodeFlowTile(
|
||||
request('http://localhost/api/tomtom/flow/12/935/1686.pbf').body,
|
||||
12,
|
||||
935,
|
||||
1686,
|
||||
).length,
|
||||
);
|
||||
assert.throws(
|
||||
() => request('https://tiles.openfreemap.org/unexpected'),
|
||||
/Unrecognized/,
|
||||
);
|
||||
assert.equal(request('http://localhost/api/unrelated'), null);
|
||||
});
|
||||
|
||||
test('flat terrain fixture decodes through Cesium and advertises no network children', async () => {
|
||||
const { createServer } = await import('node:http');
|
||||
const { CesiumTerrainProvider } = await import('cesium');
|
||||
const server = createServer((req, res) => {
|
||||
const response = trafficFixtureResponse({
|
||||
url: () => `https://terrain.reearth.land${req.url}`,
|
||||
});
|
||||
res.writeHead(response.status, { 'content-type': response.contentType });
|
||||
res.end(response.body);
|
||||
});
|
||||
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
||||
try {
|
||||
const provider = await CesiumTerrainProvider.fromUrl(
|
||||
`http://127.0.0.1:${server.address().port}`,
|
||||
);
|
||||
for (const x of [0, 1]) {
|
||||
const data = await provider.requestTileGeometry(x, 0, 0);
|
||||
const rect = provider.tilingScheme.tileXYToRectangle(x, 0, 0);
|
||||
assert.equal(
|
||||
data.interpolateHeight(rect, (rect.west + rect.east) / 2, 0),
|
||||
0,
|
||||
);
|
||||
for (const dx of [0, 1])
|
||||
for (const dy of [0, 1]) {
|
||||
assert.equal(data.isChildAvailable(x, 0, x * 2 + dx, dy), false);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
server.closeAllConnections();
|
||||
await new Promise((resolve) => server.close(resolve));
|
||||
}
|
||||
});
|
||||
|
||||
test('fixture interception targets provider URLs and leaves app workers alone', async () => {
|
||||
const { installTrafficRoutes } =
|
||||
await import('../../scripts/traffic-fixtures.mjs');
|
||||
const sent = [];
|
||||
let paused;
|
||||
const session = {
|
||||
on: (_, handler) => {
|
||||
paused = handler;
|
||||
},
|
||||
send: async (name, args) => {
|
||||
sent.push({ name, args });
|
||||
},
|
||||
};
|
||||
await installTrafficRoutes(
|
||||
{ createCDPSession: async () => session, isClosed: () => false },
|
||||
{ origin: 'http://localhost:4173', fixtures: true },
|
||||
);
|
||||
const patterns = sent[0].args.patterns.map((p) => p.urlPattern);
|
||||
assert.deepEqual(patterns, [
|
||||
'http://localhost:4173/api/tomtom/status*',
|
||||
'http://localhost:4173/api/tomtom/flow/*',
|
||||
'https://tiles.openfreemap.org/*',
|
||||
'https://terrain.reearth.land/*',
|
||||
]);
|
||||
await paused({
|
||||
requestId: 'terrain',
|
||||
request: { url: 'https://terrain.reearth.land/layer.json' },
|
||||
});
|
||||
const reply = sent.at(-1);
|
||||
assert.equal(reply.name, 'Fetch.fulfillRequest');
|
||||
assert.equal(
|
||||
JSON.parse(Buffer.from(reply.args.body, 'base64')).format,
|
||||
'heightmap-1.0',
|
||||
);
|
||||
assert.ok(
|
||||
reply.args.responseHeaders.some(
|
||||
(h) => h.name === 'access-control-allow-origin' && h.value === '*',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('keyless interception refuses and records unexpected flow requests', async () => {
|
||||
const { installTrafficRoutes } =
|
||||
await import('../../scripts/traffic-fixtures.mjs');
|
||||
const sent = [],
|
||||
flows = [];
|
||||
let paused;
|
||||
const session = {
|
||||
on: (_, handler) => {
|
||||
paused = handler;
|
||||
},
|
||||
send: async (name, args) => {
|
||||
sent.push({ name, args });
|
||||
},
|
||||
};
|
||||
await installTrafficRoutes(
|
||||
{ createCDPSession: async () => session, isClosed: () => false },
|
||||
{
|
||||
origin: 'http://localhost:4173',
|
||||
keyless: true,
|
||||
onFlow: (url) => flows.push(url),
|
||||
},
|
||||
);
|
||||
await paused({
|
||||
requestId: 'status',
|
||||
request: { url: 'http://localhost:4173/api/tomtom/status' },
|
||||
});
|
||||
assert.deepEqual(JSON.parse(Buffer.from(sent.at(-1).args.body, 'base64')), {
|
||||
hasKey: false,
|
||||
});
|
||||
await paused({
|
||||
requestId: 'flow',
|
||||
request: { url: 'http://localhost:4173/api/tomtom/flow/12/1/1.pbf' },
|
||||
});
|
||||
assert.equal(sent.at(-1).args.responseCode, 503);
|
||||
assert.equal(flows.length, 1);
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { getTransitFeed } from '../data/transitFeeds.js';
|
||||
import {
|
||||
chooseLiveCities,
|
||||
selectStyle,
|
||||
@@ -101,6 +102,18 @@ test('live visual cities follow local operating hours in summer and winter', ()
|
||||
const cities = chooseLiveCities(now);
|
||||
assert.ok(cities.length >= 1);
|
||||
for (const city of cities) {
|
||||
const feed = getTransitFeed(city.feedId);
|
||||
assert.ok(feed, `${city.id} has a registered feed`);
|
||||
const distanceKm =
|
||||
111.32 *
|
||||
Math.hypot(
|
||||
feed.center.lat - city.lat,
|
||||
(feed.center.lon - city.lon) * Math.cos((city.lat * Math.PI) / 180),
|
||||
);
|
||||
assert.ok(
|
||||
distanceKm < feed.loadRadiusKm,
|
||||
`${city.id} is inside its feed's admission region`,
|
||||
);
|
||||
const local = Number(
|
||||
new Intl.DateTimeFormat('en', {
|
||||
timeZone: city.zone,
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { BROWSER_CSP, createBrowserViteConfig } from '../../build/vite.js';
|
||||
import { rm, writeFile } from 'node:fs/promises';
|
||||
import { request } from 'node:http';
|
||||
import path from 'node:path';
|
||||
import { build, createServer, preview } from 'vite';
|
||||
import {
|
||||
BROWSER_CSP,
|
||||
createBrowserViteConfig,
|
||||
escapeFsDenyPath,
|
||||
} from '../../build/vite.js';
|
||||
import standaloneConfig, * as compatibility from '../../vite.config.js';
|
||||
import * as providers from '../../server/providers/local.js';
|
||||
import { apiNotFoundPlugin } from '../../server/standalone/api-not-found.js';
|
||||
import { makeFixtureRoot } from './fixtureRoot.mjs';
|
||||
|
||||
test('explicit build inputs preserve browser-only defines, plugin order and loopback protections', () => {
|
||||
const plugin = { name: 'fixture-provider' };
|
||||
@@ -12,15 +23,32 @@ test('explicit build inputs preserve browser-only defines, plugin order and loop
|
||||
googleApiKey: 'browser-fixture',
|
||||
cesiumToken: 'ion-fixture',
|
||||
});
|
||||
// The Host check runs before every other plugin's middleware.
|
||||
assert.equal(config.plugins[0].name, 'host-check');
|
||||
// API response headers, then the Host check, run before every other
|
||||
// plugin's middleware.
|
||||
assert.equal(config.plugins[0].name, 'api-response-headers');
|
||||
assert.equal(config.plugins[0].enforce, 'pre');
|
||||
assert.equal(config.plugins[3], plugin);
|
||||
assert.equal(config.plugins[1].name, 'host-check');
|
||||
assert.equal(config.plugins[1].enforce, 'pre');
|
||||
assert.equal(config.plugins[4], plugin);
|
||||
assert.equal(config.server.host, 'localhost');
|
||||
assert.equal(config.server.port, 4173);
|
||||
assert.deepEqual(config.server.allowedHosts, ['localhost', '127.0.0.1']);
|
||||
assert.ok(config.server.fs.deny.includes('**/ENVIRONMENT'));
|
||||
assert.ok(config.server.fs.deny.includes('.env.*'));
|
||||
assert.ok(config.server.fs.deny.includes('**/.gev-logs/**'));
|
||||
assert.ok(config.server.fs.deny.includes('**/.gev-cache/**'));
|
||||
const root = fileURLToPath(new URL('../../', import.meta.url))
|
||||
.replace(/\\/g, '/')
|
||||
.replace(/\/$/, '');
|
||||
assert.deepEqual(
|
||||
config.server.fs.deny.filter((pattern) => pattern.endsWith('/dist/**')),
|
||||
[`${escapeFsDenyPath(root)}/dist/**`],
|
||||
'only project build output is denied, not dependency dist directories',
|
||||
);
|
||||
assert.equal(
|
||||
escapeFsDenyPath('C:/Users/a/project (1) [test]{copy}'),
|
||||
'C:/Users/a/project \\(1\\) \\[test\\]\\{copy\\}',
|
||||
);
|
||||
assert.equal(config.server.headers['X-Frame-Options'], 'DENY');
|
||||
assert.equal(config.server.headers['X-Content-Type-Options'], 'nosniff');
|
||||
const csp = config.server.headers['Content-Security-Policy'];
|
||||
@@ -75,7 +103,7 @@ test('build helper does not discover environment values or construct local provi
|
||||
undefined,
|
||||
);
|
||||
assert.deepEqual(
|
||||
config.plugins.slice(3).map((plugin) => plugin.name),
|
||||
config.plugins.slice(4).map((plugin) => plugin.name),
|
||||
['embed-framing', 'panel-build'],
|
||||
);
|
||||
} finally {
|
||||
@@ -89,7 +117,7 @@ test('root config retains existing named exports and standalone provider order',
|
||||
assert.equal(compatibility[name], value, name);
|
||||
const config = standaloneConfig({ mode: 'test' });
|
||||
const standaloneProviders = config.plugins
|
||||
.slice(3, -5)
|
||||
.slice(4, -6)
|
||||
.map((plugin) => plugin.name);
|
||||
assert.equal(
|
||||
standaloneProviders.filter((name) => name === 'gemini-live-proxy').length,
|
||||
@@ -103,9 +131,10 @@ test('root config retains existing named exports and standalone provider order',
|
||||
standaloneProviders.filter((name) => name !== 'gemini-live-proxy'),
|
||||
providers.localProviderPlugins().map((plugin) => plugin.name),
|
||||
);
|
||||
assert.equal(config.plugins.at(-6).name, 'gev-key-setup');
|
||||
// The local MCP route follows every provider and precedes the API fallback.
|
||||
assert.equal(config.plugins.at(-5).name, 'local-mcp');
|
||||
assert.equal(config.plugins.at(-7).name, 'gev-key-setup');
|
||||
// Token fallback follows real providers and local MCP before the API 404.
|
||||
assert.equal(config.plugins.at(-6).name, 'local-mcp');
|
||||
assert.equal(config.plugins.at(-5).name, 'google-token-unavailable');
|
||||
assert.equal(config.plugins.at(-4).name, 'api-not-found');
|
||||
// Config-only, so it sits after the provider slice rather than in it.
|
||||
assert.equal(config.plugins.at(-3).name, 'gev-unwatched-runtime-dirs');
|
||||
@@ -257,3 +286,129 @@ test('the CSP admits the event media embeds and the Maps JavaScript API, and no
|
||||
// X posts load in X's own frames.
|
||||
assert.ok(frames.includes('https://platform.twitter.com'));
|
||||
});
|
||||
|
||||
/** A raw GET, so the Host header and an absolute-form target are sent as given. */
|
||||
function rawGet(port, target, headers = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = request(
|
||||
{
|
||||
host: '127.0.0.1',
|
||||
port,
|
||||
path: target,
|
||||
headers: { host: `localhost:${port}`, ...headers },
|
||||
},
|
||||
(res) => {
|
||||
res.resume();
|
||||
res.on('end', () =>
|
||||
resolve({ status: res.statusCode, headers: res.headers }),
|
||||
);
|
||||
},
|
||||
);
|
||||
req.on('error', reject);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
test('every API response carries the API policy and nosniff on real dev and preview servers', async (t) => {
|
||||
const policy = "default-src 'none'; sandbox";
|
||||
const root = await makeFixtureRoot('gev-api-headers-');
|
||||
t.after(() => rm(root, { recursive: true, force: true }));
|
||||
await writeFile(
|
||||
path.join(root, 'index.html'),
|
||||
'<!doctype html><title>Fixture</title><p>x</p>',
|
||||
);
|
||||
const install = (server) => {
|
||||
server.middlewares.use('/api/fixture', (req, res) => {
|
||||
const route = new URL(req.url, 'http://localhost').pathname;
|
||||
if (route === '/ok') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end('{}');
|
||||
} else if (route === '/error') {
|
||||
res.statusCode = 500;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end('{"error":"fixture"}');
|
||||
} else if (route === '/partial') {
|
||||
res.writeHead(206, { 'Content-Range': 'bytes 0-0/2' });
|
||||
res.end('x');
|
||||
} else if (route === '/own-policy') {
|
||||
res.setHeader('Content-Security-Policy', "frame-ancestors 'none'");
|
||||
res.end('{}');
|
||||
} else {
|
||||
res.writeHead(200, { 'Content-Security-Policy': 'default-src *' });
|
||||
res.end('{}');
|
||||
}
|
||||
});
|
||||
};
|
||||
const fixture = {
|
||||
name: 'fixture-provider',
|
||||
configureServer: install,
|
||||
configurePreviewServer: install,
|
||||
};
|
||||
const built = createBrowserViteConfig({ plugins: [fixture] });
|
||||
const base = {
|
||||
root,
|
||||
configFile: false,
|
||||
envFile: false,
|
||||
publicDir: false,
|
||||
logLevel: 'silent',
|
||||
};
|
||||
await build(base);
|
||||
for (const isPreview of [false, true]) {
|
||||
const server = await (isPreview ? preview : createServer)({
|
||||
...base,
|
||||
plugins: [
|
||||
...built.plugins.filter(
|
||||
(plugin) =>
|
||||
plugin === fixture ||
|
||||
['api-response-headers', 'host-check'].includes(plugin.name),
|
||||
),
|
||||
apiNotFoundPlugin(),
|
||||
],
|
||||
server: { host: '127.0.0.1', port: 0, hmr: false },
|
||||
preview: { host: '127.0.0.1', port: 0 },
|
||||
});
|
||||
if (!isPreview) await server.listen();
|
||||
const { port } = server.httpServer.address();
|
||||
const mode = isPreview ? 'preview' : 'dev';
|
||||
try {
|
||||
for (const [target, status, csp = policy, headers] of [
|
||||
['/api/fixture/ok', 200],
|
||||
['/api/fixture/error', 500],
|
||||
['/api/fixture/partial', 206],
|
||||
['/api/nope', 404],
|
||||
['/api', 404],
|
||||
['/api/fixture/ok?x=1', 200],
|
||||
// Spellings the router mounts as the same routes.
|
||||
['/API/fixture/ok', 200],
|
||||
['/Api/fixture/error', 500],
|
||||
['/aPi/nope', 404],
|
||||
['/API', 404],
|
||||
['/api.x', 404],
|
||||
[`http://localhost:${port}/api/fixture/ok`, 200],
|
||||
[`http://localhost:${port}/API/nope`, 404],
|
||||
// The router keeps dot segments in an absolute-form target's path.
|
||||
['http://elsewhere.example/api/../x', 404],
|
||||
['http://elsewhere.example/API/%2e%2e/x', 404],
|
||||
['/api/fixture/ok', 403, policy, { host: 'elsewhere.example' }],
|
||||
// A route's own policy is added to the API policy, never replaces it.
|
||||
['/api/fixture/own-policy', 200, `${policy}, frame-ancestors 'none'`],
|
||||
['/api/fixture/own-policy-head', 200, `${policy}, default-src *`],
|
||||
]) {
|
||||
const res = await rawGet(port, target, headers);
|
||||
const label = `${mode} ${target} ${JSON.stringify(headers || {})}`;
|
||||
assert.equal(res.status, status, label);
|
||||
assert.equal(res.headers['content-security-policy'], csp, label);
|
||||
assert.equal(res.headers['x-content-type-options'], 'nosniff', label);
|
||||
}
|
||||
// The app document and other paths keep the server's own headers.
|
||||
for (const target of ['/', '/apiary', '/src/api/x.js'])
|
||||
assert.notEqual(
|
||||
(await rawGet(port, target)).headers['content-security-policy'],
|
||||
policy,
|
||||
`${mode} ${target}`,
|
||||
);
|
||||
} finally {
|
||||
await server.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -234,6 +234,19 @@ test('typed argument checks feed full-call accuracy without changing the legacy
|
||||
const inMeters = gradeCore(altitude, [{ name: 'analyst_query', args: { layers: ['flights'], filters: [{ field: 'altitudeM', op: 'gt', value: 12192 }] } }]);
|
||||
assert.equal(inMeters.fullCallOk, true);
|
||||
assert.equal(where('set_hud', (a) => a.visible === 'off')({ name: 'set_hud', args: { visible: 'off' } }), true);
|
||||
const globe = byPhrase('Keep spinning the Earth slowly to the left');
|
||||
const globeArgs = { motion: 'rotate_globe', direction: 'left', speed: 'slow' };
|
||||
for (const mode of [undefined, 'continuous', 'once', null]) {
|
||||
assert.equal(gradeCore(globe, [{ name: 'move_camera', args: { ...globeArgs, mode } }]).fullCallOk, mode === undefined || mode === 'continuous');
|
||||
}
|
||||
assert.equal(gradeCore(globe, [{ name: 'move_camera', args: { ...globeArgs, direction: 'right' } }]).fullCallOk, false);
|
||||
assert.equal(gradeCore(globe, [{ name: 'move_camera', args: { ...globeArgs, speed: 'normal' } }]).fullCallOk, false);
|
||||
const right = byPhrase('Rotate the globe to the right');
|
||||
assert.equal(gradeCore(right, [{ name: 'move_camera', args: { motion: 'rotate_globe' } }]).fullCallOk, true);
|
||||
for (const direction of ['right', 'left', null]) {
|
||||
assert.equal(gradeCore(right, [{ name: 'move_camera', args: { motion: 'rotate_globe', direction } }]).fullCallOk, direction === 'right');
|
||||
}
|
||||
assert.equal(gradeCore(right, [{ name: 'move_camera', args: { motion: 'rotate', direction: 'right' } }]).fullCallOk, false);
|
||||
});
|
||||
|
||||
test('gradeDialogue scores turn 2 in the context of turn 1', () => {
|
||||
|
||||
Reference in new issue
Block a user