test : changed test format + added playwright support
Check Deno code / Check Deno code (pull_request) Has been cancelled
Tests / Unit tests (pull_request) Has been cancelled
Tests / Integration tests (pull_request) Has been cancelled
Check Deno code / Check Deno code (push) Has been cancelled
Tests / Unit tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled

This commit was merged in pull request #153.
This commit is contained in:
2026-05-03 21:52:02 +02:00
parent ed2fe69f54
commit 951c9c1fea
52 changed files with 3576 additions and 5212 deletions
+248 -119
View File
@@ -1,19 +1,28 @@
// Integration tests for /ue-modules — Drizzle ORM direct on real DB
// E2E tests for /ue-modules endpoints — handler + real DB
import { assertEquals, assertExists, assertRejects } from "@std/assert";
import { assertEquals, assertExists } from "@std/assert";
import {
makeContextWithAffiliation,
makeEmployeeContext,
makeGetRequest,
makeJsonRequest,
} from "../helpers/handler.ts";
import {
seedModules,
seedPromotions,
seedUeModules,
seedUes,
testDb,
truncateAll,
} from "../helpers/db_integration.ts";
import { ueModules } from "$root/databases/schema.ts";
import { and, eq } from "npm:drizzle-orm@0.45.2";
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: "integration ue_modules: list all associations",
name: "e2e ue_modules: GET /ue-modules returns all associations",
async fn() {
await truncateAll();
await seedPromotions([{ id: "P1" }]);
@@ -23,41 +32,113 @@ Deno.test({
{ idModule: "M1", idUE: ue.id, idPromo: "P1", coeff: 2.0 },
{ idModule: "M2", idUE: ue.id, idPromo: "P1", coeff: 3.0 },
]);
const rows = await testDb.select().from(ueModules);
assertEquals(rows.length, 2);
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: "integration ue_modules: create and retrieve by composite key",
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 Maths" }]);
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,
});
const [created] = await testDb
.insert(ueModules)
.values({ idModule: "M1", idUE: ue.id, idPromo: "P1", coeff: 4.0 })
.returning();
assertExists(created);
assertEquals(created.coeff, 4.0);
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,
});
const row = await testDb
.select()
.from(ueModules)
.where(
and(
eq(ueModules.idModule, "M1"),
eq(ueModules.idUE, ue.id),
eq(ueModules.idPromo, "P1"),
),
)
.then((r) => r[0] ?? null);
assertExists(row);
assertEquals(row.coeff, 4.0);
// --- 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,
@@ -65,118 +146,166 @@ Deno.test({
Deno.test({
name:
"integration ue_modules: get by composite key returns null when not found",
"e2e ue_modules: GET /ue-modules/:idModule/:idUE/:idPromo 403 for non-employee",
async fn() {
await truncateAll();
const row = await testDb
.select()
.from(ueModules)
.where(
and(
eq(ueModules.idModule, "GHOST"),
eq(ueModules.idUE, 99),
eq(ueModules.idPromo, "GHOST"),
),
)
.then((r) => r[0] ?? null);
assertEquals(row, null);
},
sanitizeResources: false,
sanitizeOps: false,
});
Deno.test({
name: "integration ue_modules: duplicate composite key insert fails",
async fn() {
await truncateAll();
await seedPromotions([{ id: "P1" }]);
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,
}]);
await assertRejects(() =>
testDb.insert(ueModules).values({
const res = await ueModuleHandler.GET!(
makeGetRequest("/ue-modules/M1/1/P1"),
makeContextWithAffiliation("student", {
idModule: "M1",
idUE: ue.id,
idUE: "1",
idPromo: "P1",
coeff: 5.0,
})
}),
);
assertEquals(res.status, 403);
},
sanitizeResources: false,
sanitizeOps: false,
});
Deno.test({
name: "integration ue_modules: update coeff",
name:
"e2e ue_modules: GET /ue-modules/:idModule/:idUE/:idPromo 404 when not found",
async fn() {
await truncateAll();
await seedPromotions([{ id: "P1" }]);
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,
}]);
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,
});
const [updated] = await testDb
.update(ueModules)
.set({ coeff: 6.0 })
.where(
and(
eq(ueModules.idModule, "M1"),
eq(ueModules.idUE, ue.id),
eq(ueModules.idPromo, "P1"),
),
)
.returning();
assertEquals(updated.coeff, 6.0);
// --- 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: "integration ue_modules: delete removes the association",
name:
"e2e ue_modules: PUT /ue-modules/:idModule/:idUE/:idPromo 403 for non-employee",
async fn() {
await truncateAll();
await seedPromotions([{ id: "P1" }]);
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,
}]);
await testDb
.delete(ueModules)
.where(
and(
eq(ueModules.idModule, "M1"),
eq(ueModules.idUE, ue.id),
eq(ueModules.idPromo, "P1"),
),
);
const row = await testDb
.select()
.from(ueModules)
.where(
and(
eq(ueModules.idModule, "M1"),
eq(ueModules.idUE, ue.id),
eq(ueModules.idPromo, "P1"),
),
)
.then((r) => r[0] ?? null);
assertEquals(row, null);
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,