mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
- Add A22_IMAGE_ORIGIN to the pinned frame directories, so a same-host redirect from www.autobrennero.it/WebCamImg/ cannot leave that directory, as for the other directory-pinned packs. - Give Autostrada del Brennero a 3-minute ambient refresh in cctvLod.js, like the other highway packs, instead of the 5-minute default. - DATA_SOURCES: the Open Data Hub now tags every A22 record CC0; note that this is the aggregator's metadata, not Autobrennero's own terms. - Tests for the redirect pin and the refresh cadence.
227 lines
7.2 KiB
JavaScript
227 lines
7.2 KiB
JavaScript
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
|
||
import {
|
||
a22WebcamToSource,
|
||
loadA22SourcesFromOpenDataHub,
|
||
} from '../../server/providers/cctv/sources.js';
|
||
import {
|
||
A22_DEFAULT_GROUND_ELEVATION_M,
|
||
A22_IMAGE_ORIGIN,
|
||
DEFAULT_A22_CCTV_URL,
|
||
} from '../../server/providers/cctv/constants.js';
|
||
import { createCctvCatalog } from '../../server/providers/cctv/catalog.js';
|
||
import { fetchWithinHost } from '../../server/providers/cctv/media.js';
|
||
import { staticFrameRefreshMs } from './cctvLod.js';
|
||
|
||
/** One Open Data Hub `WebcamInfo` item, shaped like the live a22 payload. */
|
||
const item = (
|
||
patch = {},
|
||
{ km = '68', lat = 46.53591, lon = 11.49772 } = {},
|
||
) => ({
|
||
Id: `A22_${km}`,
|
||
Active: true,
|
||
Source: 'a22',
|
||
Shortname: 'ADS Sciliar',
|
||
Webcamname: { it: 'ADS Sciliar' },
|
||
Webcamurl: `https://www.autobrennero.it/WebCamImg/km${km}.jpg`,
|
||
Mapping: { a22: { km } },
|
||
GpsInfo: [{ Gpstype: 'position', Latitude: lat, Longitude: lon }],
|
||
LicenseInfo: {
|
||
ClosedData: false,
|
||
LicenseHolder: 'https://www.autobrennero.it/',
|
||
},
|
||
...patch,
|
||
});
|
||
|
||
test('an A22 webcam maps to a source on the pinned operator frame', () => {
|
||
const source = a22WebcamToSource(item());
|
||
assert.equal(source.id, 'it-a22-km68');
|
||
assert.equal(source.name, 'A22 ADS Sciliar (km 68)');
|
||
assert.equal(source.city, 'A22 Brennero');
|
||
assert.equal(source.cityId, 'a22');
|
||
assert.equal(source.provider, 'Autostrada del Brennero');
|
||
assert.equal(source.lat, 46.53591);
|
||
assert.equal(source.lon, 11.49772);
|
||
assert.equal(source.headingConfidence, 'low');
|
||
assert.equal(source.feedType, 'image');
|
||
assert.equal(source.sourceKind, 'a22-opendatahub');
|
||
assert.equal(source.code, 'ADS SCILIAR');
|
||
assert.equal(source.url, `${A22_IMAGE_ORIGIN}km68.jpg`);
|
||
assert.equal(source.snapshotUrl, source.url);
|
||
});
|
||
|
||
test('ground elevation follows the motorway from the Brenner Pass to the Po', () => {
|
||
const brenner = a22WebcamToSource(
|
||
item({ Shortname: 'Brennero' }, { km: '1', lat: 46.99682, lon: 11.50218 }),
|
||
);
|
||
const ponte = a22WebcamToSource(
|
||
item(
|
||
{ Shortname: 'Ponte Po' },
|
||
{ km: '272', lat: 45.03145, lon: 10.85369 },
|
||
),
|
||
);
|
||
assert.ok(brenner.groundElevationM > 1300);
|
||
assert.ok(ponte.groundElevationM < 50);
|
||
// A kilometre the table does not know falls back to the corridor prior.
|
||
const unknown = a22WebcamToSource(
|
||
item({}, { km: '150', lat: 45.95, lon: 11.05 }),
|
||
);
|
||
assert.equal(unknown.groundElevationM, A22_DEFAULT_GROUND_ELEVATION_M);
|
||
});
|
||
|
||
test('inactive cameras, off-host frames and bad geometry are dropped', () => {
|
||
assert.equal(a22WebcamToSource(item({ Active: false })), null);
|
||
assert.equal(a22WebcamToSource(item({ Source: 'other' })), null);
|
||
assert.equal(
|
||
a22WebcamToSource(
|
||
item({ Webcamurl: 'https://evil.test/WebCamImg/km68.jpg' }),
|
||
),
|
||
null,
|
||
);
|
||
// The frame must be exactly the camera's own kilometre image.
|
||
assert.equal(
|
||
a22WebcamToSource(
|
||
item({ Webcamurl: 'https://www.autobrennero.it/WebCamImg/km1.jpg' }),
|
||
),
|
||
null,
|
||
);
|
||
assert.equal(
|
||
a22WebcamToSource(item({ Mapping: { a22: { km: '../x' } } })),
|
||
null,
|
||
);
|
||
// Milan: plausible, but off the Brenner–Modena corridor.
|
||
assert.equal(
|
||
a22WebcamToSource(item({}, { km: '68', lat: 45.46, lon: 9.19 })),
|
||
null,
|
||
);
|
||
assert.equal(a22WebcamToSource(item({ GpsInfo: [] })), null);
|
||
assert.equal(a22WebcamToSource(null), null);
|
||
});
|
||
|
||
test('the loader dedupes, orders north to south, and degrades to empty', async (t) => {
|
||
t.mock.method(console, 'log', () => {});
|
||
t.mock.method(console, 'warn', () => {});
|
||
const requested = [];
|
||
const fetchMock = t.mock.method(globalThis, 'fetch', async (url) => {
|
||
requested.push(String(url));
|
||
return Response.json({
|
||
TotalResults: 3,
|
||
Items: [
|
||
item(
|
||
{ Shortname: 'Ponte Po' },
|
||
{ km: '272', lat: 45.03145, lon: 10.85369 },
|
||
),
|
||
item(),
|
||
item(),
|
||
item(
|
||
{ Shortname: 'Brennero' },
|
||
{ km: '1', lat: 46.99682, lon: 11.50218 },
|
||
),
|
||
],
|
||
});
|
||
});
|
||
const cameras = await loadA22SourcesFromOpenDataHub();
|
||
assert.deepEqual(requested, [DEFAULT_A22_CCTV_URL]);
|
||
assert.deepEqual(
|
||
cameras.map((c) => c.id),
|
||
['it-a22-km1', 'it-a22-km68', 'it-a22-km272'],
|
||
);
|
||
|
||
fetchMock.mock.mockImplementation(
|
||
async () => new Response('nope', { status: 503 }),
|
||
);
|
||
assert.deepEqual(await loadA22SourcesFromOpenDataHub(), []);
|
||
fetchMock.mock.mockImplementation(
|
||
async () =>
|
||
new Response(null, {
|
||
status: 302,
|
||
headers: { Location: 'https://evil.test/' },
|
||
}),
|
||
);
|
||
assert.deepEqual(await loadA22SourcesFromOpenDataHub(), []);
|
||
fetchMock.mock.mockImplementation(async () => {
|
||
throw new Error('offline');
|
||
});
|
||
assert.deepEqual(await loadA22SourcesFromOpenDataHub(), []);
|
||
});
|
||
|
||
const runCatalog = async (t) => {
|
||
const requested = [];
|
||
t.mock.method(console, 'log', () => {});
|
||
t.mock.method(console, 'warn', () => {});
|
||
t.mock.method(globalThis, 'fetch', async (url) => {
|
||
const href = String(url);
|
||
requested.push(href);
|
||
if (href === DEFAULT_A22_CCTV_URL) {
|
||
return Response.json({ Items: [item()] });
|
||
}
|
||
return Response.json([]);
|
||
});
|
||
const sources = await createCctvCatalog({ sourceRoot: '/nonexistent' })();
|
||
return { requested, sources };
|
||
};
|
||
|
||
const withEnv = async (patch, fn) => {
|
||
const saved = { ...process.env };
|
||
try {
|
||
delete process.env.CCTV_SOURCES_FILE;
|
||
delete process.env.CCTV_SOURCES_JSON;
|
||
delete process.env.CCTV_A22_ENABLED;
|
||
Object.assign(process.env, patch);
|
||
await fn();
|
||
} finally {
|
||
for (const key of Object.keys(process.env)) {
|
||
if (!(key in saved)) delete process.env[key];
|
||
}
|
||
Object.assign(process.env, saved);
|
||
}
|
||
};
|
||
|
||
test('the A22 lane is wired into the catalog', async (t) => {
|
||
await withEnv({}, async () => {
|
||
const { requested, sources } = await runCatalog(t);
|
||
assert.ok(requested.includes(DEFAULT_A22_CCTV_URL));
|
||
assert.deepEqual(
|
||
sources.filter((s) => s.cityId === 'a22').map((s) => s.id),
|
||
['it-a22-km68'],
|
||
);
|
||
});
|
||
});
|
||
|
||
test('CCTV_A22_ENABLED=0 keeps the lane from being loaded', async (t) => {
|
||
await withEnv({ CCTV_A22_ENABLED: '0' }, async () => {
|
||
const { requested, sources } = await runCatalog(t);
|
||
assert.equal(requested.includes(DEFAULT_A22_CCTV_URL), false);
|
||
assert.deepEqual(
|
||
sources.filter((s) => s.cityId === 'a22'),
|
||
[],
|
||
);
|
||
});
|
||
});
|
||
|
||
test('a redirect on the A22 frame host stays inside WebCamImg/', async () => {
|
||
const frame = `${A22_IMAGE_ORIGIN}km68.jpg`;
|
||
const redirectTo = (location) => async (url) =>
|
||
url === frame
|
||
? new Response(null, { status: 302, headers: { location } })
|
||
: new Response('frame', { headers: { 'content-type': 'image/jpeg' } });
|
||
for (const location of ['/it/area-riservata/', '/WebCamImg/../x.jpg'])
|
||
assert.equal(
|
||
await fetchWithinHost(frame, {}, redirectTo(location)),
|
||
null,
|
||
location,
|
||
);
|
||
const followed = await fetchWithinHost(
|
||
frame,
|
||
{},
|
||
redirectTo('/WebCamImg/km77.jpg'),
|
||
);
|
||
assert.equal(await followed.text(), 'frame');
|
||
});
|
||
|
||
test('A22 ambient cards refresh on the highway cadence, not the default', () => {
|
||
const source = a22WebcamToSource(item());
|
||
assert.equal(staticFrameRefreshMs(source), 3 * 60 * 1000);
|
||
});
|