mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
`createVectorTileSource` took `tileJsonUrl` and `allowedOrigin` as independent options, and `createOpenFreeMapSource` hardcoded both to the default host. A caller that repointed only `tileJsonUrl` — the obvious single change — kept the default `allowedOrigin`, so the TileJSON's absolute upstream `tiles[0]` passed the origin check and every tile was fetched from the default host instead. No error, no warning, and nothing reached the configured mirror. `allowedOrigin` now defaults to the origin of `tileJsonUrl`, so repointing one option is enough, and a mirror that serves the default host's TileJSON verbatim fails with the existing `Invalid vector tile origin` instead of silently bypassing itself. The origin check is unchanged: a TileJSON still cannot point the client at a host the caller did not configure. All three existing callers pass `allowedOrigin` explicitly (`openFreeMap.js`, `alpr/source.js`, `traffic/flowSource.js`), and an explicit value still overrides the derived one, so behaviour is unchanged for every current call site.
248 lines
7.9 KiB
JavaScript
248 lines
7.9 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { createVectorTileSource } from './vectorTiles.js';
|
|
const meta = { tiles: ['https://tiles.example/{z}/{x}/{y}.pbf'] };
|
|
const box = { south: 30.267, north: 30.268, west: -97.744, east: -97.743 };
|
|
const options = {
|
|
tileJsonUrl: 'https://tiles.example/index.json',
|
|
allowedOrigin: 'https://tiles.example',
|
|
decode: () => [],
|
|
};
|
|
|
|
for (const failure of ['503', 'network', 'timeout'])
|
|
test(`TileJSON ${failure} retries after cooldown and caches only success`, async () => {
|
|
let calls = 0,
|
|
time = 0;
|
|
const source = createVectorTileSource({
|
|
...options,
|
|
now: () => time,
|
|
metadataCooldownMs: 5000,
|
|
fetchImpl: async () => {
|
|
calls++;
|
|
if (calls > 1) return Response.json(meta);
|
|
if (failure === '503') return new Response('', { status: 503 });
|
|
throw failure === 'timeout'
|
|
? new DOMException('timeout', 'TimeoutError')
|
|
: new TypeError('network');
|
|
},
|
|
});
|
|
await assert.rejects(source.getMetadata(), { retryable: true });
|
|
await assert.rejects(source.getMetadata(), { retryable: true });
|
|
assert.equal(calls, 1);
|
|
time = 5001;
|
|
assert.equal((await source.getMetadata()).template, meta.tiles[0]);
|
|
await source.getMetadata();
|
|
assert.equal(calls, 2);
|
|
source.clear();
|
|
await source.getMetadata();
|
|
assert.equal(calls, 3);
|
|
});
|
|
for (const invalid of [
|
|
{ tiles: [] },
|
|
{ tiles: ['https://evil.example/{z}/{x}/{y}'] },
|
|
])
|
|
test('invalid TileJSON is permanent only until clear', async () => {
|
|
let calls = 0;
|
|
const source = createVectorTileSource({
|
|
...options,
|
|
fetchImpl: async () => {
|
|
calls++;
|
|
return Response.json(calls === 1 ? invalid : meta);
|
|
},
|
|
});
|
|
await assert.rejects(source.getMetadata(), { retryable: false });
|
|
await assert.rejects(source.getMetadata(), { retryable: false });
|
|
assert.equal(calls, 1);
|
|
source.clear();
|
|
assert.equal((await source.getMetadata()).template, meta.tiles[0]);
|
|
});
|
|
for (const declared of [true, false])
|
|
test(`oversized ${declared ? 'declared' : 'streaming'} tile cancels body and aborts request before release`, async () => {
|
|
let cancelled = 0,
|
|
signal;
|
|
const source = createVectorTileSource({
|
|
...options,
|
|
template: meta.tiles[0],
|
|
maxResponseBytes: 8,
|
|
fetchImpl: async (_, opts) => {
|
|
signal = opts.signal;
|
|
return new Response(
|
|
new ReadableStream({
|
|
pull(c) {
|
|
c.enqueue(new Uint8Array(16));
|
|
},
|
|
cancel() {
|
|
cancelled++;
|
|
},
|
|
}),
|
|
{ headers: declared ? { 'content-length': '16' } : {} },
|
|
);
|
|
},
|
|
});
|
|
await assert.rejects(source.fetchBounds(box, { zoom: 14 }), {
|
|
code: 'RESPONSE_TOO_LARGE',
|
|
});
|
|
assert.equal(cancelled, 1);
|
|
assert.equal(signal.aborted, true);
|
|
assert.equal(source.getStats().cacheEntries, 0);
|
|
});
|
|
|
|
test('overlapping callers share XYZ; cancelling one keeps the other alive', async () => {
|
|
let releaseBody,
|
|
reads = 0,
|
|
underlying;
|
|
const source = createVectorTileSource({
|
|
...options,
|
|
template: meta.tiles[0],
|
|
fetchImpl: async (_, { signal }) => {
|
|
reads++;
|
|
underlying = signal;
|
|
await new Promise((resolve) => {
|
|
releaseBody = resolve;
|
|
});
|
|
return new Response(new Uint8Array([1]));
|
|
},
|
|
});
|
|
const a = new AbortController(),
|
|
b = new AbortController();
|
|
const first = source.fetchBounds(box, { zoom: 12, signal: a.signal });
|
|
const rejection = assert.rejects(first, { name: 'AbortError' });
|
|
const second = source.fetchBounds(box, { zoom: 12, signal: b.signal });
|
|
while (!releaseBody) await new Promise((resolve) => setTimeout(resolve, 0));
|
|
a.abort();
|
|
await rejection;
|
|
assert.equal(underlying.aborted, false);
|
|
releaseBody();
|
|
assert.equal((await second).tiles.length, 1);
|
|
assert.equal(reads, 1);
|
|
await source.fetchBounds(box, { zoom: 12 });
|
|
assert.equal(reads, 1);
|
|
});
|
|
|
|
test('final subscriber cancellation aborts the owned request; completed tiles publish before a straggler', async () => {
|
|
let owned;
|
|
const source = createVectorTileSource({
|
|
...options,
|
|
template: meta.tiles[0],
|
|
fetchImpl: (_, { signal }) =>
|
|
new Promise((resolve, reject) => {
|
|
owned = signal;
|
|
signal.addEventListener('abort', () => reject(signal.reason));
|
|
}),
|
|
});
|
|
const controller = new AbortController();
|
|
const pending = source.fetchBounds(box, {
|
|
zoom: 12,
|
|
signal: controller.signal,
|
|
});
|
|
const rejection = assert.rejects(pending, { name: 'AbortError' });
|
|
while (!owned) await new Promise((resolve) => setTimeout(resolve, 0));
|
|
controller.abort();
|
|
await rejection;
|
|
assert.equal(owned.aborted, true);
|
|
let release,
|
|
published = 0,
|
|
requests = 0;
|
|
const incremental = createVectorTileSource({
|
|
...options,
|
|
template: meta.tiles[0],
|
|
fetchImpl: async () => {
|
|
if (++requests === 2)
|
|
await new Promise((resolve) => {
|
|
release = resolve;
|
|
});
|
|
return new Response(new Uint8Array([1]));
|
|
},
|
|
});
|
|
const complete = incremental.fetchBounds(
|
|
{ ...box, east: -97.7 },
|
|
{ zoom: 12, onTile: () => published++ },
|
|
);
|
|
while (!published) await new Promise((resolve) => setTimeout(resolve, 0));
|
|
assert.equal(published, 1);
|
|
assert.equal(typeof release, 'function');
|
|
release();
|
|
await complete;
|
|
assert.equal(published, 2);
|
|
});
|
|
|
|
test('polygon-selected tiles keep nearest-first order and cached revisits issue zero requests', async () => {
|
|
const urls = [];
|
|
const source = createVectorTileSource({
|
|
...options,
|
|
template: 'https://tiles.example/{z}/{x}/{y}.pbf',
|
|
tileJsonUrl: undefined,
|
|
fetchImpl: async (url) => {
|
|
urls.push(url);
|
|
return new Response(new Uint8Array([1]));
|
|
},
|
|
});
|
|
const tiles = [
|
|
{ z: 12, x: 935, y: 1685 },
|
|
{ z: 12, x: 936, y: 1685 },
|
|
];
|
|
await source.fetchBounds(box, { zoom: 12, tiles });
|
|
assert.ok(urls[0].endsWith('/12/935/1685.pbf'));
|
|
assert.ok(urls[1].endsWith('/12/936/1685.pbf'));
|
|
await source.fetchBounds(box, { zoom: 12, tiles: [...tiles].reverse() });
|
|
assert.equal(urls.length, 2);
|
|
await assert.rejects(
|
|
source.fetchBounds(box, {
|
|
zoom: 12,
|
|
tiles: Array.from({ length: 17 }, (_, i) => ({
|
|
z: 12,
|
|
x: 935 + i,
|
|
y: 1685,
|
|
})),
|
|
}),
|
|
{ code: 'TILE_VIEW_TOO_WIDE' },
|
|
);
|
|
assert.equal(
|
|
urls.length,
|
|
2,
|
|
'over-budget selection is rejected before fetching',
|
|
);
|
|
});
|
|
|
|
test('a configured tileJsonUrl sets the tile origin, and a foreign tiles[0] is still refused', async () => {
|
|
// Without an explicit allowedOrigin the mirror's own TileJSON is honoured…
|
|
const mirrored = createVectorTileSource({
|
|
tileJsonUrl: 'http://127.0.0.1:12348/planet',
|
|
decode: () => [],
|
|
fetchImpl: async () =>
|
|
Response.json({ tiles: ['http://127.0.0.1:12348/p/{z}/{x}/{y}.pbf'] }),
|
|
});
|
|
assert.equal(
|
|
(await mirrored.getMetadata()).template,
|
|
'http://127.0.0.1:12348/p/{z}/{x}/{y}.pbf',
|
|
);
|
|
|
|
// …while a TileJSON that names a different host is refused rather than
|
|
// silently fetched, which is what the origin check is for.
|
|
const foreign = createVectorTileSource({
|
|
tileJsonUrl: 'http://127.0.0.1:12348/planet',
|
|
decode: () => [],
|
|
fetchImpl: async () =>
|
|
Response.json({
|
|
tiles: ['https://tiles.example/planet/{z}/{x}/{y}.pbf'],
|
|
}),
|
|
});
|
|
await assert.rejects(foreign.getMetadata(), { retryable: false });
|
|
});
|
|
|
|
test('an explicit allowedOrigin still wins over the one derived from tileJsonUrl', async () => {
|
|
const source = createVectorTileSource({
|
|
tileJsonUrl: 'http://127.0.0.1:12348/planet',
|
|
allowedOrigin: 'https://tiles.example',
|
|
decode: () => [],
|
|
fetchImpl: async () =>
|
|
Response.json({
|
|
tiles: ['https://tiles.example/planet/{z}/{x}/{y}.pbf'],
|
|
}),
|
|
});
|
|
assert.equal(
|
|
(await source.getMetadata()).template,
|
|
'https://tiles.example/planet/{z}/{x}/{y}.pbf',
|
|
);
|
|
});
|