Files
gods-eye-view/src/data/cctvCatalogCap.test.mjs
T
bassem chagra b7d25f4c69 Merge origin/main into feat/wsdot-camera-pack
Resolve the source-pack conflicts with Statens vegvesen (keep both packs).
The Norway pack's 900 default pushed the per-pack defaults to 5,204, past
the 4,500 catalog cap this PR enforces, so raise DEFAULT_CCTV_MAX_SOURCES
to 5,500 and CCTV_MAX_SOURCES_CEILING to 6,000.
2026-10-08 15:59:09 +02:00

163 lines
4.9 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
allocateSourceCap,
resolveCatalogCap,
} from '../../server/providers/cctv/cap.js';
import {
CCTV_MAX_SOURCES_CEILING,
DEFAULT_CCTV_MAX_SOURCES,
} from '../../server/providers/cctv/constants.js';
import { createCctvCatalog } from '../../server/providers/cctv/catalog.js';
const pack = (name, count, prefix = name) => ({
name,
sources: Array.from({ length: count }, (_, i) => ({
id: `${prefix}-${i}`,
rank: i,
})),
});
test('under the cap every pack is served whole, in merge order', () => {
const { sources, packs } = allocateSourceCap(
[pack('a', 3), pack('b', 2)],
10,
);
assert.deepEqual(
sources.map((s) => s.id),
['a-0', 'a-1', 'a-2', 'b-0', 'b-1'],
);
assert.deepEqual(packs, [
{ name: 'a', offered: 3, kept: 3 },
{ name: 'b', offered: 2, kept: 2 },
]);
});
test('over the cap the packs share it round-robin, and the last pack is never starved', () => {
// The old positional slice kept the first N of [a..., b..., c...], which
// deleted pack c entirely; the fair split thins every pack instead.
const { sources, packs } = allocateSourceCap(
[pack('a', 100), pack('b', 100), pack('c', 100)],
9,
);
assert.equal(sources.length, 9);
assert.deepEqual(
packs.map((p) => p.kept),
[3, 3, 3],
);
// Each pack contributes its own highest-priority cameras, in its own order.
assert.deepEqual(
sources.filter((s) => s.id.startsWith('c-')).map((s) => s.rank),
[0, 1, 2],
);
});
test('a duplicate inside one pack takes one slot, not two', () => {
const dup = {
name: 'file',
sources: [{ id: 'x', v: 'old' }, { id: 'x', v: 'new' }, { id: 'y' }],
};
const { sources } = allocateSourceCap([dup], 2);
assert.deepEqual(
sources.map((s) => [s.id, s.v]),
[
['x', 'new'],
['y', undefined],
],
);
});
test('a short pack releases its turns to the others', () => {
const { packs } = allocateSourceCap([pack('a', 2), pack('b', 50)], 10);
assert.deepEqual(
packs.map((p) => p.kept),
[2, 8],
);
});
test('duplicate ids resolve last-pack-wins and count against the winning pack', () => {
const live = {
name: 'live',
sources: [{ id: 'cam-1', label: 'live' }, { id: 'cam-2' }],
};
const override = {
name: 'env',
sources: [{ id: 'cam-1', label: 'override' }],
};
const { sources, packs } = allocateSourceCap([live, override], 10);
assert.deepEqual(
sources.map((s) => [s.id, s.label]),
[
['cam-2', undefined],
['cam-1', 'override'],
],
);
assert.deepEqual(packs, [
{ name: 'live', offered: 1, kept: 1 },
{ name: 'env', offered: 1, kept: 1 },
]);
});
test('resolveCatalogCap bounds the env value and falls back to the default', () => {
assert.equal(resolveCatalogCap(undefined), DEFAULT_CCTV_MAX_SOURCES);
assert.equal(resolveCatalogCap('nonsense'), DEFAULT_CCTV_MAX_SOURCES);
assert.equal(resolveCatalogCap('1'), 8);
assert.equal(resolveCatalogCap('999999'), CCTV_MAX_SOURCES_CEILING);
assert.equal(resolveCatalogCap('120.9'), 120);
});
test('the catalog applies the fair cap to configured packs end to end', async () => {
const saved = { ...process.env };
try {
process.env.CCTV_PREFER_AUSTIN = '0';
process.env.CCTV_SOURCES_FILE = '/nonexistent/cctv.json';
process.env.CCTV_SOURCES_JSON = JSON.stringify(
Array.from({ length: 12 }, (_, i) => ({
id: `env-${i}`,
name: `Env ${i}`,
lat: 30 + i * 0.01,
lon: -97,
url: `https://example.test/${i}.jpg`,
})),
);
process.env.CCTV_MAX_SOURCES = '9';
const getSources = createCctvCatalog({ sourceRoot: '/nonexistent' });
const sources = await getSources();
assert.equal(sources.length, 9);
assert.deepEqual(
sources.slice(0, 3).map((s) => s.id),
['env-0', 'env-1', 'env-2'],
);
assert.equal(sources[0].pack, 'env');
const [trimmed] = getSources.trimmedPacks();
assert.deepEqual(
{ ...trimmed, region: undefined },
{ pack: 'env', available: 12, served: 9, region: undefined },
);
assert.equal(trimmed.region.south, 30);
assert.ok(Math.abs(trimmed.region.north - 30.11) < 1e-9);
} finally {
for (const key of Object.keys(process.env)) {
if (!(key in saved)) delete process.env[key];
}
Object.assign(process.env, saved);
}
});
test('a pack trimmed to its nearest cameras reports how many it offered', async () => {
const { prioritizeSources } =
await import('../../server/providers/cctv/normalize.js');
const cameras = Array.from({ length: 5 }, (_, i) => ({
id: `c${i}`,
lat: i,
lon: 0,
}));
const kept = prioritizeSources(cameras, 2, [{ lat: 0, lon: 0 }]);
assert.deepEqual(
kept.map((camera) => camera.id),
['c0', 'c1'],
);
assert.equal(kept.available, 5);
assert.deepEqual(kept.region, { west: 0, south: 0, east: 0, north: 4 });
});