Files
core2026/app/game-frontend/test/authenticatedEventSource.test.ts
T

105 lines
4.1 KiB
TypeScript

import assert from 'node:assert/strict';
import { createServer, type Server } from 'node:http';
import test from 'node:test';
import { createAuthenticatedEventSource } from '../src/utils/authenticatedEventSource.ts';
const listen = (server: Server): Promise<string> =>
new Promise((resolve) => {
server.listen(0, '127.0.0.1', () => {
const address = server.address();
if (!address || typeof address === 'string') throw new Error('Missing fixture listener');
resolve(`http://127.0.0.1:${address.port}`);
});
});
const stop = (server: Server): Promise<void> =>
new Promise((resolve) => {
server.closeAllConnections();
server.close(() => resolve());
});
void test(
'reconnects with header auth, omits ambient credentials and preserves named UTF-8 SSE events',
{ timeout: 5_000 },
async () => {
const requests: Array<{ url: string; authorization: string | undefined; cookie: string | undefined }> = [];
const server = createServer((request, response) => {
requests.push({
url: request.url!,
authorization: request.headers.authorization,
cookie: request.headers.cookie,
});
response.writeHead(200, { 'content-type': 'text/event-stream' });
response.write(
`retry: 20\nevent: update\ndata: ${JSON.stringify({ value: '갱신', attempt: requests.length })}\n\n`
);
if (requests.length === 1) response.end();
});
const origin = await listen(server);
const source = createAuthenticatedEventSource(`${origin}/events?scope=tournament`, 'ga_synthetic-fixture');
try {
const events: unknown[] = [];
await new Promise<void>((resolve) =>
source.addEventListener('update', (event) => {
events.push(JSON.parse(event.data));
if (events.length === 2) resolve();
})
);
assert.deepEqual(events, [
{ value: '갱신', attempt: 1 },
{ value: '갱신', attempt: 2 },
]);
assert.deepEqual(
requests,
Array.from({ length: 2 }, () => ({
url: '/events?scope=tournament',
authorization: 'Bearer ga_synthetic-fixture',
cookie: undefined,
}))
);
} finally {
source.close();
await stop(server);
}
}
);
void test('401 is terminal and redirects never forward the authorization header', { timeout: 5_000 }, async () => {
let redirectedRequests = 0;
const target = createServer((_req, res) => {
redirectedRequests += 1;
res.writeHead(200);
res.end();
});
const targetOrigin = await listen(target);
const server = createServer((req, res) => {
res.writeHead(req.url === '/unauthorized' ? 401 : 302, { location: targetOrigin });
res.end();
});
const origin = await listen(server);
try {
for (const endpoint of ['unauthorized', 'redirect']) {
const source = createAuthenticatedEventSource(`${origin}/${endpoint}`, 'ga_synthetic-fixture');
try {
const code = await new Promise<number | undefined>((resolve) =>
source.addEventListener('error', (event) => resolve(event.code))
);
if (endpoint === 'unauthorized') {
assert.equal(code, 401);
assert.equal(source.readyState, source.CLOSED);
}
} finally {
source.close();
}
}
assert.equal(redirectedRequests, 0);
} finally {
await stop(server);
await stop(target);
}
});
void test('rejects configured query or URL credentials before connecting', () => {
assert.throws(() => createAuthenticatedEventSource('http://localhost/events?token=synthetic', 'ga_synthetic'));
assert.throws(() => createAuthenticatedEventSource('http://user:synthetic@localhost/events', 'ga_synthetic'));
});