fix(camera): add 30-second scan waiting watchdog

This commit is contained in:
xsl
2026-10-16 16:07:53 +08:00
parent 5e07985b19
commit b19811f974
4 changed files with 96 additions and 24 deletions
+15 -9
View File
@@ -8,9 +8,9 @@ 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: [],
function setup(withAbort = true) {
let timer, complete, signal, result, calls = 0;
const ctx = { IS_MOCK: false, camSeq: 1, AbortController: withAbort ? AbortController : undefined, CaptureQuality: require('../js/capture-quality'), captureDiagnostics: [],
setTimeout(fn, delay) { timer = { fn, delay }; return 1; },
clearTimeout() { timer = null; },
fetch(url, options) {
@@ -18,21 +18,20 @@ function setup() {
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); });
if (signal) 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; });
const cancel = ctx.requestAnalyze(['front', 'left', 'right'], 1, {}, (...args) => { result = args; calls++; });
return { ctx, get timer() { return timer; }, get signal() { return signal; },
get result() { return result; }, finish() { complete(); } };
get result() { return result; }, get calls() { return calls; }, cancel, 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');
assert.equal(delayed.timer.delay, 30000);
delayed.finish();
await flush();
assert.equal(delayed.result[0], null);
@@ -44,11 +43,18 @@ const flush = () => new Promise(resolve => setImmediate(resolve));
await flush();
assert(expired.signal.aborted);
assert.match(expired.result[0], /检测服务开小差/);
assert.equal(expired.calls, 1);
const legacy = setup(false);
legacy.timer.fn();
assert.equal(legacy.result[0], '检测服务开小差了,请重试');
legacy.finish();await flush();assert.equal(legacy.calls,1,'late success never replaces timeout');
const aborted = setup();aborted.cancel();await flush();
assert(aborted.signal.aborted);assert.equal(aborted.result,undefined);assert.equal(aborted.timer,null);
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');
console.log('PASS: 30s request timeout, legacy no-AbortController fallback, single completion, cancellation, late response suppression and timer cleanup');
})().catch(e => { console.error(e); process.exitCode = 1; });