mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 07:42:23 +00:00
Recent community wildlife observations, as a new Wildlife group in the layer panel. - Server: keyless iNaturalist proxy (v2 observations with only the projected fields; undocumented v1 UTFGrid density tiles, decoded on the server) sharing one paced queue (burst 4, ~1/s, 60/min, 8,000/day budget, up to four in flight), in-memory caches and upstream-only cooldowns; Acartia Salish Sea marine-mammal feed rounded to 0.01 degrees with comments dropped. - Layer: grid counts drawn as a soft view-ranked density glow; real sampled sightings as taxon badges at their true positions with ambient photo thumbnails and labels; obscured records stay privacy cells; a clicked sighting stays pinned through camera moves and refreshes. - Anchored photo card per selection with the single iNaturalist link; attribution in the layer row and the Data attribution popover. - Filters (CONFIRMED, taxa presets) round-trip in share links with token 9. Co-Authored-By: Claude Opus 5.5 <[email protected]>
375 lines
12 KiB
JavaScript
375 lines
12 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { projectInatGrid } from './grid.js';
|
|
import { syntheticGrid } from './grid.fixture.mjs';
|
|
import { installInatProxy, NOW } from './proxy.fixture.mjs';
|
|
import { createInatClient } from './client.js';
|
|
|
|
const tile = { z: 2, x: 1, y: 1 };
|
|
const gridUrl = '/grid?z=2&x=1&y=1&taxa=Aves&quality=research';
|
|
const install = (options, hook) => installInatProxy(gridUrl, options, hook);
|
|
|
|
test('UTFGrid projection validates the full shape and counts each logical occupied cell once', () => {
|
|
const raw = syntheticGrid();
|
|
const projected = projectInatGrid(raw, tile);
|
|
assert.equal(raw.grid.length, 64);
|
|
assert.ok(raw.grid.every((row) => row.length === 64));
|
|
assert.equal(
|
|
raw.grid.join('').split('!').length - 1,
|
|
5,
|
|
'one key is repeated by its hit area',
|
|
);
|
|
assert.equal(projected.cells.length, 2);
|
|
assert.equal(projected.totalResults, 18);
|
|
assert.deepEqual(projected.cells[0], {
|
|
id: 'grid:2/1/1:1:51',
|
|
cellCount: 6,
|
|
lat: raw.data[raw.keys[1]].latitude,
|
|
lon: raw.data[raw.keys[1]].longitude,
|
|
});
|
|
for (const cell of projected.cells)
|
|
assert.deepEqual(Object.keys(cell).sort(), [
|
|
'cellCount',
|
|
'id',
|
|
'lat',
|
|
'lon',
|
|
]);
|
|
for (const privateField of [
|
|
'900000000',
|
|
'uuid',
|
|
'geoprivacy',
|
|
'private_location',
|
|
'captive',
|
|
'quality_grade',
|
|
])
|
|
assert.ok(!JSON.stringify(projected).includes(privateField));
|
|
});
|
|
|
|
test('legitimate blank grid and null/unknown iconic ids remain safe', () => {
|
|
assert.deepEqual(projectInatGrid(syntheticGrid({ cells: [] }), tile), {
|
|
cells: [],
|
|
totalResults: 0,
|
|
});
|
|
const raw = syntheticGrid({
|
|
cells: [{ row: 1, column: 1, count: 1, iconic: null }],
|
|
});
|
|
const projected = projectInatGrid(raw, tile);
|
|
assert.equal(projected.cells[0].cellCount, 1);
|
|
raw.data[raw.keys[1]]['taxon.iconic_taxon_id'] = 999999;
|
|
assert.deepEqual(projectInatGrid(raw, tile), projected);
|
|
});
|
|
|
|
test('schema drift anywhere fails closed, including unused, malformed and orphan cells', () => {
|
|
const changes = [
|
|
(raw) => {
|
|
raw.grid.pop();
|
|
},
|
|
(raw) => {
|
|
raw.grid[0] = '';
|
|
},
|
|
(raw) => {
|
|
raw.grid[0] = '~'.repeat(64);
|
|
},
|
|
(raw) => {
|
|
raw.keys[0] = 'not-empty';
|
|
},
|
|
(raw) => {
|
|
raw.keys.push(raw.keys[1]);
|
|
},
|
|
(raw) => {
|
|
delete raw.data[raw.keys[1]];
|
|
},
|
|
(raw) => {
|
|
raw.data.extra = {};
|
|
},
|
|
(raw) => {
|
|
raw.data[raw.keys[1]].cellCount = -1;
|
|
},
|
|
(raw) => {
|
|
raw.data[raw.keys[1]].latitude = '47';
|
|
},
|
|
(raw) => {
|
|
raw.data[raw.keys[1]].longitude = 181;
|
|
},
|
|
(raw) => {
|
|
raw.data[raw.keys[1]].quality_grade = 'new-grade';
|
|
},
|
|
(raw) => {
|
|
delete raw.data[raw.keys[1]]['taxon.iconic_taxon_id'];
|
|
},
|
|
(raw) => {
|
|
raw.data[raw.keys[1]]['taxon.iconic_taxon_id'] = 'Aves';
|
|
},
|
|
(raw) => {
|
|
raw.grid = Array(64).fill(' '.repeat(64));
|
|
},
|
|
];
|
|
for (const change of changes) {
|
|
const raw = syntheticGrid();
|
|
change(raw);
|
|
assert.throws(
|
|
() => projectInatGrid(raw, tile),
|
|
/Invalid iNaturalist UTFGrid/,
|
|
);
|
|
}
|
|
});
|
|
|
|
for (const hook of ['configureServer', 'configurePreviewServer']) {
|
|
test(`${hook}: fixed grid URL shares filters, caps and allow-list cache`, async () => {
|
|
const calls = [];
|
|
const cache = new Map();
|
|
const request = install(
|
|
{
|
|
cache,
|
|
fetchImpl: async (url, options) => {
|
|
calls.push(new URL(url));
|
|
assert.equal(options.redirect, 'error');
|
|
assert.ok(options.signal instanceof AbortSignal);
|
|
assert.match(
|
|
options.headers['User-Agent'],
|
|
/^gods-eye-view-wildlife-sightings-proxy/,
|
|
);
|
|
return Response.json(syntheticGrid());
|
|
},
|
|
},
|
|
hook,
|
|
);
|
|
assert.equal(calls.length, 0);
|
|
const response = await request(
|
|
`${gridUrl}&url=https://attacker.test&per_page=999`,
|
|
);
|
|
assert.equal(response.status, 200);
|
|
assert.equal(response.body.cells.length, 2);
|
|
assert.equal(response.body.fetchedAt, NOW);
|
|
assert.equal(response.headers['Cache-Control'], 'no-store');
|
|
assert.equal(
|
|
calls[0].origin + calls[0].pathname,
|
|
'https://api.inaturalist.org/v1/grid/2/1/1.grid.json',
|
|
);
|
|
assert.deepEqual(Object.fromEntries(calls[0].searchParams), {
|
|
verifiable: 'true',
|
|
captive: 'false',
|
|
d1: '2026-11-02',
|
|
photos: 'true',
|
|
quality_grade: 'research',
|
|
iconic_taxa: 'Aves',
|
|
});
|
|
await request(gridUrl);
|
|
assert.equal(calls.length, 1, 'fresh stable tile/filter key is cached');
|
|
assert.ok(!JSON.stringify([...cache]).includes('private_location'));
|
|
assert.ok(!JSON.stringify([...cache]).includes('900000000'));
|
|
const whale = await request('/grid?z=2&x=1&y=1&taxa=whales&confirmed=true');
|
|
assert.equal(whale.status, 200);
|
|
assert.equal(calls[1].searchParams.get('taxon_id'), '152871');
|
|
assert.equal(calls[1].searchParams.get('d1'), '2026-10-28');
|
|
assert.equal(calls[1].searchParams.has('photos'), false);
|
|
});
|
|
}
|
|
|
|
test('grid admission rejects cross-site/method/path/tile before acquisition', async () => {
|
|
const request = install({
|
|
fetchImpl() {
|
|
assert.fail('invalid grid request acquired');
|
|
},
|
|
});
|
|
assert.equal(
|
|
(
|
|
await request(gridUrl, {
|
|
headers: {
|
|
host: 'localhost:5173',
|
|
origin: 'https://other.test',
|
|
'sec-fetch-site': 'cross-site',
|
|
},
|
|
})
|
|
).status,
|
|
403,
|
|
);
|
|
assert.equal((await request('/grid', { method: 'POST' })).status, 405);
|
|
assert.equal((await request('/grid/extra')).status, 404);
|
|
for (const query of [
|
|
'',
|
|
'z=2&x=4&y=0',
|
|
'z=-1&x=0&y=0',
|
|
'z=23&x=0&y=0',
|
|
'z=2&x=1.5&y=0',
|
|
'z=2&x=&y=0',
|
|
'z=2&x=0&y=4',
|
|
]) {
|
|
const result = await request(`/grid?${query}`);
|
|
assert.equal(result.status, 400, query);
|
|
assert.equal(result.body.error, 'invalid_tile');
|
|
}
|
|
});
|
|
|
|
test('grid refresh single-flight, stale fallback and hour-key expiry keep the original age', async () => {
|
|
let time = NOW;
|
|
let calls = 0;
|
|
let resolve;
|
|
let mode = 'pending';
|
|
const request = install({
|
|
now: () => time,
|
|
fetchImpl: async () => {
|
|
calls += 1;
|
|
if (mode === 'pending')
|
|
return new Promise((done) => {
|
|
resolve = done;
|
|
});
|
|
if (mode === 'bad') return Response.json({ grid: [] });
|
|
return Response.json(syntheticGrid());
|
|
},
|
|
});
|
|
const first = request();
|
|
const second = request();
|
|
for (let i = 0; i < 100 && !resolve; i += 1) await new Promise(setImmediate);
|
|
assert.equal(calls, 1);
|
|
resolve(Response.json(syntheticGrid()));
|
|
assert.equal((await first).status, 200);
|
|
assert.equal((await second).status, 200);
|
|
time += 900_001;
|
|
mode = 'bad';
|
|
const stale = await request();
|
|
assert.equal(stale.status, 200);
|
|
assert.equal(stale.body.stale, true);
|
|
assert.equal(stale.body.fetchedAt, NOW);
|
|
assert.equal(stale.headers['X-Data-Stale'], 'true');
|
|
time = NOW + 3_600_001;
|
|
const expired = await request();
|
|
assert.equal(expired.status, 502);
|
|
assert.equal(expired.body.error, 'inaturalist_unavailable');
|
|
});
|
|
|
|
test('grid and observations share the upstream cooldown and daily reservations', async () => {
|
|
let calls = 0;
|
|
const client = createInatClient({
|
|
now: () => NOW,
|
|
cacheDir: null,
|
|
queueOptions: { intervalMs: 0 },
|
|
warn() {},
|
|
fetchImpl: async () => {
|
|
calls += 1;
|
|
return new Response('', {
|
|
status: 429,
|
|
headers: { 'Retry-After': '120' },
|
|
});
|
|
},
|
|
});
|
|
await assert.rejects(
|
|
client.getGrid({ ...tile, taxa: 'all', observedD1: '2026-11-02' }),
|
|
(error) => error.status === 429 && error.retryAfterS === 120,
|
|
);
|
|
await assert.rejects(
|
|
client.getObservations({
|
|
scope: 'global',
|
|
taxa: 'all',
|
|
observedD1: '2026-11-02',
|
|
}),
|
|
(error) => error.status === 429,
|
|
);
|
|
assert.equal(calls, 1);
|
|
assert.equal(client.budgetState().count, 1);
|
|
});
|
|
|
|
test('grid body cap and upstream failures return fixed errors; only real 5xx cools down', async () => {
|
|
for (const [response, retryAfter] of [
|
|
[() => new Response('x'.repeat(8 * 1024 * 1024 + 1)), undefined],
|
|
[() => new Response('', { status: 503 }), '15'],
|
|
]) {
|
|
const request = install({ fetchImpl: async () => response() });
|
|
const result = await request();
|
|
assert.equal(result.status, 502);
|
|
assert.deepEqual(result.body, { error: 'inaturalist_unavailable' });
|
|
assert.equal(result.headers['Retry-After'], retryAfter);
|
|
}
|
|
});
|
|
|
|
test('a failed tile preserves last good cells and cannot send unrelated observations into 502', async () => {
|
|
let at = NOW;
|
|
let failGrid = false;
|
|
const calls = [];
|
|
const warnings = [];
|
|
const request = install({
|
|
now: () => at,
|
|
warn: (message) => warnings.push(message),
|
|
fetchImpl: async (url) => {
|
|
const isGrid = new URL(url).pathname.includes('/grid/');
|
|
calls.push(isGrid ? 'grid' : 'observations');
|
|
if (isGrid && failGrid)
|
|
return new Response('private upstream text', {
|
|
status: 503,
|
|
headers: { 'Retry-After': '45' },
|
|
});
|
|
return Response.json(
|
|
isGrid ? syntheticGrid() : { results: [], total_results: 0 },
|
|
);
|
|
},
|
|
});
|
|
const first = await request();
|
|
at += 900_001;
|
|
failGrid = true;
|
|
const failed = await request();
|
|
assert.equal(failed.status, 200);
|
|
assert.equal(failed.headers['X-Data-Stale'], 'true');
|
|
assert.equal(failed.body.stale, true);
|
|
assert.equal(failed.body.fetchedAt, first.body.fetchedAt);
|
|
assert.deepEqual(failed.body.cells, first.body.cells);
|
|
assert.equal(
|
|
(await request('/observations?scope=global&taxa=whales')).status,
|
|
200,
|
|
);
|
|
assert.deepEqual(calls, ['grid', 'grid', 'observations']);
|
|
assert.match(warnings[0], /resource=grid status=503 reason=http/);
|
|
assert.doesNotMatch(warnings[0], /private upstream text/);
|
|
// The tile cooldown also preserves its data without another upstream try.
|
|
const cooling = await request();
|
|
assert.equal(cooling.body.stale, true);
|
|
assert.equal(cooling.headers['Retry-After'], '45');
|
|
assert.equal(cooling.headers['X-Upstream-Status'], '503');
|
|
assert.equal(calls.length, 3);
|
|
at += 45_000;
|
|
failGrid = false;
|
|
const refreshing = await request();
|
|
assert.equal(refreshing.body.stale, true);
|
|
await new Promise(setImmediate);
|
|
const recovered = await request();
|
|
assert.equal(recovered.status, 200);
|
|
assert.equal(recovered.body.stale, undefined);
|
|
assert.equal(
|
|
calls.length,
|
|
4,
|
|
'a failed queue task does not poison later tile work',
|
|
);
|
|
});
|
|
|
|
test('twelve queued tiles leave observation capacity and give the next dispatch to observations', async () => {
|
|
const calls = [];
|
|
let release;
|
|
const request = install({
|
|
fetchImpl: (url) => {
|
|
const path = new URL(url).pathname;
|
|
calls.push(path);
|
|
if (calls.length === 1)
|
|
return new Promise((resolve) => {
|
|
release = resolve;
|
|
});
|
|
return Response.json(
|
|
path.includes('/grid/')
|
|
? syntheticGrid()
|
|
: { results: [], total_results: 0 },
|
|
);
|
|
},
|
|
});
|
|
const first = request('/grid?z=4&x=0&y=1');
|
|
for (let i = 0; i < 100 && !release; i += 1) await new Promise(setImmediate);
|
|
assert.ok(release);
|
|
const tiles = Array.from({ length: 11 }, (_, i) =>
|
|
request(`/grid?z=4&x=${i + 1}&y=1`),
|
|
);
|
|
const observation = request('/observations?scope=global');
|
|
const queuedTile = request('/grid?z=4&x=12&y=1');
|
|
release(Response.json(syntheticGrid()));
|
|
const results = await Promise.all([first, observation, ...tiles, queuedTile]);
|
|
assert.ok(results.every((result) => result.status === 200));
|
|
assert.equal(calls.length, 14);
|
|
assert.equal(calls[1], '/v2/observations');
|
|
});
|