Merge branch 'main' into codex/fix-space-provider-cooldowns

This commit is contained in:
Said Turksever authored and GitHub committed 2026-10-23 14:39:06 +01:00
commit a4f9b5e925
28 files changed
+824 -146

No files matched your search

+52
View File
@@ -712,6 +712,58 @@ test('unusable directory responses preserve warm client state atomically', async
assert.equal(getRadioUIState().stale, true);
assert.equal(getRadioUIState().degraded, true);
}
globalThis.fetch = async () => new Response(JSON.stringify({
stations: [replacement, { id: 'malformed' }], stale: false, degraded: false,
acceptedGeneration: 2, catalogInstance: 'qa-instance-a', updatedAt: now,
}));
await radioLayer.update();
assert.equal(getRadioUIState().stationCount, 1);
assert.equal(getRadioUIState().rejectedStationCount, 1);
assert.equal(getRadioUIState().degraded, true);
assert.equal(getRadioUIState().stale, false);
assert.equal(getRadioAcceptedCatalogSnapshot().generation, 2);
assert.equal(getRadioAcceptedCatalogSnapshot().stations[0].id, replacement.id);
// Server-side structural rejections are combined with client rejections
// after admission; the healthy generation is still accepted.
globalThis.fetch = async () => new Response(JSON.stringify({
stations: [replacement, { id: 'malformed' }], stale: false, degraded: false,
acceptedGeneration: 3, catalogInstance: 'qa-instance-a', updatedAt: now,
rejectedStationCount: 2,
}));
await radioLayer.update();
assert.equal(getRadioUIState().rejectedStationCount, 3);
assert.equal(getRadioUIState().degraded, true);
assert.equal(getRadioAcceptedCatalogSnapshot().generation, 3);
globalThis.fetch = async () => new Response(JSON.stringify({
stations: [replacement], stale: false, degraded: false,
acceptedGeneration: 4, catalogInstance: 'qa-instance-a', updatedAt: now,
rejectedStationCount: 10_000,
}));
await radioLayer.update();
assert.equal(getRadioUIState().rejectedStationCount, 10_000);
assert.equal(getRadioAcceptedCatalogSnapshot().generation, 4);
for (const rejectedStationCount of [-1, 1.5, '2', null, 10_001]) {
globalThis.fetch = async () => new Response(JSON.stringify({
stations: [replacement], stale: false, degraded: false,
acceptedGeneration: 5, catalogInstance: 'qa-instance-a', updatedAt: now,
rejectedStationCount,
}));
await radioLayer.update();
assert.equal(getRadioAcceptedCatalogSnapshot().generation, 4);
assert.equal(getRadioUIState().stale, true);
}
globalThis.fetch = async () => new Response(JSON.stringify({
stations: [{ id: 'malformed' }], stale: false, degraded: false,
acceptedGeneration: 5, catalogInstance: 'qa-instance-a', updatedAt: now,
}));
await radioLayer.update();
assert.equal(getRadioAcceptedCatalogSnapshot().generation, 4);
assert.equal(getRadioUIState().stale, true);
assert.equal(getRadioUIState().rejectedStationCount, 10_000);
radioLayer.destroy();
radioLayer.init(viewer);
assert.equal(getRadioUIState().rejectedStationCount, 0);
assert.equal(getRadioUIState().degraded, false);
} finally {
globalThis.fetch = originalFetch;
radioLayer.destroy();
+67 -1
View File
@@ -11,7 +11,14 @@ import {
publicRadioStation,
publicRadioHttpsUrl,
} from '../../vite.config.js';
import { rankRadioStationsForRequest } from './radio.js';
import {
getRadioAcceptedCatalogSnapshot,
getRadioUIState,
radioLayer,
rankRadioStationsForRequest,
} from './radio.js';
import { RADIO_REJECTED_STATION_COUNT_MAX as SERVER_REJECTED_MAX } from '../../server/providers/radio/constants.js';
import { RADIO_REJECTED_STATION_COUNT_MAX as CLIENT_REJECTED_MAX } from '../layers/radio/policy.js';
const UUID = '12345678-1234-4234-8234-123456789abc';
const publicLookup = async () => [{ address: '93.184.216.34', family: 4 }];
@@ -533,3 +540,62 @@ test('a mirror answering an invalid reason phrase rejects instead of crashing',
const crashed = mockMirrorStatus(t, 200, 'OK\x7f');
await assert.rejects(Promise.race([fetchMirror(), crashed]), TypeError);
});
test('case expansion is capped after tag normalization in the portable and server station path', () => {
const row = normalizeRadioBrowserStation(station({tags: 'İ'.repeat(80) + ', NEWS__RADIO ,news-radio'}));
assert.equal(row.tags[0].length, 80);
assert.equal(row.tags[0], 'i\u0307'.repeat(40));
assert.deepEqual(row.tags.slice(1), ['news radio']);
});
test('server structural rejections reach the client diagnostic without discarding a healthy generation', async () => {
const originalFetch = globalThis.fetch;
const middleware = createProxy({
now: () => Date.now(),
fetchImpl: async (url) => {
if (String(url).includes('/json/servers')) return responseJson([{ name: 'de1.api.radio-browser.info' }]);
const rows = healthyRowsForQuery(url);
if (queryTag(url)) return responseJson(rows);
// 400 valid broad rows, one row missing its required name, and one
// schema-valid row that normalization filters by policy (not counted).
const { name: _name, ...nameless } = station({ stationuuid: '50000000-0000-4000-8000-000000000001' });
return responseJson([...rows, nameless, station({ stationuuid: '50000000-0000-4000-8000-000000000002', lastcheckok: 0 })]);
},
});
const response = await invoke(middleware, '/stations');
assert.equal(response.status, 200);
const body = JSON.parse(response.body);
assert.equal(body.degraded, false);
assert.equal(body.degradedReason, null);
assert.equal(body.acceptedGeneration, 1);
assert.equal(body.rejectedStationCount, 1);
assert.equal(SERVER_REJECTED_MAX, CLIENT_REJECTED_MAX);
const viewer = {
camera: { positionWC: { x: 7_000_000, y: 0, z: 0 } },
scene: { canvas: { disableRootEvents: true, onwheel: null, addEventListener() {}, removeEventListener() {} } },
dataSources: { add() {}, remove() {} },
entities: { add(entity) { return entity; }, remove() {} },
};
radioLayer.destroy();
try {
globalThis.fetch = async () => new Response(response.body, {
status: response.status,
headers: { 'Content-Type': 'application/json' },
});
radioLayer.init(viewer);
radioLayer.enable();
await radioLayer.update();
const state = getRadioUIState();
assert.equal(state.stationCount, body.stations.length);
assert.equal(state.rejectedStationCount, 1);
assert.equal(state.degraded, true);
assert.equal(state.stale, false);
assert.equal(getRadioAcceptedCatalogSnapshot().generation, 1);
assert.equal(getRadioAcceptedCatalogSnapshot().instance, body.catalogInstance);
} finally {
globalThis.fetch = originalFetch;
radioLayer.destroy();
}
});
+22 -6
View File
@@ -2,6 +2,7 @@ import { msg } from '../../i18n/index.js';
import {
RADIO_DIRECTORY_STALE_MS,
RADIO_DIRECTORY_FUTURE_SKEW_MS,
RADIO_REJECTED_STATION_COUNT_MAX,
} from './policy.js';
// Directory errors stay English in layer state and stats; the layer row and
@@ -64,17 +65,24 @@ export function createIngestion({
typeof body.degraded !== 'boolean'
)
throw new Error('Radio directory freshness metadata was malformed');
// Rows the server skipped as structurally malformed. Older envelopes
// omit the field; a present value must be a bounded count.
const serverRejectedStationCount =
body.rejectedStationCount === undefined
? 0
: body.rejectedStationCount;
if (
!Number.isSafeInteger(serverRejectedStationCount) ||
serverRejectedStationCount < 0 ||
serverRejectedStationCount > RADIO_REJECTED_STATION_COUNT_MAX
)
throw new Error('Radio directory diagnostic metadata was malformed');
const rows = body.stations;
const acceptedRows = rows.filter(
parts.catalogModel.isValidRadioDirectoryStation,
);
if (!acceptedRows.length)
throw new Error('Radio directory returned no usable stations');
if (acceptedRows.length !== rows.length) {
throw new Error(
'Radio directory response contained malformed stations',
);
}
const acceptedGeneration = body.acceptedGeneration;
if (
acceptedGeneration !== null &&
@@ -147,7 +155,15 @@ export function createIngestion({
}
if (!repeatingAcceptedGeneration) layerState._updatedAt = updatedAt;
}
layerState._degraded = body.degraded;
// Combine server-side structural rejections with rows this client
// refused. The count is diagnostic: it marks coverage degraded but does
// not undo the generation admitted above.
layerState._rejectedStationCount = Math.min(
RADIO_REJECTED_STATION_COUNT_MAX,
serverRejectedStationCount + rows.length - acceptedRows.length,
);
layerState._degraded =
body.degraded || layerState._rejectedStationCount > 0;
layerState._stale = body.stale;
layerState._error = preservingWarmCatalog
? ERROR_REFRESH_DEGRADED
+1
View File
@@ -108,6 +108,7 @@ export function createLifecycle({
layerState._loading = false;
layerState._stale = false;
layerState._degraded = false;
layerState._rejectedStationCount = 0;
layerState._error = null;
layerState._updatedAt = null;
layerState._managerLifecyclePresentation = null;
+4
View File
@@ -24,6 +24,10 @@ export const RADIO_DIRECTORY_STALE_MS = 7 * 24 * 60 * 60 * 1000;
export const RADIO_DIRECTORY_FUTURE_SKEW_MS = 5 * 60 * 1000;
// Bound for the server's `rejectedStationCount` envelope field and the
// combined client counter; matches server/providers/radio/constants.js.
export const RADIO_REJECTED_STATION_COUNT_MAX = 10_000;
export const RADIO_UUID_RE =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
+1
View File
@@ -20,6 +20,7 @@ export function createPresentation({
loading: layerState._loading,
stale: layerState._stale,
degraded: layerState._degraded,
rejectedStationCount: layerState._rejectedStationCount,
error: layerState._error,
updatedAt: layerState._updatedAt,
filter: layerState._filter,
+1
View File
@@ -25,6 +25,7 @@ export function createState({ services }) {
layerState._stale = false;
layerState._degraded = false;
layerState._rejectedStationCount = 0;
layerState._error = null;
+28 -1
View File
@@ -20,6 +20,23 @@ export function createAnimation({
parts,
source,
}) {
function syncRenderDemand() {
const needed = Boolean(
layerState._enabled &&
(layerState._dots.length || layerState._heatJamPrim),
);
// Height easing only runs in a held frame. Settle it before releasing, so
// a population emptied mid-correction cannot report loading forever.
if (!needed && layerState._heightPending) parts.retention.finishHeights();
if (needed === Boolean(layerState._renderHeld)) return;
layerState._renderHeld = needed;
if (needed) {
layerState._lastAnimTime = 0;
services.render.holdContinuousRender('traffic');
layerState._viewer?.scene?.requestRender?.();
} else services.render.releaseContinuousRender('traffic');
}
/**
* Spawn animated dot primitives along a single road.
*
@@ -188,6 +205,7 @@ export function createAnimation({
: null,
});
}
syncRenderDemand();
}
/**
@@ -206,6 +224,8 @@ export function createAnimation({
*/
function animate() {
syncRenderDemand();
if (!layerState._renderHeld) return;
const now = Date.now();
// Delta time in seconds, capped to avoid jumps when returning from background tab
const dt = layerState._lastAnimTime
@@ -339,6 +359,13 @@ export function createAnimation({
layerState._count = 0;
layerState._bucketCounts = { free: 0, slow: 0, jam: 0, sim: 0 };
layerState._closedRoads = 0;
syncRenderDemand();
}
return { spawnDotsForRoad, animate, maybeStopLight, clearDots };
return {
spawnDotsForRoad,
animate,
maybeStopLight,
clearDots,
syncRenderDemand,
};
}
+2 -3
View File
@@ -12,7 +12,6 @@ export function createLifecycle({
parts,
source,
}) {
const { holdContinuousRender, releaseContinuousRender } = services.render;
const { resetFlowTileCache } = source;
const methods = {
@@ -81,7 +80,7 @@ export function createLifecycle({
layerState._enabled = true;
layerState._surfaceFrameRemover = observeTrafficSurface(viewer.scene);
if (layerState._roadMode !== 'tomtom') source.prefetch?.();
holdContinuousRender('traffic'); // per-frame animator (perf wave 2)
parts.animation.syncRenderDemand();
layerState._lastAnimTime = 0;
layerState._pointCollection.show = true;
@@ -142,7 +141,7 @@ export function createLifecycle({
disable(viewer) {
layerState._enabled = false;
services.credits?.hideOsmCredit?.(layerState._viewer, 'traffic');
releaseContinuousRender('traffic');
parts.animation.syncRenderDemand();
clearTimeout(layerState._fetchTimeout);
clearInterval(layerState._enableKickTimer);
layerState._enableKickTimer = null;
+39 -1
View File
@@ -37,7 +37,14 @@ function setup(t) {
visibleRoadsForAltitude: (roads) => roads,
},
};
const ctx = { state, services: {}, parts, source: {} };
const ctx = {
state,
services: {
render: { holdContinuousRender() {}, releaseContinuousRender() {} },
},
parts,
source: {},
};
parts.style = createStyle(ctx);
parts.model = createModel(ctx);
parts.animation = createAnimation(ctx);
@@ -558,6 +565,37 @@ test('provisional opposite-direction heights cannot overwrite a measured sibling
assert.notEqual(forward.waypoints, reverse.waypoints);
});
test('a closure that empties the population mid-correction settles heights and clears loading before release', (t) => {
const h = setup(t);
h.parts.retention.reconcile(h.roads(), 450, true);
h.settle();
assert.equal(h.state._renderHeld, true);
const road = h.state._roads[0];
// Republishing the sole road starts a height correction.
const corrected = h.roads(0, 120);
h.parts.retention.reconcile(corrected, 450, true);
assert.equal(h.state._heightPending, true);
assert.equal(road.heightMoving, true);
// The closure arrives before the next population pass, which then removes
// the final points after their fade, long before the correction finishes.
corrected[0].flow = { level: 0, closure: true };
for (let i = 0; i < 20 && h.state._dots.length; i++) h.frame();
assert.equal(h.state._dots.length, 0);
assert.equal(h.state._heatJamPrim, null);
assert.equal(h.state._renderHeld, false);
assert.equal(h.state._heightPending, false);
assert.equal(h.state._populationPending, false);
assert.equal(road.heightMoving, false);
assert.ok(
Math.abs(C.Cartographic.fromCartesian(road.waypoints[0]).height - 120) <
0.01,
);
// Frames driven by other layers stay idle and cannot revive the work.
h.frame();
assert.equal(h.state._heightPending, false);
assert.equal(h.state._renderHeld, false);
});
test('large height corrections obey a screen-pixel bound and reuse projection scratch objects', (t) => {
const h = setup(t);
h.state._viewer.scene = {
+45
View File
@@ -0,0 +1,45 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { createAnimation } from './animation.js';
test('traffic render demand follows animated population and jam lines across empty, disable and re-enable', () => {
const calls = [];
const state = { _enabled: true, _dots: [], _motion: { rebuilds: 0 } };
const animation = createAnimation({
state,
services: {
render: {
holdContinuousRender: (id) => calls.push(['hold', id]),
releaseContinuousRender: (id) => calls.push(['release', id]),
},
},
parts: {
retention: { clear() {} },
rendering: {
removeHeatLines() {
state._heatJamPrim = null;
},
},
},
});
animation.syncRenderDemand();
assert.deepEqual(calls, []);
state._dots.push({});
animation.syncRenderDemand();
animation.syncRenderDemand();
assert.deepEqual(calls, [['hold', 'traffic']]);
animation.clearDots();
assert.deepEqual(calls.at(-1), ['release', 'traffic']);
state._heatJamPrim = {};
animation.syncRenderDemand();
assert.deepEqual(calls.at(-1), ['hold', 'traffic']);
state._enabled = false;
animation.syncRenderDemand();
assert.deepEqual(calls.at(-1), ['release', 'traffic']);
state._enabled = true;
animation.clearDots();
assert.equal(state._renderHeld, false);
state._dots.push({});
animation.syncRenderDemand();
assert.equal(state._renderHeld, true);
});
+2
View File
@@ -50,6 +50,7 @@ export function createRendering({
layerState._heatSlowPrim = null;
}
layerState._heatLineCount = 0;
parts.animation.syncRenderDemand?.();
}
/**
@@ -152,6 +153,7 @@ export function createRendering({
}
layerState._heatLineCount = kept.length;
parts.animation.syncRenderDemand?.();
if (candidates.length > kept.length) {
console.log(
`[Data:Traffic] Heat-lines capped at ${HEAT_LINE_CAP} (${candidates.length} congested roads in view)`,
+25 -1
View File
@@ -125,6 +125,7 @@ export function createRetention({ state, parts }) {
count++;
}
state._count = count;
parts.animation.syncRenderDemand?.();
}
function reconcile(roads, height, replace) {
altitude = height;
@@ -241,6 +242,29 @@ export function createRetention({ state, parts }) {
road.heightMoving = moving;
}
}
/**
* Settle every retained road on its corrected heights at once. Easing only
* runs inside the held animation frame, so once nothing is left to animate
* the remaining correction would otherwise stay pending indefinitely.
* Published roads whose record was just dropped are settled too, so a later
* held frame cannot revive their correction.
*/
function finishHeights() {
const roads = new Set(state._roads);
for (const record of records.values()) roads.add(record.road);
for (const road of roads) {
if (!road.heightMoving) continue;
for (let i = 0; i < road.waypoints.length; i++)
Cesium.Cartesian3.clone(road.targetWaypoints[i], road.waypoints[i]);
for (let i = 0; i < road.segmentDist.length; i++)
road.segmentDist[i] = Cesium.Cartesian3.distance(
road.waypoints[i],
road.waypoints[i + 1],
);
road.heightMoving = false;
}
state._heightPending = false;
}
function clear() {
if (timer !== null) {
if (typeof cancelAnimationFrame === 'function')
@@ -252,5 +276,5 @@ export function createRetention({ state, parts }) {
state._populationPending = false;
state._heightPending = false;
}
return { reconcile, easeHeights, clear, refreshCounts };
return { reconcile, easeHeights, finishHeights, clear, refreshCounts };
}
+1 -1
View File
@@ -3574,7 +3574,7 @@ test('actual Traffic, Bikeshare and Transit lifecycles retain sensitivity in eve
source: {},
parts: {
flow: { ensureFlowStatus: noop },
animation: { animate: noop, clearDots: noop },
animation: { animate: noop, clearDots: noop, syncRenderDemand: noop },
viewport: { onCameraChanged: noop },
ingestion: { cancelActiveFetch: noop },
},
+56 -1
View File
@@ -1,6 +1,10 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { MACOS_CHROME, chromeExecutablePath } from '../scripts/qa-chrome.mjs';
import {
MACOS_CHROME,
chromeExecutablePath,
captureQaScreenshot,
} from '../scripts/qa-chrome.mjs';
test('gates keep macOS Chrome where it is installed', () => {
assert.equal(
@@ -25,3 +29,54 @@ test('an explicit executable path wins', () => {
'C:\\Chrome\\chrome.exe',
);
});
test('screenshots restore minimized test windows before capture and detach their session', async () => {
const calls = [];
const session = {
send: async (method, params) => {
calls.push([method, params]);
return { windowId: 7, bounds: { windowState: 'minimized' } };
},
detach: async () => calls.push(['detach']),
};
const page = {
createCDPSession: async () => session,
bringToFront: async () => calls.push(['front']),
screenshot: async (options) => {
calls.push(['capture', options]);
return 'pixels';
},
};
assert.equal(
await captureQaScreenshot(page, { path: 'proof.png' }),
'pixels',
);
assert.deepEqual(
calls.map((c) => c[0]),
[
'Browser.getWindowForTarget',
'Browser.setWindowBounds',
'front',
'detach',
'capture',
],
);
assert.deepEqual(calls[1][1], {
windowId: 7,
bounds: { windowState: 'normal' },
});
});
test('headless capture does not manipulate a native window', async () => {
const options = { path: 'proof.png' };
const page = {
screenshot: async (supplied) => {
assert.equal(supplied, options);
return 'pixels';
},
};
assert.equal(
await captureQaScreenshot(page, options, { headful: false }),
'pixels',
);
});
+11 -7
View File
@@ -11,6 +11,16 @@ export function cleanRadioText(value, maxLength) {
.trim();
}
/** Normalize a station/filter tag and cap its final, case-folded representation. */
export function normalizeRadioTag(value) {
return cleanRadioText(
String(value ?? '')
.toLowerCase()
.replace(/[_-]+/g, ' '),
80,
);
}
export function isNonGlobalIpv4(hostname) {
const pieces = hostname.split('.');
if (pieces.length !== 4 || pieces.some((piece) => !/^\d{1,3}$/.test(piece)))
@@ -99,13 +109,7 @@ export function normalizeRadioBrowserStation(
if (!name) return null;
const tags = String(raw?.tags ?? '')
.split(',')
.map((tag) =>
cleanRadioText(tag, 80)
.toLocaleLowerCase()
.replace(/[_-]+/g, ' ')
.replace(/\s+/g, ' ')
.trim(),
)
.map(normalizeRadioTag)
.filter(Boolean)
.filter((tag, index, all) => all.indexOf(tag) === index)
.slice(0, 24);
+170
View File
@@ -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);
});