Harden traffic history worker lifecycle and query performance
This commit is contained in:
@@ -143,8 +143,12 @@ test('typed endpoint facade preserves exact request contracts and raw payload id
|
||||
assert.equal(await invoke(), payload);
|
||||
const [actualUrl, actualOptions] = calls.at(-1);
|
||||
assert.equal(actualUrl, url);
|
||||
assert.deepEqual(actualOptions, {
|
||||
...options,
|
||||
assert.ok(actualOptions.signal instanceof AbortSignal);
|
||||
assert.equal(actualOptions.signal.aborted, false);
|
||||
const { signal: requestedSignal, ...requestOptions } = options;
|
||||
const { signal: actualSignal, ...fetchOptions } = actualOptions;
|
||||
assert.deepEqual(fetchOptions, {
|
||||
...requestOptions,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
@@ -162,7 +166,9 @@ test('request preserves caller headers, AbortError identity and JSON fallbacks',
|
||||
received = [url, options];
|
||||
return { ok: true, status: 200, json: async () => value };
|
||||
}), value);
|
||||
assert.deepEqual(received, ['/api/test', {
|
||||
assert.ok(received[1].signal instanceof AbortSignal);
|
||||
const { signal, ...options } = received[1];
|
||||
assert.deepEqual([received[0], options], ['/api/test', {
|
||||
headers: { 'content-type': 'application/custom', 'x-harbor': 'yes' },
|
||||
}]);
|
||||
|
||||
@@ -188,3 +194,40 @@ test('request preserves caller headers, AbortError identity and JSON fallbacks',
|
||||
(error) => error.code === 'CONTROL_UNREACHABLE' && error.status === 503,
|
||||
);
|
||||
});
|
||||
|
||||
test('API deadlines cover headers and response bodies, while caller cancellation keeps its reason', async (t) => {
|
||||
t.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
const stalled = (signal) => new Promise((resolve, reject) => {
|
||||
signal.addEventListener('abort', () => reject(signal.reason), { once: true });
|
||||
});
|
||||
for (const phase of ['headers', 'body']) {
|
||||
let signal;
|
||||
const pending = request('/api/state', {}, async (url, options) => {
|
||||
signal = options.signal;
|
||||
return phase === 'headers' ? stalled(signal)
|
||||
: { ok: true, status: 200, json: () => stalled(signal) };
|
||||
});
|
||||
const checked = assert.rejects(pending, (error) => error.code === 'CONTROL_UNREACHABLE' && error.retryable);
|
||||
await Promise.resolve();
|
||||
t.mock.timers.tick(14_999);
|
||||
assert.equal(signal.aborted, false);
|
||||
t.mock.timers.tick(1);
|
||||
await checked;
|
||||
}
|
||||
const caller = new AbortController();
|
||||
const reason = new DOMException('left the page', 'AbortError');
|
||||
const pending = request('/api/test', { signal: caller.signal }, (url, options) => stalled(options.signal));
|
||||
const checked = assert.rejects(pending, (error) => error === reason);
|
||||
caller.abort(reason);
|
||||
await checked;
|
||||
let commandSignal;
|
||||
const command = request('/api/diagnostics/dns', { method: 'POST' }, (url, options) => {
|
||||
commandSignal = options.signal;
|
||||
return stalled(commandSignal);
|
||||
});
|
||||
const commandChecked = assert.rejects(command, (error) => error.code === 'CONTROL_UNREACHABLE');
|
||||
t.mock.timers.tick(59_999);
|
||||
assert.equal(commandSignal.aborted, false);
|
||||
t.mock.timers.tick(1);
|
||||
await commandChecked;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import test from 'node:test';
|
||||
import { parse } from '@babel/parser';
|
||||
|
||||
const index = readFileSync(new URL('../../index.html', import.meta.url), 'utf8');
|
||||
const main = readFileSync(new URL('../../src/web/main.tsx', import.meta.url), 'utf8');
|
||||
@@ -23,3 +24,44 @@ test('App remains the exported composition component without bootstrap side effe
|
||||
assert.match(app, /<ClientOverviewPage/);
|
||||
assert.match(app, /<StaleBanner/);
|
||||
});
|
||||
|
||||
test('state polling waits for completion and stops after cleanup', async (t) => {
|
||||
const compiled = readFileSync(new URL('../../.test-dist/src/web/App.js', import.meta.url), 'utf8');
|
||||
const component = parse(compiled, { sourceType: 'module' }).program.body
|
||||
.find((node) => node.declaration?.id?.name === 'App').declaration;
|
||||
const effect = component.body.body.map((node) => node.expression)
|
||||
.filter((node) => node?.callee?.name === 'useEffect')
|
||||
.map((node) => node.arguments[0])
|
||||
.find((node) => compiled.slice(node.start, node.end).includes('loadState('));
|
||||
// Execute the shipped effect with a deferred request; no DOM or copied polling loop.
|
||||
const setup = new Function('loadState', `return (${compiled.slice(effect.start, effect.end)})`);
|
||||
t.mock.timers.enable({ apis: ['setTimeout', 'setInterval'] });
|
||||
let calls = 0;
|
||||
let finish;
|
||||
const start = setup(() => {
|
||||
calls++;
|
||||
return new Promise((resolve) => { finish = resolve; });
|
||||
});
|
||||
const cleanup = start();
|
||||
assert.equal(calls, 1);
|
||||
t.mock.timers.tick(40_000);
|
||||
assert.equal(calls, 1);
|
||||
finish();
|
||||
await Promise.resolve();
|
||||
t.mock.timers.tick(4_999);
|
||||
assert.equal(calls, 1);
|
||||
t.mock.timers.tick(1);
|
||||
assert.equal(calls, 2);
|
||||
cleanup();
|
||||
finish();
|
||||
await Promise.resolve();
|
||||
t.mock.timers.tick(40_000);
|
||||
assert.equal(calls, 2);
|
||||
|
||||
const cleanupScheduled = start();
|
||||
finish();
|
||||
await Promise.resolve();
|
||||
cleanupScheduled();
|
||||
t.mock.timers.tick(40_000);
|
||||
assert.equal(calls, 3);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user