mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
338 lines
11 KiB
JavaScript
338 lines
11 KiB
JavaScript
#!/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();
|
|
}
|