/** * Helper : serveur HTTP local Node natif (boundary integration). * * Pourquoi pas testcontainers Baserow / Docmost reels ? Demarrage 60-120s par * suite, trop couteux pour CI. On simule les endpoints via http.createServer * — ofetch + fetch natif des adapters tapent un vrai socket TCP, donc on teste * le pipeline reseau, le parsing, les retries, le timeout, les codes d erreur. * Si on veut un jour un test container heavy, ajouter un flag INTEGRATION_HEAVY=1. */ import { type IncomingMessage, type Server, type ServerResponse, createServer } from 'node:http'; import type { AddressInfo } from 'node:net'; export interface HttpRequestRecord { method: string; path: string; headers: NodeJS.Dict; body: string; } export type RouteHandler = ( req: IncomingMessage, res: ServerResponse, body: string, ) => void | Promise; export interface FakeHttpServer { url: string; port: number; requests: HttpRequestRecord[]; setRoute(method: string, pathPattern: string | RegExp, handler: RouteHandler): void; reset(): void; stop(): Promise; } interface RouteEntry { method: string; matcher: string | RegExp; handler: RouteHandler; } export async function startFakeHttpServer(): Promise { const requests: HttpRequestRecord[] = []; const routes: RouteEntry[] = []; const server: Server = createServer((req, res) => { let body = ''; req.on('data', (chunk) => { body += chunk; }); req.on('end', async () => { const path = req.url ?? '/'; const method = (req.method ?? 'GET').toUpperCase(); requests.push({ method, path, headers: req.headers, body }); const route = routes.find((r) => { if (r.method !== method) return false; if (typeof r.matcher === 'string') return r.matcher === path.split('?')[0]; return r.matcher.test(path); }); if (!route) { res.statusCode = 404; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ error: 'no route registered', method, path })); return; } try { await route.handler(req, res, body); } catch (err) { res.statusCode = 500; res.end(JSON.stringify({ error: (err as Error).message })); } }); }); await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)); const addr = server.address() as AddressInfo; const port = addr.port; return { url: `http://127.0.0.1:${port}`, port, requests, setRoute(method, pathPattern, handler) { routes.push({ method: method.toUpperCase(), matcher: pathPattern, handler }); }, reset() { requests.length = 0; routes.length = 0; }, stop() { return new Promise((resolve, reject) => { server.close((err) => (err ? reject(err) : resolve())); }); }, }; } export function jsonResponse(res: ServerResponse, status: number, body: unknown): void { res.statusCode = status; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(body)); }