fix(cctv): Ontario 511 cameras take the developer key the API now requires

The Ontario 511 camera list answered 400 "Invalid Key" on every catalog load
("[CCTV] Ontario 511 camera download failed: 400"), so the pack loaded
nothing. 511on.ca's developer docs now say the API "Requires a developer
key" (free, from 511on.ca/developers; 10 calls a minute). The loader reads
ONTARIO_511_API_KEY and sends it as `key`; without it the pack is skipped
with one log line instead of a failed request on every load. The key is
never logged. .env.example and DATA_SOURCES no longer call the endpoint
keyless.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
quanticsoul4772andClaude Opus 5.5 committed 2026-10-18 15:40:22 -07:00
1 parent 93ea088f45
commit 48c8a64c97
4 files changed
+95 -7

No files matched your search

+65
View File
@@ -0,0 +1,65 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { loadOntarioSourcesFromOpenData } from './sources.js';
const ROW = {
Id: '42',
Latitude: 43.6532,
Longitude: -79.3832,
Location: 'Hwy 401 near Yonge St',
Direction: 'Eastbound',
Views: [
{
Status: 'Enabled',
Url: 'https://511on.ca/map/Cctv/4201',
Description: 'Eastbound',
},
],
};
test('without ONTARIO_511_API_KEY the Ontario pack is skipped, logged once', async (t) => {
const warnings = [];
t.mock.method(console, 'warn', (...args) => warnings.push(args.join(' ')));
t.mock.method(globalThis, 'fetch', async () =>
assert.fail('no request without a key'),
);
assert.deepEqual(await loadOntarioSourcesFromOpenData({}, {}), []);
assert.deepEqual(await loadOntarioSourcesFromOpenData({}, {}), []);
assert.equal(warnings.length, 1);
assert.match(warnings[0], /ONTARIO_511_API_KEY/);
});
test('with a key the camera list is requested with it and parsed', async (t) => {
const requested = [];
t.mock.method(globalThis, 'fetch', async (url) => {
requested.push(String(url));
return Response.json([ROW]);
});
const cameras = await loadOntarioSourcesFromOpenData(
{ sourceRoot: '/ignored' },
{ ONTARIO_511_API_KEY: ' test-key ' },
);
const url = new URL(requested[0]);
assert.equal(url.host, '511on.ca');
assert.equal(url.searchParams.get('key'), 'test-key');
assert.equal(url.searchParams.get('format'), 'json');
assert.equal(cameras.length, 1);
assert.equal(cameras[0].id, 'on-42');
assert.equal(cameras[0].url, 'https://511on.ca/map/Cctv/4201');
});
test('a rejected key is reported by status, never by the key itself', async (t) => {
const warnings = [];
t.mock.method(console, 'warn', (...args) => warnings.push(args.join(' ')));
t.mock.method(globalThis, 'fetch', async () =>
Response.json({ Message: 'Invalid Key' }, { status: 400 }),
);
const cameras = await loadOntarioSourcesFromOpenData(
{},
{ ONTARIO_511_API_KEY: 'secret-key-value' },
);
assert.deepEqual(cameras, []);
assert.match(warnings.join('\n'), /400/);
assert.doesNotMatch(warnings.join('\n'), /secret-key-value/);
});
+28 -6
View File
@@ -502,17 +502,39 @@ function pickOntarioCctvView(views) {
);
}
let ontarioKeyMissingLogged = false;
/**
* Fetch Ontario 511 CCTV cameras. Keyless: the catalog is exposed by the
* public 511 API, while frame URLs are stable still-image endpoints under
* 511on.ca/map/Cctv/. Only rows with finite Ontario coords and at least one
* enabled official still view are kept.
* Fetch Ontario 511 CCTV cameras. The camera list comes from the 511 API,
* which now requires a free developer key (511on.ca/developers, "Requires a
* developer key"; unkeyed calls answer 400 "Invalid Key"), read from
* ONTARIO_511_API_KEY. Without one the pack is skipped with a single log line.
* Frame URLs are stable still-image endpoints under 511on.ca/map/Cctv/. Only
* rows with finite Ontario coords and at least one enabled official still
* view are kept.
*
* @param {object} [_options] The catalog's loader options (unused).
* @param {object} [env] Environment to read the key from.
* @returns {Promise<Array<object>>} Normalized camera source objects.
*/
export async function loadOntarioSourcesFromOpenData() {
export async function loadOntarioSourcesFromOpenData(
_options = {},
env = process.env,
) {
const key = String(env.ONTARIO_511_API_KEY ?? '').trim();
if (!key) {
if (!ontarioKeyMissingLogged) {
ontarioKeyMissingLogged = true;
console.warn(
'[CCTV] Ontario 511 cameras skipped: the 511on.ca API needs a free developer key (set ONTARIO_511_API_KEY)',
);
}
return [];
}
try {
const resp = await fetch(ONTARIO_511_CAMERAS_URL, {
const url = new URL(ONTARIO_511_CAMERAS_URL);
url.searchParams.set('key', key);
const resp = await fetch(url, {
headers: { Accept: 'application/json' },
signal: AbortSignal.timeout(CCTV_SOURCE_FETCH_TIMEOUT_MS),
});