Files
PolyMPR/tests/helpers/api_mock.ts
T
djalim 17c5b33a5b refactor(test): improve fetch mock and update fixture types
Add support for HTTP methods, status codes, body and headers in the fetch
mock. Track calls and expose getFetchCalls for assertions. Update fixture
interfaces to use string IDs, add ImportResult and ApiError types, and
provide standard error constants. Adjust fixture data to match new types.
2026-04-21 10:14:25 +00:00

120 lines
3.1 KiB
TypeScript

// Mock de fetch() pour les tests — supporte méthodes HTTP et status codes
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
export interface MockRoute {
method?: HttpMethod;
status?: number;
body?: unknown;
headers?: Record<string, string>;
}
// deno-lint-ignore no-explicit-any
let _originalFetch: ((input: any, init?: any) => Promise<Response>) | null =
null;
let _calls: { url: string; method: string; body?: unknown }[] = [];
/**
* Remplace globalThis.fetch par un mock configurable.
*
* Usage simple (GET 200 par défaut) :
* mockFetch({ "/students": studentsData })
*
* Usage avancé (méthode + status) :
* mockFetch({ "/students": { method: "POST", status: 201, body: newStudent } })
*/
export function mockFetch(
routes: Record<string, unknown | MockRoute>,
): void {
_originalFetch = globalThis.fetch;
_calls = [];
globalThis.fetch = async (
input: string | URL | Request,
init?: RequestInit,
): Promise<Response> => {
const url = typeof input === "string"
? input
: input instanceof URL
? input.toString()
: input.url;
const method = (init?.method ?? "GET").toUpperCase();
// Parse le body si présent
let reqBody: unknown = undefined;
if (init?.body) {
try {
reqBody = JSON.parse(init.body as string);
} catch {
reqBody = init.body;
}
}
_calls.push({ url, method, body: reqBody });
for (const [pattern, config] of Object.entries(routes)) {
if (!url.includes(pattern)) continue;
// Config simple : la valeur est directement le body de réponse (GET 200)
if (!isRouteConfig(config)) {
return new Response(JSON.stringify(config), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
// Config avancée : vérifier la méthode si spécifiée
if (config.method && config.method !== method) continue;
const status = config.status ?? 200;
// 204 : pas de body
if (status === 204) {
return new Response(null, { status: 204 });
}
return new Response(
config.body !== undefined ? JSON.stringify(config.body) : null,
{
status,
headers: {
"Content-Type": "application/json",
...config.headers,
},
},
);
}
return new Response(JSON.stringify({ error: "Not Found" }), {
status: 404,
headers: { "Content-Type": "application/json" },
});
};
}
/**
* Restaure le fetch original.
*/
export function restoreFetch(): void {
if (_originalFetch) {
globalThis.fetch = _originalFetch;
_originalFetch = null;
}
_calls = [];
}
/**
* Retourne la liste des appels fetch interceptés.
*/
export function getFetchCalls(): { url: string; method: string; body?: unknown }[] {
return [..._calls];
}
function isRouteConfig(value: unknown): value is MockRoute {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
return false;
}
const v = value as Record<string, unknown>;
return "status" in v || "method" in v || "body" in v;
}