/** * Minimal HTTP server stub impersonating the two Directus endpoints the * Processor calls: * * GET /users/me — returns a fake user if the cookie matches * GET /items/events/:id — returns 200 if (cookie, eventId) is allowed * * Instantiate with `createDirectusStub(opts)` and tear down with * `stub.close()`. The stub binds to a random OS port and exposes `stub.url` * for config injection. * * Design: bare `node:http` — no Express dependency. */ import * as http from 'node:http'; import type { AddressInfo } from 'node:net'; // --------------------------------------------------------------------------- // Public types // --------------------------------------------------------------------------- export type FakeUser = { readonly id: string; readonly email: string; readonly role: string | null; readonly first_name: string; readonly last_name: string; }; export type StubOptions = { /** * Map from the raw cookie header value (e.g. `"session=abc"`) to the fake * user that cookie represents. Any cookie not in this map → 401. */ readonly allowedCookieToUser: Map; /** * Map from Directus user ID → set of event IDs that user may access. * A request from a valid user for an event not in their set → 403. */ readonly allowedEvents: Map>; }; export type DirectusStub = { readonly url: string; readonly close: () => Promise; }; // --------------------------------------------------------------------------- // Factory // --------------------------------------------------------------------------- /** * Creates and starts a Directus stub server on a random port. * Returns a promise that resolves once the server is listening. */ export function createDirectusStub(opts: StubOptions): Promise { const server = http.createServer((req, res) => { const cookie = req.headers['cookie'] ?? ''; const user = opts.allowedCookieToUser.get(cookie); // GET /users/me if (req.url === '/users/me') { if (!user) { res.writeHead(401).end(); return; } res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify({ data: user })); return; } // GET /items/events/:id const eventMatch = /^\/items\/events\/([0-9a-f-]+)/i.exec(req.url ?? ''); if (eventMatch) { if (!user) { res.writeHead(401).end(); return; } const eventId = eventMatch[1]!; const allowed = opts.allowedEvents.get(user.id)?.has(eventId) ?? false; if (!allowed) { res.writeHead(403).end(); return; } res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify({ data: { id: eventId } })); return; } res.writeHead(404).end(); }); return new Promise((resolve, reject) => { server.on('error', reject); server.listen(0, '127.0.0.1', () => { server.off('error', reject); const addr = server.address() as AddressInfo; resolve({ url: `http://127.0.0.1:${addr.port}`, close: () => new Promise((res, rej) => server.close((err) => (err ? rej(err) : res())), ), }); }); }); }