const request = require("supertest"); const bcrypt = require("bcrypt"); const jwt = require("jsonwebtoken"); const app = require("../src/app"); const prisma = require("../src/config/prisma"); const mailer = require("../src/config/mailer"); const { passwordRuleError } = require("../src/validations/password.validation"); jest.mock("../src/config/mailer", () => ({ sendMail: jest.fn() })); const PREFIX = "onboarding-"; const ADMIN_EMAIL = PREFIX + "admin@test.local"; const EMPLOYEE_EMAIL = PREFIX + "employe@test.local"; const ctx = {}; const tk = (u) => jwt.sign({ id: u.id, role: u.role }, process.env.JWT_SECRET, { expiresIn: "1h" }); // Récupère le mot de passe provisoire dans l'email envoyé à l'employé. function tempPasswordFromMail() { const html = mailer.sendMail.mock.calls[0][0].html; return html.match(/Mot de passe provisoire : ]*>([^<]+)<\/strong>/)[1]; } async function cleanup() { await prisma.user.deleteMany({ where: { email: { startsWith: PREFIX } } }); } beforeAll(async () => { await cleanup(); const h = await bcrypt.hash("Password123", 10); ctx.admin = await prisma.user.create({ data: { firstname: "Alice", lastname: "Admin", email: ADMIN_EMAIL, password: h, role: "ADMIN", isEmailVerified: true }, }); ctx.tAdmin = tk(ctx.admin); }); afterAll(async () => { await cleanup(); await prisma.$disconnect(); }); describe("Creation d un employe avec mot de passe provisoire", () => { test("un echec d envoi de l email annule la creation du compte", async () => { mailer.sendMail.mockRejectedValueOnce(new Error("SMTP indisponible")); jest.spyOn(console, "error").mockImplementation(() => {}); const res = await request(app) .post("/api/admin/employees") .set("Authorization", `Bearer ${ctx.tAdmin}`) .send({ firstname: "Marc", lastname: "Durand", email: EMPLOYEE_EMAIL }); console.error.mockRestore(); expect(res.status).toBe(502); expect(await prisma.user.findUnique({ where: { email: EMPLOYEE_EMAIL } })).toBeNull(); }); test("l admin cree l employe sans mot de passe, un mot de passe provisoire est envoye", async () => { mailer.sendMail.mockClear(); mailer.sendMail.mockResolvedValue(true); const res = await request(app) .post("/api/admin/employees") .set("Authorization", `Bearer ${ctx.tAdmin}`) // Un mot de passe choisi par l'admin est ignoré. .send({ firstname: "Marc", lastname: "Durand", email: EMPLOYEE_EMAIL, password: "ChoixAdmin123" }); expect(res.status).toBe(201); expect(res.body.employee.role).toBe("EMPLOYEE"); // Le mot de passe ne transite jamais par la réponse faite à l'admin. expect(JSON.stringify(res.body)).not.toMatch(/password/i); expect(mailer.sendMail).toHaveBeenCalledTimes(1); expect(mailer.sendMail.mock.calls[0][0].to).toBe(EMPLOYEE_EMAIL); ctx.tempPassword = tempPasswordFromMail(); expect(ctx.tempPassword).toHaveLength(12); expect(ctx.tempPassword).not.toBe("ChoixAdmin123"); // Le provisoire respecte lui aussi la règle commune. expect(passwordRuleError(ctx.tempPassword)).toBeNull(); const enBase = await prisma.user.findUnique({ where: { email: EMPLOYEE_EMAIL } }); expect(enBase.mustChangePassword).toBe(true); expect(await bcrypt.compare("ChoixAdmin123", enBase.password)).toBe(false); }); test("la connexion avec le mot de passe provisoire signale le changement obligatoire", async () => { const res = await request(app).post("/api/auth/login").send({ email: EMPLOYEE_EMAIL, password: ctx.tempPassword }); expect(res.status).toBe(200); expect(res.body.user.mustChangePassword).toBe(true); ctx.tEmployee = res.body.token; }); test("tant que le mot de passe n est pas change, l API est bloquee sauf /me", async () => { const bloque = await request(app).get("/api/employee/ticket/INCONNU").set("Authorization", `Bearer ${ctx.tEmployee}`); expect(bloque.status).toBe(403); expect(bloque.body.code).toBe("PASSWORD_CHANGE_REQUIRED"); const changement = await request(app) .patch("/api/auth/change-password") .set("Authorization", `Bearer ${ctx.tEmployee}`) .send({ currentPassword: ctx.tempPassword, newPassword: "Contournement1" }); expect(changement.status).toBe(403); const me = await request(app).get("/api/auth/me").set("Authorization", `Bearer ${ctx.tEmployee}`); expect(me.status).toBe(200); expect(me.body.user.mustChangePassword).toBe(true); }); test("le nouveau mot de passe doit etre confirme et different du provisoire", async () => { const auth = { Authorization: `Bearer ${ctx.tEmployee}` }; const different = await request(app) .post("/api/auth/initial-password") .set(auth) .send({ password: "NouveauMdp123!", confirmPassword: "AutreMdp456!" }); expect(different.status).toBe(400); expect(different.body.message).toMatch(/ne correspondent pas/); const court = await request(app) .post("/api/auth/initial-password") .set(auth) .send({ password: "court", confirmPassword: "court" }); expect(court.status).toBe(400); const sansSpecial = await request(app) .post("/api/auth/initial-password") .set(auth) .send({ password: "NouveauMdp123", confirmPassword: "NouveauMdp123" }); expect(sansSpecial.status).toBe(400); expect(sansSpecial.body.message).toMatch(/caractère spécial/); const identique = await request(app) .post("/api/auth/initial-password") .set(auth) .send({ password: ctx.tempPassword, confirmPassword: ctx.tempPassword }); expect(identique.status).toBe(400); expect(identique.body.message).toMatch(/provisoire/); }); test("apres le changement, l acces est retabli et seul le nouveau mot de passe fonctionne", async () => { const auth = { Authorization: `Bearer ${ctx.tEmployee}` }; const ok = await request(app) .post("/api/auth/initial-password") .set(auth) .send({ password: "NouveauMdp123!", confirmPassword: "NouveauMdp123!" }); expect(ok.status).toBe(200); // Le ticket n'existe pas : un 404 prouve que la requête a franchi le filtre. const acces = await request(app).get("/api/employee/ticket/INCONNU").set(auth); expect(acces.status).not.toBe(403); const encore = await request(app) .post("/api/auth/initial-password") .set(auth) .send({ password: "EncoreUnAutre1!", confirmPassword: "EncoreUnAutre1!" }); expect(encore.status).toBe(400); const ancien = await request(app).post("/api/auth/login").send({ email: EMPLOYEE_EMAIL, password: ctx.tempPassword }); expect(ancien.status).toBe(401); const nouveau = await request(app).post("/api/auth/login").send({ email: EMPLOYEE_EMAIL, password: "NouveauMdp123!" }); expect(nouveau.status).toBe(200); expect(nouveau.body.user.mustChangePassword).toBe(false); }); });