changement
This commit is contained in:
166
backend/tests/employeeOnboarding.test.js
Normal file
166
backend/tests/employeeOnboarding.test.js
Normal file
@@ -0,0 +1,166 @@
|
||||
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[^>]*>([^<]+)<\/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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user