Files
behind_glass/scripts/test-analytics-transport.mjs
T
horoli e358e24436 Replace project with current server release and Windows setup
Import the 2026-10-09 source release, Fastify/SQLite backend, restored source
assets, and analytics-enabled server build. Replace files from the previous
release while retaining the existing repository history.

Make builds portable across Windows and Linux, configure local startup,
normalize Windows static asset paths, and fix test fixture cleanup and file
URL handling. Exclude local environment settings, databases, dependencies,
validation output, and TypeScript build cache from version control.

Validation: npm ci, static and server builds, 106 arrival tests, and actual
local HTTP checks passed. The full suite passed 467 of 470 tests; three tests
still require QA fixtures omitted from the supplied release, documented in
README-서버실행.md.
2026-10-09 16:43:35 +09:00

211 lines
11 KiB
JavaScript

import assert from 'node:assert/strict';
import { access } from 'node:fs/promises';
import { test } from 'node:test';
import { loadTypescript } from './support/load-typescript.mjs';
const moduleURL = new URL('../src/analytics/Transport.ts', import.meta.url);
assert.equal(await access(moduleURL).then(() => true, () => false), true, 'bounded telemetry transport module must exist');
const { createTransport } = await loadTypescript(moduleURL);
const flush = () => new Promise(resolve => setImmediate(resolve));
const response = (status, value) => ({ status, json: async () => value });
test('POST sends only serialized JSON and GET returns a valid count', async () => {
const sent = [];
const transport = createTransport({ fetch: async (path, init) => { sent.push({ path, ...init }); return response(init.method === 'POST' ? 204 : 200, { count: 1234 }); } });
assert.deepEqual(await transport.request('/api/visits', { visitor_id: 'visitor' }), { ok: true });
assert.equal(sent[0].body, '{"visitor_id":"visitor"}');
assert.equal(sent[0].headers['Content-Type'], 'application/json');
assert.equal(sent[0].method, 'POST');
assert.equal(sent[0].credentials, 'same-origin');
assert.deepEqual(await transport.request('/api/arrivals/count'), { ok: true, count: 1234 });
assert.equal(sent[1].method, 'GET');
assert.equal(sent[1].body, undefined);
assert.equal(sent[1].cache, 'no-store');
});
for (const failure of ['network', 429, 500, 503]) {
test(`${failure} retries after 500 and 1500 ms with an identical payload, never a fourth attempt`, async () => {
const sent = [], delays = [];
const body = { dog_id: 'original' };
const transport = createTransport({
fetch: async (_path, init) => { sent.push(init.body); body.dog_id = 'edited'; if (failure === 'network') throw Error('offline'); return response(failure); },
sleep: async ms => { delays.push(ms); },
});
assert.deepEqual(await transport.request('/api/arrivals', body), { ok: false });
assert.deepEqual(delays, [500, 1500]);
assert.deepEqual(sent, ['{"dog_id":"original"}', '{"dog_id":"original"}', '{"dog_id":"original"}']);
});
}
test('a later successful retry finishes without further attempts', async () => {
let calls = 0;
const delays = [];
const transport = createTransport({ fetch: async () => response(++calls === 1 ? 503 : 204), sleep: async ms => { delays.push(ms); } });
assert.deepEqual(await transport.request('/api/arrivals', { dog_id: 'id' }), { ok: true });
assert.equal(calls, 2);
assert.deepEqual(delays, [500]);
});
for (const status of [400, 403, 404, 415]) {
test(`${status} is not retried`, async () => {
let calls = 0;
const transport = createTransport({ fetch: async () => { calls++; return response(status); }, sleep: async () => assert.fail('must not retry') });
assert.deepEqual(await transport.request('/api/visits', { visitor_id: 'id' }), { ok: false });
assert.equal(calls, 1);
});
}
test('the 3000 ms deadline aborts the actual fetch and is cleared after completion', async t => {
t.mock.timers.enable({ apis: ['setTimeout'] });
let signal;
const transport = createTransport({ fetch: (_path, init) => { signal = init.signal; return new Promise(() => {}); }, retryDelays: [] });
const pending = transport.request('/api/arrivals/count');
await flush();
t.mock.timers.tick(2999);
assert.equal(signal.aborted, false);
t.mock.timers.tick(1);
await flush();
assert.equal(signal.aborted, true);
assert.deepEqual(await pending, { ok: false });
let completedSignal;
const successful = createTransport({ fetch: async (_path, init) => { completedSignal = init.signal; return response(204); } });
await successful.request('/api/visits', { visitor_id: 'id' });
t.mock.timers.tick(10000);
assert.equal(completedSignal.aborted, false, 'completed attempt timeout was removed');
});
test('deadline also covers a stalled JSON body read', async t => {
t.mock.timers.enable({ apis: ['setTimeout'] });
let signal;
const transport = createTransport({ fetch: async (_path, init) => { signal = init.signal; return { status: 200, json: () => new Promise(() => {}) }; }, retryDelays: [] });
const pending = transport.request('/api/arrivals/count');
await flush();
t.mock.timers.tick(3000);
await flush();
assert.deepEqual(await pending, { ok: false });
assert.equal(signal.aborted, true);
});
test('caller abort cancels an in-flight request, timeout and further retries', async t => {
t.mock.timers.enable({ apis: ['setTimeout'] });
const caller = new AbortController();
let calls = 0, signal;
const transport = createTransport({ fetch: (_path, init) => { calls++; signal = init.signal; return new Promise(() => {}); } });
const pending = transport.request('/api/visits', { visitor_id: 'id' }, caller.signal);
await flush(); caller.abort(); await flush();
assert.deepEqual(await pending, { ok: false });
assert.equal(signal.aborted, true);
t.mock.timers.tick(100000); await flush();
assert.equal(calls, 1);
});
test('caller abort cancels default delayed retry and never makes another request', async t => {
t.mock.timers.enable({ apis: ['setTimeout'] });
const caller = new AbortController();
let calls = 0;
const transport = createTransport({ fetch: async () => { calls++; throw Error('offline'); } });
const pending = transport.request('/api/arrivals', { dog_id: 'id' }, caller.signal);
await flush(); t.mock.timers.tick(499); await flush();
assert.equal(calls, 1);
caller.abort(); await flush();
assert.deepEqual(await pending, { ok: false });
t.mock.timers.tick(100000); await flush();
assert.equal(calls, 1);
});
test('caller abort settles even an injected sleep that ignores cancellation', async () => {
const caller = new AbortController();
let calls = 0, sleepSignal;
const transport = createTransport({ fetch: async () => { calls++; throw Error('offline'); }, sleep: (_ms, signal) => { sleepSignal = signal; return new Promise(() => {}); } });
const pending = transport.request('/api/arrivals', { dog_id: 'id' }, caller.signal);
await flush(); caller.abort(); await flush();
assert.deepEqual(await pending, { ok: false });
assert.equal(sleepSignal.aborted, true);
assert.equal(calls, 1);
});
test('already-aborted requests are inert', async () => {
const caller = new AbortController(); caller.abort();
const transport = createTransport({ fetch: () => assert.fail('must not fetch') });
assert.deepEqual(await transport.request('/api/arrivals/count', undefined, caller.signal), { ok: false });
});
test('malformed count values and malformed JSON never become displayed zero', async () => {
for (const value of [null, {}, [], { count: -1 }, { count: 0.5 }, { count: '2' }, { count: Number.MAX_SAFE_INTEGER + 1 }, { count: NaN }, { count: Infinity }]) {
const transport = createTransport({ fetch: async () => response(200, value) });
assert.deepEqual(await transport.request('/api/arrivals/count'), { ok: false });
}
for (const count of [0, 1, Number.MAX_SAFE_INTEGER]) {
assert.deepEqual(await createTransport({ fetch: async () => response(200, { count }) }).request('/api/arrivals/count'), { ok: true, count });
}
assert.deepEqual(await createTransport({ fetch: async () => ({ status: 200, json: async () => { throw Error('bad JSON'); } }), retryDelays: [] }).request('/api/arrivals/count'), { ok: false });
});
test('serialization and injected dependency failures are contained without rejection', async () => {
const circular = {}; circular.self = circular;
const transport = createTransport({ fetch: () => { throw Error('missing'); }, sleep: () => { throw Error('clock unavailable'); } });
assert.deepEqual(await transport.request('/api/visits', circular), { ok: false });
assert.deepEqual(await transport.request('/api/visits', { visitor_id: 'id' }), { ok: false });
});
test('non-contract success statuses cannot register an arrival or invent a count', async () => {
assert.deepEqual(await createTransport({ fetch: async () => response(200, {}) }).request('/api/arrivals', { dog_id: 'id' }), { ok: false });
assert.deepEqual(await createTransport({ fetch: async () => response(204) }).request('/api/arrivals/count'), { ok: false });
});
test('network failure reading a count response body retries and can recover', async () => {
const delays = [];
let calls = 0;
const transport = createTransport({
fetch: async () => ++calls === 1
? new Response(new ReadableStream({ start(controller) { controller.error(new TypeError('network reset')); } }), { status: 200 })
: Response.json({ count: 14 }),
sleep: async ms => { delays.push(ms); },
});
assert.deepEqual(await transport.request('/api/arrivals/count'), { ok: true, count: 14 });
assert.equal(calls, 2); assert.deepEqual(delays, [500]);
});
test('permanent network failure reading count bodies has exactly two delayed retries', async () => {
const delays = [];
let calls = 0;
const transport = createTransport({
fetch: async () => { calls++; return new Response(new ReadableStream({ start(controller) { controller.error(new TypeError('network reset')); } }), { status: 200 }); },
sleep: async ms => { delays.push(ms); },
});
assert.deepEqual(await transport.request('/api/arrivals/count'), { ok: false });
assert.equal(calls, 3); assert.deepEqual(delays, [500, 1500]);
});
test('syntax-invalid JSON is not a retryable network failure', async () => {
let calls = 0;
const transport = createTransport({ fetch: async () => { calls++; return new Response('{invalid', { status: 200 }); }, sleep: async () => assert.fail('invalid JSON must not retry') });
assert.deepEqual(await transport.request('/api/arrivals/count'), { ok: false });
assert.equal(calls, 1);
});
test('retryable and terminal unread response bodies are cancelled before the attempt completes', async () => {
for (const [status, body, expectedAttempts] of [[429, { dog_id: 'id' }, 3], [503, { dog_id: 'id' }, 3], [200, { dog_id: 'id' }, 1], [403, undefined, 1]]) {
let calls = 0, cancelled = 0;
const cancelledBeforeRetry = [];
const caller = new AbortController();
const transport = createTransport({
fetch: async () => { calls++; return new Response(new ReadableStream({ cancel() { cancelled++; } }), { status }); },
sleep: async () => { cancelledBeforeRetry.push(cancelled); },
});
assert.deepEqual(await transport.request('/api/arrivals', body, caller.signal), { ok: false });
assert.equal(calls, expectedAttempts); assert.equal(cancelled, calls);
assert.deepEqual(cancelledBeforeRetry, expectedAttempts === 3 ? [1, 2] : []);
caller.abort(); await flush(); assert.equal(cancelled, calls, 'later caller abort leaves no open body behind');
}
});
test('response-body cleanup failures cannot reject or stall the bounded request', async () => {
for (const cancel of [() => { throw Error('cancel failed'); }, () => Promise.reject(Error('cancel failed')), () => new Promise(() => {})]) {
let cancelled = 0, signal;
const transport = createTransport({ fetch: async (_path, init) => { signal = init.signal; return { status: 403, body: { cancel: () => { cancelled++; return cancel(); } } }; } });
assert.deepEqual(await transport.request('/api/arrivals/count'), { ok: false });
assert.equal(cancelled, 1); assert.equal(signal.aborted, true);
}
});