Wiki/bridge/tests/helpers/http-server.ts
Corentin JOGUET 1528017bab test(adapters): tests integration redis (testcontainers) + baserow/docmost (fake HTTP server)
- redis-cache.ts : 16 tests via testcontainers redis:7-alpine, coverage 100% lines / 95.2% branches
- baserow-client.ts : 18 tests via serveur node:http local, coverage 99% lines / 96.9% branches
- docmost-client.ts : 25 tests via serveur node:http local (login + cookie + envelope { data }), coverage 97.7% lines / 93.7% branches
- helper tests/helpers/http-server.ts : serveur Node natif reutilisable (request log + route registry)
- vitest.config.ts : ajout threshold 70% lines+branches sur src/adapters/**
- suppression sanity.test.ts (stub remplace par 3 vraies suites)
- justification fake HTTP vs container heavy en commentaire en tete de fichier

Resultat : 220/220 tests verts, coverage adapters >> seuil 70% requis.
2026-05-07 20:31:08 +02:00

105 lines
3 KiB
TypeScript

/**
* 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<string | string[]>;
body: string;
}
export type RouteHandler = (
req: IncomingMessage,
res: ServerResponse,
body: string,
) => void | Promise<void>;
export interface FakeHttpServer {
url: string;
port: number;
requests: HttpRequestRecord[];
setRoute(method: string, pathPattern: string | RegExp, handler: RouteHandler): void;
reset(): void;
stop(): Promise<void>;
}
interface RouteEntry {
method: string;
matcher: string | RegExp;
handler: RouteHandler;
}
export async function startFakeHttpServer(): Promise<FakeHttpServer> {
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<void>((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<void>((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));
}