mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
Merge current main into S2 GhostMaps and preserve updated import and voice behavior
This commit is contained in:
commit
4da002bd42
515 files changed
+17681
-2202
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 },
|
||||
|
||||
@@ -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,3 +1,4 @@
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
@@ -5,7 +6,11 @@ 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 } from '../../build/vite.js';
|
||||
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';
|
||||
@@ -30,6 +35,20 @@ test('explicit build inputs preserve browser-only defines, plugin order and loop
|
||||
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'];
|
||||
@@ -98,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(4, -5)
|
||||
.slice(4, -6)
|
||||
.map((plugin) => plugin.name);
|
||||
assert.equal(
|
||||
standaloneProviders.filter((name) => name === 'gemini-live-proxy').length,
|
||||
@@ -112,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');
|
||||
|
||||
@@ -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