Release Candidate : 1.0.0 #150
+84
-21
@@ -1,47 +1,94 @@
|
||||
// Mock de fetch() pour les tests
|
||||
// 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 qui retourne des réponses
|
||||
* pré-configurées selon l'URL.
|
||||
* Remplace globalThis.fetch par un mock configurable.
|
||||
*
|
||||
* @param routes - Map URL pattern → données de réponse (sera sérialisé en JSON)
|
||||
* 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>,
|
||||
routes: Record<string, unknown | MockRoute>,
|
||||
): void {
|
||||
_originalFetch = globalThis.fetch;
|
||||
_calls = [];
|
||||
|
||||
globalThis.fetch = (
|
||||
globalThis.fetch = async (
|
||||
input: string | URL | Request,
|
||||
_init?: RequestInit,
|
||||
init?: RequestInit,
|
||||
): Promise<Response> => {
|
||||
const url = typeof input === "string"
|
||||
? input
|
||||
: input instanceof URL
|
||||
? input.toString()
|
||||
: input.url;
|
||||
const method = (init?.method ?? "GET").toUpperCase();
|
||||
|
||||
for (const [pattern, data] of Object.entries(routes)) {
|
||||
if (url.includes(pattern)) {
|
||||
return Promise.resolve(
|
||||
new Response(JSON.stringify(data), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}),
|
||||
);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(
|
||||
new Response(JSON.stringify({ error: "Not Found" }), {
|
||||
status: 404,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}),
|
||||
);
|
||||
_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" },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -53,4 +100,20 @@ export function restoreFetch(): void {
|
||||
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;
|
||||
}
|
||||
|
||||
+61
-31
@@ -1,14 +1,16 @@
|
||||
// Types et données de test pour l'API PolyMPR
|
||||
// Types et données de test alignés sur l'API REST PolyMPR
|
||||
|
||||
// --- Types ---
|
||||
|
||||
export interface Student {
|
||||
numEtud: number;
|
||||
nom: string;
|
||||
prenom: string;
|
||||
idPromo: number;
|
||||
idPromo: string;
|
||||
}
|
||||
|
||||
export interface Promotion {
|
||||
idPromo: number;
|
||||
idPromo: string;
|
||||
annee: string;
|
||||
}
|
||||
|
||||
@@ -19,14 +21,14 @@ export interface Prof {
|
||||
}
|
||||
|
||||
export interface Module {
|
||||
id: number;
|
||||
id: string;
|
||||
nom: string;
|
||||
}
|
||||
|
||||
export interface Note {
|
||||
note: number;
|
||||
numEtud: number;
|
||||
idModule: number;
|
||||
idModule: string;
|
||||
}
|
||||
|
||||
export interface UE {
|
||||
@@ -35,16 +37,16 @@ export interface UE {
|
||||
}
|
||||
|
||||
export interface UeModule {
|
||||
idModule: number;
|
||||
idModule: string;
|
||||
idUE: number;
|
||||
idPromo: number;
|
||||
idPromo: string;
|
||||
coeff: number;
|
||||
}
|
||||
|
||||
export interface Enseignement {
|
||||
idProf: number;
|
||||
idModule: number;
|
||||
idPromo: number;
|
||||
idModule: string;
|
||||
idPromo: string;
|
||||
}
|
||||
|
||||
export interface Ajustement {
|
||||
@@ -53,17 +55,37 @@ export interface Ajustement {
|
||||
valeur: number;
|
||||
}
|
||||
|
||||
export interface ImportResult {
|
||||
imported: number;
|
||||
errors: { line: number; message: string }[];
|
||||
}
|
||||
|
||||
export interface ApiError {
|
||||
error: string;
|
||||
}
|
||||
|
||||
// --- Fixtures ---
|
||||
|
||||
export const students: Student[] = [
|
||||
{ numEtud: 1, nom: "Dupont", prenom: "Alice", idPromo: 1 },
|
||||
{ numEtud: 2, nom: "Martin", prenom: "Bob", idPromo: 1 },
|
||||
{ numEtud: 3, nom: "Durand", prenom: "Claire", idPromo: 2 },
|
||||
{ numEtud: 21212006, nom: "Dupont", prenom: "Jean", idPromo: "4AFISE25/26" },
|
||||
{
|
||||
numEtud: 21212007,
|
||||
nom: "Martin",
|
||||
prenom: "Alice",
|
||||
idPromo: "4AFISE25/26",
|
||||
},
|
||||
{
|
||||
numEtud: 21212008,
|
||||
nom: "Durand",
|
||||
prenom: "Claire",
|
||||
idPromo: "3AFISE25/26",
|
||||
},
|
||||
];
|
||||
|
||||
export const promotions: Promotion[] = [
|
||||
{ idPromo: 1, annee: "2025-2026" },
|
||||
{ idPromo: 2, annee: "2024-2025" },
|
||||
{ idPromo: "4AFISE25/26", annee: "2025" },
|
||||
{ idPromo: "3AFISE25/26", annee: "2025" },
|
||||
{ idPromo: "JIA4A2526", annee: "2025" },
|
||||
];
|
||||
|
||||
export const profs: Prof[] = [
|
||||
@@ -72,36 +94,44 @@ export const profs: Prof[] = [
|
||||
];
|
||||
|
||||
export const modules: Module[] = [
|
||||
{ id: 1, nom: "Mathématiques" },
|
||||
{ id: 2, nom: "Informatique" },
|
||||
{ id: 3, nom: "Physique" },
|
||||
{ id: "JIN702C", nom: "Optimisation" },
|
||||
{ id: "JIN703C", nom: "Informatique" },
|
||||
{ id: "JIN704C", nom: "Physique" },
|
||||
];
|
||||
|
||||
export const notes: Note[] = [
|
||||
{ note: 15, numEtud: 1, idModule: 1 },
|
||||
{ note: 12, numEtud: 1, idModule: 2 },
|
||||
{ note: 18, numEtud: 2, idModule: 1 },
|
||||
{ note: 9, numEtud: 3, idModule: 3 },
|
||||
{ note: 15.5, numEtud: 21212006, idModule: "JIN702C" },
|
||||
{ note: 12.0, numEtud: 21212006, idModule: "JIN703C" },
|
||||
{ note: 18.0, numEtud: 21212007, idModule: "JIN702C" },
|
||||
{ note: 9.0, numEtud: 21212008, idModule: "JIN704C" },
|
||||
];
|
||||
|
||||
export const ues: UE[] = [
|
||||
{ id: 1, nom: "Sciences fondamentales" },
|
||||
{ id: 2, nom: "Sciences appliquées" },
|
||||
{ id: 1, nom: "UE Informatique" },
|
||||
{ id: 2, nom: "UE Mathématiques" },
|
||||
];
|
||||
|
||||
export const ueModules: UeModule[] = [
|
||||
{ idModule: 1, idUE: 1, idPromo: 1, coeff: 3 },
|
||||
{ idModule: 2, idUE: 2, idPromo: 1, coeff: 4 },
|
||||
{ idModule: 3, idUE: 1, idPromo: 2, coeff: 2 },
|
||||
{ idModule: "JIN702C", idUE: 1, idPromo: "4AFISE25/26", coeff: 3.0 },
|
||||
{ idModule: "JIN703C", idUE: 2, idPromo: "4AFISE25/26", coeff: 4.0 },
|
||||
{ idModule: "JIN704C", idUE: 1, idPromo: "3AFISE25/26", coeff: 2.0 },
|
||||
];
|
||||
|
||||
export const enseignements: Enseignement[] = [
|
||||
{ idProf: 1, idModule: 1, idPromo: 1 },
|
||||
{ idProf: 2, idModule: 2, idPromo: 1 },
|
||||
{ idProf: 1, idModule: 3, idPromo: 2 },
|
||||
{ idProf: 1, idModule: "JIN702C", idPromo: "4AFISE25/26" },
|
||||
{ idProf: 2, idModule: "JIN703C", idPromo: "4AFISE25/26" },
|
||||
{ idProf: 1, idModule: "JIN704C", idPromo: "3AFISE25/26" },
|
||||
];
|
||||
|
||||
export const ajustements: Ajustement[] = [
|
||||
{ numEtud: 1, idUE: 1, valeur: 0.5 },
|
||||
{ numEtud: 3, idUE: 1, valeur: -1 },
|
||||
{ numEtud: 21212006, idUE: 1, valeur: 13.25 },
|
||||
{ numEtud: 21212008, idUE: 1, valeur: 11.0 },
|
||||
];
|
||||
|
||||
// --- Réponses d'erreur standard ---
|
||||
|
||||
export const ERROR_NOT_FOUND: ApiError = { error: "Ressource introuvable" };
|
||||
export const ERROR_CONFLICT: ApiError = { error: "Ressource déjà existante" };
|
||||
export const ERROR_BAD_REQUEST: ApiError = { error: "Requête invalide" };
|
||||
export const ERROR_UNAUTHORIZED: ApiError = { error: "Non authentifié" };
|
||||
export const ERROR_FORBIDDEN: ApiError = { error: "Accès interdit" };
|
||||
|
||||
Reference in New Issue
Block a user