- 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.
105 lines
3 KiB
TypeScript
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));
|
|
}
|