mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
fix: allocate share-link layer tokens without collisions
This commit is contained in:
1 parent
ce671ce500
commit
54735a9e6f
15 files changed
+1295
-20
No files matched your search
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import {
|
||||
LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
parseLayerStateTokenReservations,
|
||||
validateLayerStateAllocations,
|
||||
validateLayerStateRegistry,
|
||||
} from '../src/data/layerState.js';
|
||||
|
||||
// The pre-ledger main revision had exactly the 28 pinned v2 registry entries.
|
||||
// Accept only that source blob as the one-time bootstrap; any other published
|
||||
// source without a ledger must fail closed rather than risk losing a token.
|
||||
const PRE_LEDGER_LAYER_STATE_BLOB = 'e559a4c40191512dcefb83134958a3bfd4a98a69';
|
||||
// This snapshot belongs to the verified published blob above, not to the PR's
|
||||
// editable legacy map. Otherwise a candidate could redefine both its ledger
|
||||
// and its comparison baseline before the first ledger reaches main.
|
||||
export const PRE_LEDGER_LAYER_STATE_TOKENS = Object.freeze({
|
||||
'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',
|
||||
});
|
||||
const LEDGER_PATH = 'src/data/layerStateTokenReservations.json';
|
||||
const SOURCE_PATH = 'src/data/layerState.js';
|
||||
|
||||
function git(args, cwd) {
|
||||
return execFileSync('git', args, {
|
||||
cwd,
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
}).trim();
|
||||
}
|
||||
|
||||
/** Read every permanent assignment from a published Git revision. */
|
||||
export function readPublishedLayerStateReservations(
|
||||
baseRef,
|
||||
cwd = process.cwd(),
|
||||
) {
|
||||
const commit = git(
|
||||
['rev-parse', '--verify', '--end-of-options', `${baseRef}^{commit}`],
|
||||
cwd,
|
||||
);
|
||||
const ledgerSpec = `${commit}:${LEDGER_PATH}`;
|
||||
let hasLedger = true;
|
||||
try {
|
||||
git(['cat-file', '-e', ledgerSpec], cwd);
|
||||
} catch {
|
||||
hasLedger = false;
|
||||
}
|
||||
if (!hasLedger) {
|
||||
const sourceBlob = git(['rev-parse', `${commit}:${SOURCE_PATH}`], cwd);
|
||||
if (sourceBlob !== PRE_LEDGER_LAYER_STATE_BLOB) {
|
||||
throw new Error(
|
||||
'Published base has no recognizable layer-state token ledger',
|
||||
);
|
||||
}
|
||||
return PRE_LEDGER_LAYER_STATE_TOKENS;
|
||||
}
|
||||
let rows;
|
||||
try {
|
||||
rows = JSON.parse(git(['show', ledgerSpec], cwd));
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Cannot read published layer-state token ledger: ${error.message}`,
|
||||
);
|
||||
}
|
||||
return parseLayerStateTokenReservations(rows);
|
||||
}
|
||||
|
||||
export function checkLayerStateTokens(baseRef, cwd = process.cwd()) {
|
||||
const base = readPublishedLayerStateReservations(baseRef, cwd);
|
||||
validateLayerStateRegistry();
|
||||
validateLayerStateAllocations(base, LAYER_STATE_TOKEN_RESERVATIONS);
|
||||
return {
|
||||
published: Object.keys(base).length,
|
||||
added:
|
||||
Object.keys(LAYER_STATE_TOKEN_RESERVATIONS).length -
|
||||
Object.keys(base).length,
|
||||
};
|
||||
}
|
||||
|
||||
const invokedPath = process.argv[1]
|
||||
? pathToFileURL(path.resolve(process.argv[1])).href
|
||||
: '';
|
||||
if (import.meta.url === invokedPath) {
|
||||
const args = process.argv.slice(2);
|
||||
const baseRef =
|
||||
args[0] === '--base-ref' && args.length === 2 ? args[1] : null;
|
||||
if (!baseRef || baseRef.startsWith('-')) {
|
||||
console.error('Usage: npm run layer-token:check -- --base-ref origin/main');
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
try {
|
||||
const result = checkLayerStateTokens(baseRef);
|
||||
console.log(
|
||||
`Layer tokens valid against ${baseRef}: ${result.published} published, ` +
|
||||
`${result.added} new.`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`Layer token check failed: ${error.message}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,11 @@
|
||||
"scripts/format-scope.json",
|
||||
"scripts/package-boundaries.json",
|
||||
"scripts/format.mjs",
|
||||
"scripts/next-layer-state-token.mjs",
|
||||
"scripts/check-layer-state-tokens.mjs",
|
||||
"scripts/qa-layer-token-twochar.mjs",
|
||||
"src/data/layerStateTokenReservations.json",
|
||||
"src/data/layerStateTokenLedger.test.mjs",
|
||||
"scripts/check-package-boundaries.mjs",
|
||||
"scripts/precompute-cctv-heights.mjs",
|
||||
"src/tooling/precomputeCctvHeights.test.mjs",
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import {
|
||||
LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
nextLayerStateToken,
|
||||
} from '../src/data/layerState.js';
|
||||
|
||||
const layerId = String(process.argv[2] || '').trim();
|
||||
|
||||
if (!/^[a-z0-9-]+$/.test(layerId)) {
|
||||
console.error('Usage: npm run layer-token:next -- <layer-id>');
|
||||
process.exitCode = 1;
|
||||
} else if (Object.hasOwn(LAYER_STATE_TOKEN_RESERVATIONS, layerId)) {
|
||||
console.error(
|
||||
`${layerId} already owns token ${LAYER_STATE_TOKEN_RESERVATIONS[layerId]}`,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
console.log(`${layerId}: ${nextLayerStateToken()}`);
|
||||
console.log('Order: free digits 0-9, then two-character base-36 00-zz.');
|
||||
console.log('Re-run after rebasing onto the latest main before merge.');
|
||||
}
|
||||
@@ -507,6 +507,7 @@
|
||||
"src/data/detectionPolicy.js",
|
||||
"src/data/installationFeedback.js",
|
||||
"src/data/layerState.js",
|
||||
"src/data/layerStateTokenReservations.json",
|
||||
"src/data/militaryAwarenessEngine.js",
|
||||
"src/data/scenePick.js",
|
||||
"src/keySetupCore.mjs",
|
||||
@@ -746,6 +747,7 @@
|
||||
"src/data/contactMatch.js",
|
||||
"src/data/iconOrientation.js",
|
||||
"src/data/layerState.js",
|
||||
"src/data/layerStateTokenReservations.json",
|
||||
"src/data/modelVisualAnchor.js",
|
||||
"src/data/motionModel.js",
|
||||
"src/data/renderAltitude.js",
|
||||
@@ -787,6 +789,7 @@
|
||||
"src/data/contactMatch.js",
|
||||
"src/data/iconOrientation.js",
|
||||
"src/data/layerState.js",
|
||||
"src/data/layerStateTokenReservations.json",
|
||||
"src/data/modelVisualAnchor.js",
|
||||
"src/data/motionModel.js",
|
||||
"src/data/renderAltitude.js",
|
||||
@@ -823,6 +826,7 @@
|
||||
"src/data/contactMatch.js",
|
||||
"src/data/iconOrientation.js",
|
||||
"src/data/layerState.js",
|
||||
"src/data/layerStateTokenReservations.json",
|
||||
"src/data/modelVisualAnchor.js",
|
||||
"src/data/motionModel.js",
|
||||
"src/data/renderAltitude.js",
|
||||
@@ -1115,6 +1119,7 @@
|
||||
"modules": [
|
||||
"src/data/iconOrientation.js",
|
||||
"src/data/layerState.js",
|
||||
"src/data/layerStateTokenReservations.json",
|
||||
"src/data/radioCountry.js",
|
||||
"src/layers/radio/catalogModel.js",
|
||||
"src/layers/radio/categories.js",
|
||||
@@ -1383,6 +1388,7 @@
|
||||
"src/data/issPass.js",
|
||||
"src/data/labelArbiter.js",
|
||||
"src/data/layerState.js",
|
||||
"src/data/layerStateTokenReservations.json",
|
||||
"src/data/lifecycle.js",
|
||||
"src/data/localGeojson.js",
|
||||
"src/data/localGeojsonCore.js",
|
||||
@@ -2046,6 +2052,7 @@
|
||||
"src/data/satellitePass.js",
|
||||
"src/data/labelArbiter.js",
|
||||
"src/data/layerState.js",
|
||||
"src/data/layerStateTokenReservations.json",
|
||||
"src/data/localGeojson.js",
|
||||
"src/data/localGeojsonCore.js",
|
||||
"src/data/localGeojsonLod.js",
|
||||
@@ -2497,6 +2504,7 @@
|
||||
"modules": [
|
||||
"src/data/installationFeedback.js",
|
||||
"src/data/layerState.js",
|
||||
"src/data/layerStateTokenReservations.json",
|
||||
"src/loadingFeedback.js",
|
||||
"src/navigationPolicy.js",
|
||||
"src/ui/shareRestoration.js",
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* End-to-end share/reload proof for the first two-character token. The QA
|
||||
* layer and an exhausted-digit reservation scenario are injected into
|
||||
* dev-server responses in this isolated browser only. Neither belongs in a
|
||||
* production bundle or ledger.
|
||||
*
|
||||
* Run against a local Vite server: node scripts/qa-layer-token-twochar.mjs
|
||||
*/
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import puppeteer from 'puppeteer';
|
||||
import {
|
||||
LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
nextLayerStateToken,
|
||||
parseLayerStateTokenReservations,
|
||||
validateLayerStateAllocations,
|
||||
} from '../src/data/layerState.js';
|
||||
|
||||
const base = new URL(process.env.QA_BASE_URL || 'http://127.0.0.1:4173');
|
||||
if (!['127.0.0.1', 'localhost'].includes(base.hostname))
|
||||
throw new Error('The QA fixture requires a loopback-only dev server');
|
||||
const origin = base.origin;
|
||||
const fixtureId = 'qa-twochar-fixture';
|
||||
const injections = { codec: 0, catalog: 0 };
|
||||
const errors = [];
|
||||
const reservations = JSON.parse(
|
||||
await readFile(
|
||||
new URL('../src/data/layerStateTokenReservations.json', import.meta.url),
|
||||
'utf8',
|
||||
),
|
||||
);
|
||||
assert.equal(
|
||||
reservations.some(([, token]) => token === '00'),
|
||||
false,
|
||||
'00 must remain unallocated in the production ledger for this QA fixture',
|
||||
);
|
||||
const productionTokens = new Set(reservations.map(([, token]) => token));
|
||||
const priorDigitRows = [...'0123456789']
|
||||
.filter((digit) => !productionTokens.has(digit))
|
||||
.map((digit) => [`qa-prior-digit-${digit}`, digit]);
|
||||
const priorState = parseLayerStateTokenReservations([
|
||||
...reservations,
|
||||
...priorDigitRows,
|
||||
]);
|
||||
assert.equal(nextLayerStateToken(priorState), '00');
|
||||
assert.equal(
|
||||
validateLayerStateAllocations(
|
||||
LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
parseLayerStateTokenReservations([
|
||||
...reservations,
|
||||
...priorDigitRows,
|
||||
[fixtureId, '00'],
|
||||
]),
|
||||
),
|
||||
true,
|
||||
);
|
||||
console.log(
|
||||
'PASS: isolated fixture exhausts prior digits before allocating 00',
|
||||
);
|
||||
for (const source of [
|
||||
'../src/data/layerState.js',
|
||||
'../src/app/constructCatalog.js',
|
||||
]) {
|
||||
assert.equal(
|
||||
(await readFile(new URL(source, import.meta.url), 'utf8')).includes(
|
||||
fixtureId,
|
||||
),
|
||||
false,
|
||||
`${fixtureId} must not exist in production source`,
|
||||
);
|
||||
}
|
||||
|
||||
function replaceOnce(source, from, to, label) {
|
||||
const first = source.indexOf(from);
|
||||
if (first < 0 || source.indexOf(from, first + from.length) >= 0)
|
||||
throw new Error(`QA fixture anchor changed: ${label}`);
|
||||
return source.replace(from, to);
|
||||
}
|
||||
|
||||
function injectCodec(source) {
|
||||
const fixtureRows = JSON.stringify([...priorDigitRows, [fixtureId, '00']]);
|
||||
let result = replaceOnce(
|
||||
source,
|
||||
'parseLayerStateTokenReservations(reservationRows)',
|
||||
`parseLayerStateTokenReservations([...reservationRows, ...${fixtureRows}])`,
|
||||
'reservation',
|
||||
);
|
||||
result = replaceOnce(
|
||||
result,
|
||||
']);\n\nexport const REGISTERED_LAYER_IDS',
|
||||
` Object.freeze({ id: '${fixtureId}', token: '00', disposition: 'enabled-only' }),\n]);\n\nexport const REGISTERED_LAYER_IDS`,
|
||||
'registry',
|
||||
);
|
||||
injections.codec += 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
function injectCatalog(source) {
|
||||
const result = replaceOnce(
|
||||
source,
|
||||
' createBhoteKoshiEventLayer(),',
|
||||
` {
|
||||
id: '${fixtureId}',
|
||||
name: 'Synthetic two-character token',
|
||||
icon: '◆',
|
||||
source: 'QA-only fixture',
|
||||
async init() { return true; },
|
||||
async enable() { return true; },
|
||||
async update() { return true; },
|
||||
async disable() { return true; },
|
||||
async destroy() { return true; },
|
||||
getStats() { return { count: 0 }; },
|
||||
},
|
||||
createBhoteKoshiEventLayer(),`,
|
||||
'catalog',
|
||||
);
|
||||
injections.catalog += 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
args: [
|
||||
'--no-sandbox',
|
||||
'--disable-dev-shm-usage',
|
||||
...(process.platform === 'darwin'
|
||||
? ['--use-angle=metal', '--enable-gpu']
|
||||
: ['--use-gl=angle', '--use-angle=swiftshader']),
|
||||
],
|
||||
});
|
||||
|
||||
async function newPage() {
|
||||
const context = await browser.createBrowserContext();
|
||||
const page = await context.newPage();
|
||||
page.on('pageerror', (error) => errors.push(error.message));
|
||||
await page.setViewport({ width: 1280, height: 800 });
|
||||
await page.setCacheEnabled(false);
|
||||
await page.evaluateOnNewDocument(() => {
|
||||
window.__qaRestoreEvents = [];
|
||||
window.addEventListener('gev:initial-share-restore-settled', (event) => {
|
||||
window.__qaRestoreEvents.push(event.detail);
|
||||
});
|
||||
});
|
||||
await page.setRequestInterception(true);
|
||||
page.on('request', async (request) => {
|
||||
try {
|
||||
const url = new URL(request.url());
|
||||
if (url.origin !== origin) return await request.abort();
|
||||
const transform =
|
||||
url.pathname === '/src/data/layerState.js'
|
||||
? injectCodec
|
||||
: url.pathname === '/src/app/constructCatalog.js'
|
||||
? injectCatalog
|
||||
: null;
|
||||
if (!transform) return await request.continue();
|
||||
const response = await fetch(request.url());
|
||||
assert.equal(response.status, 200, `dev module ${url.pathname}`);
|
||||
await request.respond({
|
||||
status: 200,
|
||||
contentType: 'application/javascript',
|
||||
body: transform(await response.text()),
|
||||
});
|
||||
} catch (error) {
|
||||
errors.push(String(error?.stack || error));
|
||||
await request.abort().catch(() => {});
|
||||
}
|
||||
});
|
||||
return { context, page };
|
||||
}
|
||||
|
||||
async function load(page, url) {
|
||||
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
||||
await page.waitForFunction(
|
||||
(id) => window.__godsEyeView?.dataManager?.layers?.has(id),
|
||||
{ timeout: 60_000 },
|
||||
fixtureId,
|
||||
);
|
||||
}
|
||||
|
||||
async function loadedState(page) {
|
||||
return page.evaluate((id) => {
|
||||
const manager = window.__godsEyeView.dataManager;
|
||||
return {
|
||||
enabled: manager.isEnabled(id),
|
||||
flights: manager.isEnabled('flights'),
|
||||
localStorage: localStorage.getItem('gev:layer-state:v2'),
|
||||
restoreEvents: window.__qaRestoreEvents,
|
||||
hash: location.hash,
|
||||
};
|
||||
}, fixtureId);
|
||||
}
|
||||
|
||||
async function waitForRestore(page) {
|
||||
await page.waitForFunction(() => window.__qaRestoreEvents.length > 0, {
|
||||
timeout: 60_000,
|
||||
});
|
||||
return loadedState(page);
|
||||
}
|
||||
|
||||
function withLayers(link, layers) {
|
||||
const url = new URL(link);
|
||||
const params = new URLSearchParams(url.hash.slice(1));
|
||||
params.set('l', layers);
|
||||
url.hash = params.toString();
|
||||
return url.href;
|
||||
}
|
||||
|
||||
const contexts = [];
|
||||
try {
|
||||
const sender = await newPage();
|
||||
contexts.push(sender.context);
|
||||
await load(sender.page, `${origin}/?welcome=0`);
|
||||
const toggled = await sender.page.evaluate(
|
||||
(id) =>
|
||||
window.__godsEyeView.dataManager.setEnabled(id, true, { origin: 'user' }),
|
||||
fixtureId,
|
||||
);
|
||||
assert.equal(toggled, true, 'synthetic layer enables through manager');
|
||||
await sender.page.waitForFunction(
|
||||
() => new URLSearchParams(location.hash.slice(1)).get('l') === '00',
|
||||
{ timeout: 15_000 },
|
||||
);
|
||||
const sharedUrl = sender.page.url();
|
||||
const authored = new URLSearchParams(new URL(sharedUrl).hash.slice(1));
|
||||
assert.equal(authored.get('v'), '2');
|
||||
assert.equal(authored.get('l'), '00');
|
||||
assert.ok(authored.has('lat') && authored.has('lon'));
|
||||
console.log('PASS: real user-origin enable encoded 00 in a full share URL');
|
||||
|
||||
const recipient = await newPage();
|
||||
contexts.push(recipient.context);
|
||||
await load(recipient.page, sharedUrl);
|
||||
const restored = await waitForRestore(recipient.page);
|
||||
assert.equal(restored.enabled, true);
|
||||
assert.equal(restored.localStorage, null);
|
||||
assert.equal(restored.restoreEvents.at(-1)?.status, 'settled');
|
||||
assert.ok(
|
||||
restored.restoreEvents
|
||||
.at(-1)
|
||||
?.layers?.some(
|
||||
(layer) =>
|
||||
layer.layerId === fixtureId &&
|
||||
layer.targetEnabled === true &&
|
||||
layer.succeeded === true,
|
||||
),
|
||||
'share coordinator restored the synthetic layer',
|
||||
);
|
||||
console.log(
|
||||
'PASS: cold recipient reload restored the 00 layer without writing local preferences',
|
||||
);
|
||||
|
||||
await recipient.page.reload({
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 60_000,
|
||||
});
|
||||
await recipient.page.waitForFunction(
|
||||
(id) => window.__godsEyeView?.dataManager?.layers?.has(id),
|
||||
{ timeout: 60_000 },
|
||||
fixtureId,
|
||||
);
|
||||
const reloaded = await waitForRestore(recipient.page);
|
||||
assert.equal(reloaded.enabled, true);
|
||||
assert.equal(reloaded.localStorage, null);
|
||||
assert.equal(new URLSearchParams(reloaded.hash.slice(1)).get('l'), '00');
|
||||
console.log('PASS: a full recipient reload restores the same 00 layer state');
|
||||
|
||||
const disabled = await recipient.page.evaluate(
|
||||
(id) =>
|
||||
window.__godsEyeView.dataManager.setEnabled(id, false, {
|
||||
origin: 'user',
|
||||
}),
|
||||
fixtureId,
|
||||
);
|
||||
assert.equal(disabled, true);
|
||||
await recipient.page.waitForFunction(
|
||||
() => new URLSearchParams(location.hash.slice(1)).get('l') === '',
|
||||
{ timeout: 15_000 },
|
||||
);
|
||||
assert.equal((await loadedState(recipient.page)).enabled, false);
|
||||
console.log('PASS: explicit OFF removes 00 from the authored share URL');
|
||||
|
||||
const legacy = await newPage();
|
||||
contexts.push(legacy.context);
|
||||
await load(legacy.page, withLayers(sharedUrl, 'f'));
|
||||
const legacyState = await waitForRestore(legacy.page);
|
||||
assert.equal(legacyState.flights, true);
|
||||
assert.equal(legacyState.enabled, false);
|
||||
assert.equal(legacyState.restoreEvents.at(-1)?.status, 'settled');
|
||||
console.log('PASS: a legacy one-character token still restores');
|
||||
|
||||
const mixed = await newPage();
|
||||
contexts.push(mixed.context);
|
||||
await load(mixed.page, withLayers(sharedUrl, 'f.00'));
|
||||
const mixedState = await waitForRestore(mixed.page);
|
||||
assert.equal(mixedState.flights, true);
|
||||
assert.equal(mixedState.enabled, true);
|
||||
console.log('PASS: mixed legacy and two-character tokens restore together');
|
||||
|
||||
const malformed = await newPage();
|
||||
contexts.push(malformed.context);
|
||||
await load(malformed.page, withLayers(sharedUrl, 'f..00'));
|
||||
const malformedState = await waitForRestore(malformed.page);
|
||||
assert.equal(malformedState.flights, false);
|
||||
assert.equal(malformedState.enabled, false);
|
||||
assert.equal(malformedState.localStorage, null);
|
||||
console.log(
|
||||
'PASS: malformed layer list is rejected without partial restoration',
|
||||
);
|
||||
|
||||
const repeated = await newPage();
|
||||
contexts.push(repeated.context);
|
||||
const repeatedUrl = new URL(withLayers(sharedUrl, 'f'));
|
||||
const repeatedParams = new URLSearchParams(repeatedUrl.hash.slice(1));
|
||||
repeatedParams.append('l', '00');
|
||||
repeatedUrl.hash = repeatedParams.toString();
|
||||
await load(repeated.page, repeatedUrl.href);
|
||||
const repeatedState = await waitForRestore(repeated.page);
|
||||
assert.equal(repeatedState.flights, false);
|
||||
assert.equal(repeatedState.enabled, false);
|
||||
assert.equal(repeatedState.localStorage, null);
|
||||
console.log('PASS: repeated layer fields reject the entire share payload');
|
||||
|
||||
assert.ok(injections.codec >= contexts.length);
|
||||
assert.ok(injections.catalog >= contexts.length);
|
||||
assert.deepEqual(errors, []);
|
||||
console.log(
|
||||
'PASS: QA-only codec and catalog injections reached every isolated session',
|
||||
);
|
||||
console.log(
|
||||
'Synthetic end-to-end evidence only; production 00 allocation remains pending.',
|
||||
);
|
||||
console.log('RESULT: 10 passed, 0 failed, 0 skipped');
|
||||
} finally {
|
||||
await Promise.all(contexts.map((context) => context.close().catch(() => {})));
|
||||
await browser.close();
|
||||
}
|
||||
Reference in new issue
Block a user