const request = require("supertest"); const app = require("../src/app"); const prisma = require("../src/config/prisma"); const jwt = require("jsonwebtoken"); const bcrypt = require("bcrypt"); jest.mock("../src/config/mailer", () => ({ sendMail: jest.fn().mockResolvedValue(true) })); const DAY = 86400000; const PREFIX = "r2-"; const CODES = ["R2TICKET01", "R2TICKET02", "R2TICKET03", "R2TICKET04"]; let ctx = {}; const log = (...a) => console.log(" ", ...a); const titre = (t) => console.log("\n===== " + t + " ====="); async function cleanup() { const us = await prisma.user.findMany({ where: { email: { contains: PREFIX } } }); const ids = us.map((u) => u.id); const ps = await prisma.participation.findMany({ where: { userId: { in: ids } } }); await prisma.claim.deleteMany({ where: { participationId: { in: ps.map((p) => p.id) } } }); await prisma.participation.deleteMany({ where: { userId: { in: ids } } }); await prisma.grandPrizeWinner.deleteMany({ where: { userId: { in: ids } } }); await prisma.ticket.deleteMany({ where: { code: { in: CODES } } }); await prisma.user.deleteMany({ where: { id: { in: ids } } }); } async function jourJ(offsetJours) { const row = await prisma.gameSettings.findFirst({ orderBy: { id: "asc" } }); const debut = new Date(Date.now() - offsetJours * DAY); if (row) await prisma.gameSettings.update({ where: { id: row.id }, data: { startDate: debut } }); else await prisma.gameSettings.create({ data: { startDate: debut } }); } const tk = (u) => jwt.sign({ id: u.id, role: u.role }, process.env.JWT_SECRET, { expiresIn: "2h" }); // La table GrandPrizeWinner est unique pour tout le jeu : on met de cote un // eventuel tirage reel le temps du test, puis on le restaure. async function mettreDeCoteLeGagnant() { const existant = await prisma.grandPrizeWinner.findFirst(); if (existant) await prisma.grandPrizeWinner.delete({ where: { id: existant.id } }); return existant; } async function restaurerLeGagnant(sauvegarde) { if (!sauvegarde) return; await prisma.grandPrizeWinner.deleteMany({}); await prisma.grandPrizeWinner.create({ data: { id: sauvegarde.id, userId: sauvegarde.userId, prize: sauvegarde.prize, drawnAt: sauvegarde.drawnAt }, }); } beforeAll(async () => { ctx.gagnantSauvegarde = await mettreDeCoteLeGagnant(); await cleanup(); const row = await prisma.gameSettings.findFirst({ orderBy: { id: "asc" } }); ctx.startDateOriginale = row ? row.startDate : null; const lot = await prisma.lot.findFirst({ orderBy: { id: "asc" } }); for (const c of CODES) await prisma.ticket.create({ data: { code: c, lotId: lot.id } }); const h = await bcrypt.hash("Password123", 10); ctx.admin = await prisma.user.create({ data: { firstname: "Alice", lastname: "Admin", email: PREFIX + "admin@test.local", password: h, role: "ADMIN", isEmailVerified: true } }); ctx.client = await prisma.user.create({ data: { firstname: "Zoe", lastname: "Cliente", email: PREFIX + "zoe@test.local", password: h, role: "CLIENT", isEmailVerified: true } }); ctx.tAdmin = tk(ctx.admin); ctx.tClient = tk(ctx.client); }); afterAll(async () => { await restaurerLeGagnant(ctx.gagnantSauvegarde); const row = await prisma.gameSettings.findFirst({ orderBy: { id: "asc" } }); if (ctx.startDateOriginale) { if (row) await prisma.gameSettings.update({ where: { id: row.id }, data: { startDate: ctx.startDateOriginale } }); else await prisma.gameSettings.create({ data: { startDate: ctx.startDateOriginale } }); } await cleanup(); await prisma.$disconnect(); }); describe("VERIFICATION DES 3 CORRECTIFS DE PHASE", () => { test("A. rien nest jouable avant tout lancement", async () => { titre("A : aucune GameSettings en base"); await prisma.gameSettings.deleteMany({}); const vue = await request(app).get("/api/admin/game-settings").set("Authorization", "Bearer " + ctx.tAdmin).expect(200); log("phase :", vue.body.gameWindow.phase, "| participations ouvertes :", vue.body.gameWindow.isParticipationOpen); expect(vue.body.gameWindow.phase).toBe("NOT_CONFIGURED"); const essai = await request(app).post("/api/game/participate").set("Authorization", "Bearer " + ctx.tClient).send({ code: CODES[0] }); log("un client tente de jouer ->", essai.status, essai.body.message); expect(essai.status).toBe(410); const t = await prisma.ticket.findUnique({ where: { code: CODES[0] } }); log("le ticket reste vierge :", t.isUsed === false); expect(t.isUsed).toBe(false); const tirage = await request(app).post("/api/admin/draw").set("Authorization", "Bearer " + ctx.tAdmin); log("admin tente le tirage ->", tirage.status, tirage.body.message); expect(tirage.status).toBe(409); expect(await prisma.grandPrizeWinner.count()).toBe(0); }); test("B. jeu programme : message juste et tirage bloque", async () => { titre("B : lancement programme a J+7"); const futur = new Date(Date.now() + 7 * DAY).toISOString(); const r = await request(app).put("/api/admin/game-settings").set("Authorization", "Bearer " + ctx.tAdmin).send({ startDate: futur }); log("PUT game-settings ->", r.status, "| phase :", r.body.gameWindow.phase); expect(r.body.gameWindow.phase).toBe("SCHEDULED"); const anticipe = await request(app).post("/api/game/participate").set("Authorization", "Bearer " + ctx.tClient).send({ code: CODES[0] }); log("client joue avant ouverture ->", anticipe.status); log("message :", JSON.stringify(anticipe.body.message)); expect(anticipe.body.message).toMatch(/ouvrira le/); expect(anticipe.body.message).not.toMatch(/terminée/); const tirage = await request(app).post("/api/admin/draw").set("Authorization", "Bearer " + ctx.tAdmin); log("tirage avant ouverture ->", tirage.status, tirage.body.message); expect(tirage.status).toBe(409); expect(tirage.body.message).toMatch(/pas encore commencé/); }); test("C. les quatre phases dun jeu configure", async () => { titre("C : parcours des phases"); await jourJ(1); let w = (await request(app).get("/api/admin/game-settings").set("Authorization", "Bearer " + ctx.tAdmin)).body.gameWindow; const ok = await request(app).post("/api/game/participate").set("Authorization", "Bearer " + ctx.tClient).send({ code: CODES[0] }); log("jour 1 | phase", w.phase, "| participate ->", ok.status); expect(w.phase).toBe("OPEN"); expect(ok.status).toBe(201); ctx.claimId = ok.body.claim.id; await jourJ(35); w = (await request(app).get("/api/admin/game-settings").set("Authorization", "Bearer " + ctx.tAdmin)).body.gameWindow; const tard = await request(app).post("/api/game/participate").set("Authorization", "Bearer " + ctx.tClient).send({ code: CODES[1] }); log("jour 35 | phase", w.phase, "| participate ->", tard.status, JSON.stringify(tard.body.message)); expect(w.phase).toBe("CLAIM_ONLY"); expect(tard.body.message).toMatch(/participation est terminée/); const tirage = await request(app).post("/api/admin/draw").set("Authorization", "Bearer " + ctx.tAdmin); log("tirage apres cloture ->", tirage.status, tirage.body.message); expect(tirage.status).toBe(201); await jourJ(70); w = (await request(app).get("/api/admin/game-settings").set("Authorization", "Bearer " + ctx.tAdmin)).body.gameWindow; const fini = await request(app).post("/api/game/participate").set("Authorization", "Bearer " + ctx.tClient).send({ code: CODES[2] }); log("jour 70 | phase", w.phase, "| participate ->", fini.status, JSON.stringify(fini.body.message)); expect(w.phase).toBe("CLOSED"); expect(fini.body.message).toMatch(/jeu-concours est terminé/); }); test("D. non-regression : le parcours nominal fonctionne toujours", async () => { titre("D : verification du parcours de base"); await jourJ(2); const g = await request(app).get("/api/game/my-gains").set("Authorization", "Bearer " + ctx.tClient).expect(200); log("gains du client :", JSON.stringify(g.body.gains.map((x) => x.claimStatus))); expect(g.body.gains[0].claimStatus).toBe("PENDING"); const stats = await request(app).get("/api/admin/stats").set("Authorization", "Bearer " + ctx.tAdmin).expect(200); log("stats : phase =", stats.body.gameWindow.phase, "| participations =", stats.body.participations); expect(stats.body.gameWindow.phase).toBe("OPEN"); }); });