const request = require("supertest"); const bcrypt = require("bcrypt"); const jwt = require("jsonwebtoken"); const app = require("../src/app"); const prisma = require("../src/config/prisma"); jest.mock("../src/config/mailer", () => ({ sendMail: jest.fn().mockResolvedValue(true) })); const PREFIX = "tracabilite-"; const CODES = ["TRACE00001", "TRACE00002", "TRACE00003", "TRACE00004"]; const ctx = {}; const tk = (u) => jwt.sign({ id: u.id, role: u.role }, process.env.JWT_SECRET, { expiresIn: "1h" }); const asAdmin = (req) => req.set("Authorization", `Bearer ${ctx.tAdmin}`); async function cleanup() { const users = await prisma.user.findMany({ where: { email: { startsWith: PREFIX } } }); const ids = users.map((u) => u.id); const participations = await prisma.participation.findMany({ where: { userId: { in: ids } } }); await prisma.claim.deleteMany({ where: { participationId: { in: participations.map((p) => p.id) } } }); await prisma.participation.deleteMany({ where: { userId: { in: ids } } }); await prisma.ticket.deleteMany({ where: { code: { in: CODES } } }); await prisma.user.deleteMany({ where: { id: { in: ids } } }); } // Ticket joué par le client, avec une réclamation éventuellement remise. async function jouer(code, { remisPar, remisLe } = {}) { const ticket = await prisma.ticket.create({ data: { code, lotId: ctx.lot.id, isUsed: true, usedAt: new Date("2026-09-01T10:00:00Z"), userId: ctx.client.id }, }); const participation = await prisma.participation.create({ data: { userId: ctx.client.id, ticketId: ticket.id, lotId: ctx.lot.id }, }); return prisma.claim.create({ data: { participationId: participation.id, status: remisPar ? "DELIVERED" : "PENDING", validatedByEmployeeId: remisPar?.id ?? null, claimedAt: remisLe ?? null, }, }); } beforeAll(async () => { await cleanup(); const h = await bcrypt.hash("Password123", 10); const mk = (email, role, firstname, lastname, extra = {}) => prisma.user.create({ data: { firstname, lastname, email: PREFIX + email, password: h, role, isEmailVerified: true, ...extra } }); ctx.admin = await mk("admin@test.local", "ADMIN", "Alice", "Admin"); ctx.marc = await mk("marc@test.local", "EMPLOYEE", "Marc", "Durand"); ctx.sophie = await mk("sophie@test.local", "EMPLOYEE", "Sophie", "Bernard", { mustChangePassword: true }); ctx.client = await mk("client@test.local", "CLIENT", "Chloé", "Martin"); ctx.tAdmin = tk(ctx.admin); ctx.lot = await prisma.lot.findFirst({ orderBy: { id: "asc" } }); await jouer(CODES[0], { remisPar: ctx.marc, remisLe: new Date("2026-09-10T14:30:00Z") }); await jouer(CODES[1], { remisPar: ctx.marc, remisLe: new Date("2026-09-12T09:15:00Z") }); await jouer(CODES[2]); await prisma.ticket.create({ data: { code: CODES[3], lotId: ctx.lot.id } }); }); afterAll(async () => { await cleanup(); await prisma.$disconnect(); }); describe("Liste des employes", () => { test("renvoie les employes avec leur nombre de lots remis", async () => { const res = await asAdmin(request(app).get("/api/admin/employees")).expect(200); const marc = res.body.employees.find((e) => e.id === ctx.marc.id); const sophie = res.body.employees.find((e) => e.id === ctx.sophie.id); expect(marc.deliveredCount).toBe(2); expect(sophie.deliveredCount).toBe(0); expect(sophie.mustChangePassword).toBe(true); // Ni client, ni admin, ni hash de mot de passe. expect(res.body.employees.every((e) => e.id !== ctx.client.id && e.id !== ctx.admin.id)).toBe(true); expect(JSON.stringify(res.body)).not.toMatch(/"password"/); }); test("filtre par nom ou email avec q", async () => { const res = await asAdmin(request(app).get("/api/admin/employees?q=BERNARD")).expect(200); expect(res.body.employees.map((e) => e.id)).toEqual([ctx.sophie.id]); }); test("est reserve a l admin", async () => { await request(app).get("/api/admin/employees").set("Authorization", `Bearer ${tk(ctx.marc)}`).expect(403); }); }); describe("Lots remis par un employe", () => { test("liste les remises de l employe, de la plus recente a la plus ancienne", async () => { const res = await asAdmin(request(app).get(`/api/admin/employees/${ctx.marc.id}/deliveries`)).expect(200); expect(res.body.employee.email).toBe(PREFIX + "marc@test.local"); expect(res.body.deliveries.map((d) => d.ticketCode)).toEqual([CODES[1], CODES[0]]); expect(res.body.deliveries[0].client.firstname).toBe("Chloé"); expect(res.body.deliveries[0].lot.name).toBe(ctx.lot.name); expect(new Date(res.body.deliveries[0].deliveredAt).toISOString()).toBe("2026-09-12T09:15:00.000Z"); }); test("renvoie une liste vide pour un employe sans remise", async () => { const res = await asAdmin(request(app).get(`/api/admin/employees/${ctx.sophie.id}/deliveries`)).expect(200); expect(res.body.deliveries).toEqual([]); }); test("refuse un identifiant qui n est pas un employe", async () => { await asAdmin(request(app).get(`/api/admin/employees/${ctx.client.id}/deliveries`)).expect(404); await asAdmin(request(app).get("/api/admin/employees/abc/deliveries")).expect(400); }); }); describe("Recherche par numero de ticket", () => { test("indique l employe qui a remis le lot et la date, sans tenir compte de la casse", async () => { const res = await asAdmin(request(app).get(`/api/admin/deliveries/${CODES[0].toLowerCase()}`)).expect(200); expect(res.body.ticketCode).toBe(CODES[0]); expect(res.body.status).toBe("DELIVERED"); expect(res.body.deliveredBy).toMatchObject({ id: ctx.marc.id, firstname: "Marc", lastname: "Durand" }); expect(new Date(res.body.deliveredAt).toISOString()).toBe("2026-09-10T14:30:00.000Z"); expect(res.body.client.email).toBe(PREFIX + "client@test.local"); }); test("un lot gagne mais pas encore remis n a pas d employe", async () => { const res = await asAdmin(request(app).get(`/api/admin/deliveries/${CODES[2]}`)).expect(200); // PENDING, ou CANCELLED si la période de retrait du jeu en base est passée. expect(["PENDING", "CANCELLED"]).toContain(res.body.status); expect(res.body.deliveredBy).toBeNull(); expect(res.body.deliveredAt).toBeNull(); }); test("un ticket jamais joue est signale comme tel", async () => { const res = await asAdmin(request(app).get(`/api/admin/deliveries/${CODES[3]}`)).expect(200); expect(res.body.status).toBe("NOT_PLAYED"); expect(res.body.client).toBeNull(); }); test("un numero inconnu renvoie 404", async () => { await asAdmin(request(app).get("/api/admin/deliveries/INEXISTANT")).expect(404); }); });