// Checks the shared scan pipeline is wired to the shell's runtime gate. // // On Windows the bundled WSL2 engine is imported on first run, so a Run must // wait for the gate before launching the engine. This test guards two things // without needing a Windows host: // 1. the behaviour of ensureRuntimeGateReady() against stub gates; // 2. that startSequence() actually awaits it, so a future refactor cannot // silently drop the gate and race the import. // // Run with: node tests/runtime-gate.test.js const fs = require('fs'); const path = require('path'); const assert = require('assert'); const libPath = path.join(__dirname, '..', 'TrimSPlib.js'); const src = fs.readFileSync(libPath, 'utf8'); function grab(re) { const m = src.match(re); if (!m) { throw new Error('pattern not found in ' + libPath + ': ' + re); } return m[0]; } // Lift the real ensureRuntimeGateReady() so its shipped code is exercised. const gateSource = grab(/async function ensureRuntimeGateReady\s*\([^)]*\)\s*\{[\s\S]*?\n\}/m); let statusCalls = []; let stubWindow = {}; const factory = new Function('window', 'updateStatusBar', gateSource + '\nreturn ensureRuntimeGateReady;'); function makeGateFn() { statusCalls = []; return factory(stubWindow, function (payload) { statusCalls.push(payload); }); } let passed = 0; let failed = 0; function check(name, ok, detail) { if (ok) { passed++; } else { failed++; } console.log((ok ? 'PASS ' : 'FAIL ') + name.padEnd(46) + (detail || '')); } async function main() { // 1. No gate installed (web/standalone) -> proceed, no status write. stubWindow = {}; let gate = makeGateFn(); let result = await gate('Run'); check('no gate: proceeds', result === true && statusCalls.length === 0); // 2. Gate resolves true -> proceed. stubWindow = { TrimSPRuntimeGate: { ready: () => Promise.resolve(true) } }; gate = makeGateFn(); result = await gate('Run'); check('gate ready: proceeds', result === true); // 3. Gate resolves false -> blocked and message surfaced. stubWindow = { TrimSPRuntimeGate: { ready: () => Promise.resolve(false), message: () => 'Check that WSL2 is installed, then try again.' } }; gate = makeGateFn(); result = await gate('Run'); check('gate not ready: blocked', result === false); check('gate not ready: status shown', statusCalls.length === 1 && /runtime is not ready/.test(statusCalls[0].run) && /WSL2 is installed/.test(statusCalls[0].run), statusCalls.length ? '"' + statusCalls[0].run + '"' : 'no status'); // 4. Gate that throws -> treated as not ready, not an unhandled rejection. stubWindow = { TrimSPRuntimeGate: { ready: () => Promise.reject(new Error('boom')), message: () => 'failed' } }; gate = makeGateFn(); result = await gate('Run'); check('gate throwing: blocked', result === false && statusCalls.length === 1); // 5. startSequence must await the gate before running the engine. const startSequence = grab(/async function startSequence\s*\([^)]*\)\s*\{[\s\S]*?\n\}/m); const guardIndex = startSequence.indexOf('ensureRuntimeGateReady'); const launchIndex = startSequence.indexOf('runSimulationSequence'); check('startSequence awaits gate', guardIndex !== -1, guardIndex !== -1 ? '' : 'no ensureRuntimeGateReady() call'); check('gate checked before launch', guardIndex !== -1 && launchIndex !== -1 && guardIndex < launchIndex); console.log('\n' + passed + ' passed, ' + failed + ' failed'); process.exit(failed ? 1 : 0); } main();