// E2E tests for /ue-modules endpoints — handler + real DB import { assertEquals, assertExists } from "@std/assert"; import { makeContextWithAffiliation, makeEmployeeContext, makeGetRequest, makeJsonRequest, } from "../helpers/handler.ts"; import { seedModules, seedPromotions, seedUeModules, seedUes, truncateAll, } from "../helpers/db_integration.ts"; import { handler as ueModulesHandler } from "$apps/admin/api/ue-modules.ts"; import { handler as ueModuleHandler } from "$apps/admin/api/ue-modules/[idModule]/[idUE]/[idPromo].ts"; import { ueModules as ueModulesTable } from "$root/databases/schema.ts"; import { testDb } from "../helpers/db_integration.ts"; // --- GET /ue-modules --- Deno.test({ name: "e2e ue_modules: GET /ue-modules returns all associations", async fn() { await truncateAll(); await seedPromotions([{ id: "P1" }]); await seedModules([{ id: "M1", nom: "Mod A" }, { id: "M2", nom: "Mod B" }]); const [ue] = await seedUes([{ nom: "UE Info" }]); await seedUeModules([ { idModule: "M1", idUE: ue.id, idPromo: "P1", coeff: 2.0 }, { idModule: "M2", idUE: ue.id, idPromo: "P1", coeff: 3.0 }, ]); const res = await ueModulesHandler.GET!( makeGetRequest("/ue-modules"), makeEmployeeContext(), ); assertEquals(res.status, 200); const body = await res.json(); assertEquals(body.length, 2); }, sanitizeResources: false, sanitizeOps: false, }); Deno.test({ name: "e2e ue_modules: GET /ue-modules?idPromo filters by promo", async fn() { await truncateAll(); await seedPromotions([{ id: "P1" }, { id: "P2" }]); await seedModules([{ id: "M1", nom: "Mod A" }]); const [ue] = await seedUes([{ nom: "UE Info" }]); await seedUeModules([ { idModule: "M1", idUE: ue.id, idPromo: "P1", coeff: 2.0 }, { idModule: "M1", idUE: ue.id, idPromo: "P2", coeff: 3.0 }, ]); const res = await ueModulesHandler.GET!( makeGetRequest("/ue-modules", { idPromo: "P1" }), makeEmployeeContext(), ); assertEquals(res.status, 200); const body = await res.json(); assertEquals(body.length, 1); assertEquals(body[0].idPromo, "P1"); }, sanitizeResources: false, sanitizeOps: false, }); // --- POST /ue-modules --- Deno.test({ name: "e2e ue_modules: POST /ue-modules creates association (201)", async fn() { await truncateAll(); await seedPromotions([{ id: "P1" }]); await seedModules([{ id: "M1", nom: "Mod A" }]); const [ue] = await seedUes([{ nom: "UE Info" }]); const res = await ueModulesHandler.POST!( makeJsonRequest("/ue-modules", "POST", { idModule: "M1", idUE: ue.id, idPromo: "P1", coeff: 4.0, }), makeEmployeeContext(), ); assertEquals(res.status, 201); const body = await res.json(); assertExists(body.idModule); assertEquals(body.coeff, 4.0); }, sanitizeResources: false, sanitizeOps: false, }); Deno.test({ name: "e2e ue_modules: POST /ue-modules 400 on missing fields", async fn() { await truncateAll(); const res = await ueModulesHandler.POST!( makeJsonRequest("/ue-modules", "POST", { idModule: "M1" }), makeEmployeeContext(), ); assertEquals(res.status, 400); }, sanitizeResources: false, sanitizeOps: false, }); // --- GET /ue-modules/:idModule/:idUE/:idPromo --- Deno.test({ name: "e2e ue_modules: GET /ue-modules/:idModule/:idUE/:idPromo returns correct association (employee)", async fn() { await truncateAll(); await seedPromotions([{ id: "P1" }, { id: "P2" }]); await seedModules([{ id: "M1", nom: "Mod A" }, { id: "M2", nom: "Mod B" }]); const [ue1, ue2] = await seedUes([{ nom: "UE Info" }, { nom: "UE Maths" }]); // Plusieurs lignes qui partagent idModule="M1" — le handler doit discriminer par idUE ET idPromo await seedUeModules([ { idModule: "M1", idUE: ue1.id, idPromo: "P1", coeff: 3.5 }, { idModule: "M1", idUE: ue2.id, idPromo: "P1", coeff: 1.0 }, { idModule: "M1", idUE: ue1.id, idPromo: "P2", coeff: 2.0 }, { idModule: "M2", idUE: ue1.id, idPromo: "P1", coeff: 4.0 }, ]); const res = await ueModuleHandler.GET!( makeGetRequest(`/ue-modules/M1/${ue1.id}/P1`), makeEmployeeContext({ idModule: "M1", idUE: String(ue1.id), idPromo: "P1", }), ); assertEquals(res.status, 200); const body = await res.json(); // Doit retourner exactement M1/ue1/P1 avec coeff 3.5, pas une autre ligne assertEquals(body.coeff, 3.5); assertEquals(body.idPromo, "P1"); }, sanitizeResources: false, sanitizeOps: false, }); Deno.test({ name: "e2e ue_modules: GET /ue-modules/:idModule/:idUE/:idPromo 403 for non-employee", async fn() { await truncateAll(); const res = await ueModuleHandler.GET!( makeGetRequest("/ue-modules/M1/1/P1"), makeContextWithAffiliation("student", { idModule: "M1", idUE: "1", idPromo: "P1", }), ); assertEquals(res.status, 403); }, sanitizeResources: false, sanitizeOps: false, }); Deno.test({ name: "e2e ue_modules: GET /ue-modules/:idModule/:idUE/:idPromo 404 when not found", async fn() { await truncateAll(); const res = await ueModuleHandler.GET!( makeGetRequest("/ue-modules/GHOST/1/GHOST"), makeEmployeeContext({ idModule: "GHOST", idUE: "1", idPromo: "GHOST" }), ); assertEquals(res.status, 404); }, sanitizeResources: false, sanitizeOps: false, }); // --- PUT /ue-modules/:idModule/:idUE/:idPromo --- Deno.test({ name: "e2e ue_modules: PUT /ue-modules/:idModule/:idUE/:idPromo updates only the targeted row (employee)", async fn() { await truncateAll(); await seedPromotions([{ id: "P1" }, { id: "P2" }]); await seedModules([{ id: "M1", nom: "Mod A" }]); const [ue1, ue2] = await seedUes([{ nom: "UE Info" }, { nom: "UE Maths" }]); // Deux lignes avec même idModule — le PUT ne doit modifier que celle ciblée await seedUeModules([ { idModule: "M1", idUE: ue1.id, idPromo: "P1", coeff: 2.0 }, { idModule: "M1", idUE: ue2.id, idPromo: "P2", coeff: 9.0 }, ]); const res = await ueModuleHandler.PUT!( makeJsonRequest(`/ue-modules/M1/${ue1.id}/P1`, "PUT", { coeff: 5.0 }), makeEmployeeContext({ idModule: "M1", idUE: String(ue1.id), idPromo: "P1", }), ); assertEquals(res.status, 200); const body = await res.json(); assertEquals(body.coeff, 5.0); assertEquals(body.idPromo, "P1"); }, sanitizeResources: false, sanitizeOps: false, }); Deno.test({ name: "e2e ue_modules: PUT /ue-modules/:idModule/:idUE/:idPromo 403 for non-employee", async fn() { await truncateAll(); const res = await ueModuleHandler.PUT!( makeJsonRequest("/ue-modules/M1/1/P1", "PUT", { coeff: 5.0 }), makeContextWithAffiliation("student", { idModule: "M1", idUE: "1", idPromo: "P1", }), ); assertEquals(res.status, 403); }, sanitizeResources: false, sanitizeOps: false, }); Deno.test({ name: "e2e ue_modules: PUT /ue-modules/:idModule/:idUE/:idPromo 404 when not found", async fn() { await truncateAll(); const res = await ueModuleHandler.PUT!( makeJsonRequest("/ue-modules/GHOST/1/GHOST", "PUT", { coeff: 5.0 }), makeEmployeeContext({ idModule: "GHOST", idUE: "1", idPromo: "GHOST" }), ); assertEquals(res.status, 404); }, sanitizeResources: false, sanitizeOps: false, }); // --- DELETE /ue-modules/:idModule/:idUE/:idPromo --- Deno.test({ name: "e2e ue_modules: DELETE /ue-modules/:idModule/:idUE/:idPromo deletes only targeted row (employee)", async fn() { await truncateAll(); await seedPromotions([{ id: "P1" }, { id: "P2" }]); await seedModules([{ id: "M1", nom: "Mod A" }]); const [ue1, ue2] = await seedUes([{ nom: "UE Info" }, { nom: "UE Maths" }]); // Deux lignes avec même idModule — seule celle ciblée doit être supprimée await seedUeModules([ { idModule: "M1", idUE: ue1.id, idPromo: "P1", coeff: 2.0 }, { idModule: "M1", idUE: ue2.id, idPromo: "P2", coeff: 4.0 }, ]); const res = await ueModuleHandler.DELETE!( makeGetRequest(`/ue-modules/M1/${ue1.id}/P1`), makeEmployeeContext({ idModule: "M1", idUE: String(ue1.id), idPromo: "P1", }), ); assertEquals(res.status, 204); // L'autre ligne doit toujours exister const remaining = await testDb.select().from(ueModulesTable); assertEquals(remaining.length, 1); assertEquals(remaining[0].idUE, ue2.id); }, sanitizeResources: false, sanitizeOps: false, }); Deno.test({ name: "e2e ue_modules: DELETE /ue-modules/:idModule/:idUE/:idPromo 403 for non-employee", async fn() { await truncateAll(); const res = await ueModuleHandler.DELETE!( makeGetRequest("/ue-modules/M1/1/P1"), makeContextWithAffiliation("student", { idModule: "M1", idUE: "1", idPromo: "P1", }), ); assertEquals(res.status, 403); }, sanitizeResources: false, sanitizeOps: false, }); Deno.test({ name: "e2e ue_modules: DELETE /ue-modules/:idModule/:idUE/:idPromo 404 when not found", async fn() { await truncateAll(); const res = await ueModuleHandler.DELETE!( makeGetRequest("/ue-modules/GHOST/1/GHOST"), makeEmployeeContext({ idModule: "GHOST", idUE: "1", idPromo: "GHOST" }), ); assertEquals(res.status, 404); }, sanitizeResources: false, sanitizeOps: false, });