// Exercise the actual browser request function with a deterministic fake clock. 'use strict'; const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const vm = require('node:vm'); const source = fs.readFileSync(path.join(__dirname, '../js/app.js'), 'utf8'); const start = source.indexOf(' var ANALYZE_TIMEOUT_MS ='); const end = source.indexOf(' /* ─── SkinScore viewModel', start); assert(start >= 0 && end > start); function setup() { let timer, complete, signal, result; const ctx = { IS_MOCK: false, camSeq: 1, AbortController, CaptureQuality: require('../js/capture-quality'), captureDiagnostics: [], setTimeout(fn, delay) { timer = { fn, delay }; return 1; }, clearTimeout() { timer = null; }, fetch(url, options) { assert.equal(url, 'api/skin-analyze'); signal = options.signal; return new Promise((resolve, reject) => { complete = () => resolve({ json: async () => ({ ok: true, report_saved: true }) }); signal.addEventListener('abort', () => { const e = new Error('aborted'); e.name = 'AbortError'; reject(e); }); }); } }; vm.createContext(ctx); vm.runInContext(source.slice(start, end), ctx); ctx.requestAnalyze(['front', 'left', 'right'], 1, {}, (...args) => { result = args; }); return { ctx, get timer() { return timer; }, get signal() { return signal; }, get result() { return result; }, finish() { complete(); } }; } const flush = () => new Promise(resolve => setImmediate(resolve)); (async () => { const delayed = setup(); assert.equal(delayed.timer.delay, 150000); assert(delayed.timer.delay > 60000 * 2 + 4000, 'Allow both backend attempts plus retry wait'); delayed.finish(); await flush(); assert.equal(delayed.result[0], null); assert.equal(delayed.result[1].report_saved, true); assert.equal(delayed.timer, null); assert.equal(delayed.signal.aborted, false); const expired = setup(); expired.timer.fn(); await flush(); assert(expired.signal.aborted); assert.match(expired.result[0], /检测服务开小差/); const canceled = setup(); canceled.ctx.camSeq++; canceled.finish(); await flush(); assert.equal(canceled.result, undefined); assert.equal(canceled.timer, null); console.log('PASS: frontend timeout covers backend retry budget; success, timeout, stale response, timer cleanup'); })().catch(e => { console.error(e); process.exitCode = 1; });