mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
Merge upstream/main into pr/mapillary-layer
Brings in #744 (share-link token ledger). package.json: keep the branch's sorted scripts and add layer-token:next and layer-token:check. Street Level's token moves from the provisional `3` to `0`: following CONTRIBUTING, the provisional entry was removed and `npm run layer-token:next -- street-level` assigned `0` (free digits in order). It is added to the registry and to src/data/layerStateTokenReservations.json, and `npm run layer-token:check -- --base-ref upstream/main` passes (28 published, 1 new). No link with the provisional token was ever published. Pinned token tests move with the ledger as CONTRIBUTING asks: 29 reservations, next free digit `3`, and the competing-PR and retired-digit scenarios shift up one free digit (0->3, 3->4) so they still test the same rules. The README share examples and the free-digit notes in CONTRIBUTING and CURRENT-STATE now read `0`. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
ecb16b2fe2
16 files changed
+1477
-23
No files matched your search
@@ -6,17 +6,22 @@ import assert from 'node:assert/strict';
|
||||
|
||||
import { DataLayerManager } from './manager.js';
|
||||
import {
|
||||
LEGACY_LAYER_STATE_TOKENS,
|
||||
LAYER_STATE_REGISTRY,
|
||||
LAYER_STATE_STORAGE_KEY,
|
||||
LAYER_STATE_TOKEN_ALPHABET,
|
||||
LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
LayerStateCoordinator,
|
||||
REGISTERED_LAYER_IDS,
|
||||
SHARE_TRACKING_RESTORE_POLICIES,
|
||||
createDefaultLayerState,
|
||||
decodeLayerStateParams,
|
||||
encodeLayerStateParams,
|
||||
nextLayerStateToken,
|
||||
normalizeLayerState,
|
||||
parseStoredLayerState,
|
||||
serializeStoredLayerState,
|
||||
validateLayerStateAllocations,
|
||||
validateLayerStateRegistry,
|
||||
} from './layerState.js';
|
||||
import radioLayer from './radio.js';
|
||||
@@ -198,6 +203,144 @@ test('production registry is exact, canonical, and rejects incomplete contracts'
|
||||
assert.equal(new Set(REGISTERED_LAYER_IDS).size, 29);
|
||||
assert.ok(REGISTERED_LAYER_IDS.includes('transit'));
|
||||
assert.deepEqual(REGISTERED_LAYER_IDS, [...REGISTERED_LAYER_IDS].sort());
|
||||
assert.deepEqual(LEGACY_LAYER_STATE_TOKENS, {
|
||||
'ais-live-vessels': 'a',
|
||||
'alpr-cameras': 'p',
|
||||
'bhote-koshi-2026': 'h',
|
||||
'bhote-koshi-locator': 'z',
|
||||
bikeshare: 'b',
|
||||
cctv: 'c',
|
||||
directions: 'n',
|
||||
earthquakes: 'e',
|
||||
'fire-perimeters': '2',
|
||||
flights: 'f',
|
||||
'local-dams': 'q',
|
||||
'local-datacenters': 'd',
|
||||
'local-firms': 'w',
|
||||
military: 'm',
|
||||
'military-awareness': 'g',
|
||||
'military-installations': 'i',
|
||||
radio: 'r',
|
||||
'recent-imagery': '1',
|
||||
'rocket-launches': 'x',
|
||||
satellites: 's',
|
||||
'telegeography-submarine-cables': 'u',
|
||||
traffic: 't',
|
||||
transit: 'j',
|
||||
'weather-cyclones': 'y',
|
||||
'weather-lightning': 'l',
|
||||
'weather-radar': 'v',
|
||||
'weather-satellite': 'o',
|
||||
wind: 'k',
|
||||
});
|
||||
for (const { id, token } of LAYER_STATE_REGISTRY) {
|
||||
assert.equal(LAYER_STATE_TOKEN_RESERVATIONS[id], token);
|
||||
}
|
||||
for (const [id, token] of Object.entries(LEGACY_LAYER_STATE_TOKENS)) {
|
||||
assert.equal(LAYER_STATE_TOKEN_RESERVATIONS[id], token);
|
||||
}
|
||||
assert.equal(nextLayerStateToken(), '3');
|
||||
assert.equal(
|
||||
nextLayerStateToken({ ...LAYER_STATE_TOKEN_RESERVATIONS, alpha: '3', bravo: '4' }),
|
||||
'5',
|
||||
);
|
||||
const digitsExhausted = {
|
||||
...LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
...Object.fromEntries(
|
||||
[...'3456789'].map((digit) => [`prior-${digit}`, digit]),
|
||||
),
|
||||
};
|
||||
assert.equal(nextLayerStateToken(digitsExhausted), '00');
|
||||
assert.equal(
|
||||
nextLayerStateToken({ ...digitsExhausted, retired: '00', used: '01' }),
|
||||
'02',
|
||||
);
|
||||
assert.equal(
|
||||
nextLayerStateToken({
|
||||
...digitsExhausted,
|
||||
...Object.fromEntries(
|
||||
[...LAYER_STATE_TOKEN_ALPHABET].map((second) => [
|
||||
`pair-0${second}`,
|
||||
`0${second}`,
|
||||
]),
|
||||
),
|
||||
}),
|
||||
'10',
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
nextLayerStateToken(
|
||||
Object.fromEntries(
|
||||
[
|
||||
...'0123456789',
|
||||
...[...LAYER_STATE_TOKEN_ALPHABET].flatMap((first) =>
|
||||
[...LAYER_STATE_TOKEN_ALPHABET].map((second) => `${first}${second}`),
|
||||
),
|
||||
].map((token, index) => [`occupied-${index}`, token]),
|
||||
),
|
||||
),
|
||||
/namespace exhausted/,
|
||||
);
|
||||
assert.equal(
|
||||
validateLayerStateRegistry(
|
||||
[{ id: 'future-layer', token: '0', disposition: 'enabled-only' }],
|
||||
{ 'future-layer': '0' },
|
||||
),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
validateLayerStateRegistry(
|
||||
[{ id: 'future-layer', token: '01', disposition: 'enabled-only' }],
|
||||
{ retired: '00', 'future-layer': '01' },
|
||||
),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
validateLayerStateAllocations(
|
||||
LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
{ ...LAYER_STATE_TOKEN_RESERVATIONS, future: '3', next: '4' },
|
||||
),
|
||||
true,
|
||||
);
|
||||
assert.throws(
|
||||
() => validateLayerStateAllocations(LAYER_STATE_TOKEN_RESERVATIONS, {
|
||||
...LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
future: '00',
|
||||
}),
|
||||
/next free token 3/,
|
||||
);
|
||||
const beforeLastDigit = { ...digitsExhausted };
|
||||
delete beforeLastDigit['prior-9'];
|
||||
assert.equal(
|
||||
validateLayerStateAllocations(
|
||||
beforeLastDigit,
|
||||
{ ...beforeLastDigit, futurePair: '00', futureDigit: '9' },
|
||||
),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
validateLayerStateAllocations(
|
||||
digitsExhausted,
|
||||
{ ...digitsExhausted, pairB: '01', pairA: '00' },
|
||||
),
|
||||
true,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
validateLayerStateAllocations(
|
||||
{ ...LAYER_STATE_TOKEN_RESERVATIONS, merged: '3' },
|
||||
{ ...LAYER_STATE_TOKEN_RESERVATIONS, merged: '3', competing: '3' },
|
||||
),
|
||||
/next free token 4/,
|
||||
);
|
||||
assert.throws(
|
||||
() => validateLayerStateAllocations({ future: '00' }, { future: '01' }),
|
||||
/changed or removed/,
|
||||
);
|
||||
assert.throws(
|
||||
() => validateLayerStateAllocations({ retired: '00' }, { newcomer: '00' }),
|
||||
/changed or removed/,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
validateLayerStateRegistry([
|
||||
@@ -206,6 +349,30 @@ test('production registry is exact, canonical, and rejects incomplete contracts'
|
||||
]),
|
||||
/Duplicate layer-state id/,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
validateLayerStateRegistry(
|
||||
[{ id: 'future-layer', token: '000', disposition: 'enabled-only' }],
|
||||
{ 'future-layer': '000' },
|
||||
),
|
||||
/Invalid layer-state token reservation/,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
validateLayerStateRegistry(
|
||||
[{ id: 'future-layer', token: 'a', disposition: 'enabled-only' }],
|
||||
{ 'future-layer': 'a' },
|
||||
),
|
||||
/Legacy layer-state token is immutable/,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
validateLayerStateRegistry(
|
||||
[{ id: 'flights', token: '0', disposition: 'enabled-only' }],
|
||||
{ flights: '0' },
|
||||
),
|
||||
/Legacy layer-state token is immutable/,
|
||||
);
|
||||
|
||||
const manager = new DataLayerManager({});
|
||||
manager.register(fakeLayer('earthquakes'));
|
||||
@@ -289,6 +456,25 @@ test('v2 codec distinguishes absent from empty and keeps canonical deterministic
|
||||
);
|
||||
});
|
||||
|
||||
test('all production layers and options round-trip through a v2 share URL', () => {
|
||||
const allEnabled = normalizeLayerState({
|
||||
enabledLayerIds: REGISTERED_LAYER_IDS,
|
||||
options: {
|
||||
cctv: { coverageMode: 'viewshed', showProjection: false, autoHop: true },
|
||||
flights: { models3d: true, models3dMode: 'all' },
|
||||
radio: { filter: 'news', volume: 0.37 },
|
||||
},
|
||||
});
|
||||
const shareUrl = new URL('https://example.invalid/');
|
||||
shareUrl.hash = encode(allEnabled);
|
||||
const params = new URLSearchParams(shareUrl.hash.slice(1));
|
||||
const restored = decodeLayerStateParams(params);
|
||||
|
||||
assert.equal(params.get('l')?.split('.').length, REGISTERED_LAYER_IDS.length);
|
||||
assert.deepEqual(restored?.enabledLayerIds, REGISTERED_LAYER_IDS);
|
||||
assert.deepEqual(restored?.options, allEnabled.options);
|
||||
});
|
||||
|
||||
test('unknown enabled-layer tokens reject the payload instead of becoming an empty set', () => {
|
||||
assert.equal(
|
||||
decodeLayerStateParams(new URLSearchParams('v=2&l=unknown')),
|
||||
@@ -300,6 +486,27 @@ test('unknown enabled-layer tokens reject the payload instead of becoming an emp
|
||||
);
|
||||
});
|
||||
|
||||
test('malformed enabled-layer lists reject the entire payload', () => {
|
||||
for (const value of ['.c', 'c.', 'c..e', 'c.c', '00', 'c.00']) {
|
||||
assert.equal(
|
||||
decodeLayerStateParams(new URLSearchParams(`v=2&l=${value}`)),
|
||||
null,
|
||||
`l=${value}`,
|
||||
);
|
||||
}
|
||||
assert.deepEqual(
|
||||
decodeLayerStateParams(new URLSearchParams('v=2&l=c.e')).enabledLayerIds,
|
||||
['cctv', 'earthquakes'],
|
||||
);
|
||||
for (const fields of ['l=f&l=f', 'l=f&l=unknown', 'l=&l=f']) {
|
||||
assert.equal(
|
||||
decodeLayerStateParams(new URLSearchParams(`v=2&${fields}`)),
|
||||
null,
|
||||
fields,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('Nepal event and locator have distinct enabled-only share tokens', () => {
|
||||
const decoded = decodeLayerStateParams(new URLSearchParams('v=2&l=h.z'));
|
||||
assert.deepEqual(decoded.enabledLayerIds, [
|
||||
@@ -2421,7 +2628,17 @@ test('the recent-imagery split is share-link only: never stored locally, and a s
|
||||
|
||||
test('fire perimeters uses digit 2 without colliding with wind or recent imagery', () => {
|
||||
const decoded = decodeLayerStateParams(new URLSearchParams('v=2&l=2.k.1'));
|
||||
assert.deepEqual(decoded.enabledLayerIds, ['fire-perimeters', 'recent-imagery', 'wind']);
|
||||
assert.equal(LAYER_STATE_REGISTRY.find(({ id }) => id === 'fire-perimeters').token, '2');
|
||||
assert.deepEqual(decodeLayerStateParams(new URLSearchParams(encode(decoded))), decoded);
|
||||
assert.deepEqual(decoded.enabledLayerIds, [
|
||||
'fire-perimeters',
|
||||
'recent-imagery',
|
||||
'wind',
|
||||
]);
|
||||
assert.equal(
|
||||
LAYER_STATE_REGISTRY.find(({ id }) => id === 'fire-perimeters').token,
|
||||
'2',
|
||||
);
|
||||
assert.deepEqual(
|
||||
decodeLayerStateParams(new URLSearchParams(encode(decoded))),
|
||||
decoded,
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user