changement
This commit is contained in:
144
backend/tests/adminDeliveries.test.js
Normal file
144
backend/tests/adminDeliveries.test.js
Normal file
@@ -0,0 +1,144 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -21,13 +21,31 @@ const testEmail = "e2e-flow-test@test.com";
|
||||
const ticketCode = "ZZE2ETEST1";
|
||||
let token;
|
||||
let createdTicketId;
|
||||
let settingsBackup;
|
||||
|
||||
beforeAll(async () => {
|
||||
await prisma.user.deleteMany({ where: { email: testEmail } });
|
||||
await prisma.ticket.deleteMany({ where: { code: ticketCode } });
|
||||
|
||||
// Le parcours a besoin d'un jeu ouvert, quelle que soit la date de début
|
||||
// enregistrée en base (par défaut le 2 octobre 2026) : on la place à hier
|
||||
// le temps du test, puis on la restaure.
|
||||
settingsBackup = await prisma.gameSettings.findFirst({ orderBy: { id: "asc" } });
|
||||
const hier = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||
if (settingsBackup) {
|
||||
await prisma.gameSettings.update({ where: { id: settingsBackup.id }, data: { startDate: hier } });
|
||||
} else {
|
||||
await prisma.gameSettings.create({ data: { startDate: hier } });
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (settingsBackup) {
|
||||
await prisma.gameSettings.update({ where: { id: settingsBackup.id }, data: { startDate: settingsBackup.startDate } });
|
||||
} else {
|
||||
await prisma.gameSettings.deleteMany({});
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { email: testEmail } });
|
||||
if (user) {
|
||||
await prisma.claim.deleteMany({ where: { participation: { userId: user.id } } });
|
||||
@@ -48,7 +66,8 @@ describe("THE TIP TOP - FULL E2E FLOW", () => {
|
||||
firstname: "E2E",
|
||||
lastname: "Test",
|
||||
email: testEmail,
|
||||
password: "Password123",
|
||||
password: "Password123!",
|
||||
confirmPassword: "Password123!",
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(201);
|
||||
@@ -62,7 +81,7 @@ describe("THE TIP TOP - FULL E2E FLOW", () => {
|
||||
|
||||
const res = await request(app).post("/api/auth/login").send({
|
||||
email: testEmail,
|
||||
password: "Password123",
|
||||
password: "Password123!",
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
167
backend/tests/forgotPassword.test.js
Normal file
167
backend/tests/forgotPassword.test.js
Normal file
@@ -0,0 +1,167 @@
|
||||
const request = require("supertest");
|
||||
const bcrypt = require("bcrypt");
|
||||
const app = require("../src/app");
|
||||
const prisma = require("../src/config/prisma");
|
||||
const mailer = require("../src/config/mailer");
|
||||
|
||||
jest.mock("../src/config/mailer", () => ({ sendMail: jest.fn().mockResolvedValue(true) }));
|
||||
|
||||
const EMAIL = "forgot-password-test@test.local";
|
||||
const NON_VERIFIE = "forgot-password-unverified@test.local";
|
||||
const INSCRIT = "forgot-password-register@test.local";
|
||||
|
||||
// Jeton extrait du lien reçu par email.
|
||||
function tokenFromMail() {
|
||||
const html = mailer.sendMail.mock.calls.at(-1)[0].html;
|
||||
return html.match(/\/definir-mot-de-passe\/([a-f0-9]+)"/)[1];
|
||||
}
|
||||
|
||||
async function cleanup() {
|
||||
await prisma.user.deleteMany({ where: { email: { in: [EMAIL, NON_VERIFIE, INSCRIT] } } });
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanup();
|
||||
const h = await bcrypt.hash("AncienMdp1!", 10);
|
||||
await prisma.user.create({ data: { firstname: "Léa", lastname: "Durand", email: EMAIL, password: h, isEmailVerified: true } });
|
||||
await prisma.user.create({ data: { firstname: "Tom", lastname: "Petit", email: NON_VERIFIE, password: h, isEmailVerified: false } });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanup();
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
describe("Mot de passe oublie", () => {
|
||||
beforeEach(() => mailer.sendMail.mockClear());
|
||||
|
||||
test("un email inconnu recoit la meme reponse, sans envoi", async () => {
|
||||
const res = await request(app).post("/api/auth/forgot-password").send({ email: "inconnu@test.local" });
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.message).toMatch(/Si un compte existe/);
|
||||
expect(mailer.sendMail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("un email connu recoit un lien vers la page du front", async () => {
|
||||
const res = await request(app).post("/api/auth/forgot-password").send({ email: EMAIL });
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(mailer.sendMail).toHaveBeenCalledTimes(1);
|
||||
const mail = mailer.sendMail.mock.calls[0][0];
|
||||
expect(mail.to).toBe(EMAIL);
|
||||
const frontendUrl = (process.env.FRONTEND_URL || "http://localhost:5173").split(",")[0].trim();
|
||||
expect(mail.html).toContain(`${frontendUrl}/definir-mot-de-passe/`);
|
||||
expect(tokenFromMail()).toHaveLength(64);
|
||||
});
|
||||
|
||||
test("une nouvelle demande invalide le lien precedent", async () => {
|
||||
await request(app).post("/api/auth/forgot-password").send({ email: EMAIL });
|
||||
const ancien = tokenFromMail();
|
||||
await request(app).post("/api/auth/forgot-password").send({ email: EMAIL });
|
||||
const nouveau = tokenFromMail();
|
||||
|
||||
const res = await request(app)
|
||||
.post(`/api/auth/reset-password/${ancien}`)
|
||||
.send({ password: "NouveauMdp1!", confirmPassword: "NouveauMdp1!" });
|
||||
expect(res.status).toBe(400);
|
||||
expect(nouveau).not.toBe(ancien);
|
||||
});
|
||||
|
||||
test("le lien permet de changer le mot de passe une seule fois", async () => {
|
||||
await request(app).post("/api/auth/forgot-password").send({ email: EMAIL });
|
||||
const token = tokenFromMail();
|
||||
|
||||
const ok = await request(app)
|
||||
.post(`/api/auth/reset-password/${token}`)
|
||||
.send({ password: "NouveauMdp1!", confirmPassword: "NouveauMdp1!" });
|
||||
expect(ok.status).toBe(200);
|
||||
|
||||
expect((await request(app).post("/api/auth/login").send({ email: EMAIL, password: "AncienMdp1!" })).status).toBe(401);
|
||||
expect((await request(app).post("/api/auth/login").send({ email: EMAIL, password: "NouveauMdp1!" })).status).toBe(200);
|
||||
|
||||
const reutilise = await request(app)
|
||||
.post(`/api/auth/reset-password/${token}`)
|
||||
.send({ password: "EncoreAutre1!", confirmPassword: "EncoreAutre1!" });
|
||||
expect(reutilise.status).toBe(400);
|
||||
expect(reutilise.body.message).toMatch(/invalide ou expiré/);
|
||||
});
|
||||
|
||||
test("un lien expire est refuse", async () => {
|
||||
await request(app).post("/api/auth/forgot-password").send({ email: EMAIL });
|
||||
const token = tokenFromMail();
|
||||
await prisma.user.update({ where: { email: EMAIL }, data: { resetPasswordExpires: new Date(Date.now() - 1000) } });
|
||||
|
||||
const res = await request(app)
|
||||
.post(`/api/auth/reset-password/${token}`)
|
||||
.send({ password: "EncoreAutre1!", confirmPassword: "EncoreAutre1!" });
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.message).toMatch(/invalide ou expiré/);
|
||||
});
|
||||
|
||||
test("l email est reconnu quelle que soit la casse et malgre des espaces", async () => {
|
||||
const res = await request(app).post("/api/auth/forgot-password").send({ email: ` ${EMAIL.toUpperCase()} ` });
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(mailer.sendMail).toHaveBeenCalledTimes(1);
|
||||
expect(mailer.sendMail.mock.calls[0][0].to).toBe(EMAIL);
|
||||
});
|
||||
|
||||
test("la reinitialisation valide un compte jamais verifie, qui peut ensuite se connecter", async () => {
|
||||
await request(app).post("/api/auth/forgot-password").send({ email: NON_VERIFIE });
|
||||
await request(app)
|
||||
.post(`/api/auth/reset-password/${tokenFromMail()}`)
|
||||
.send({ password: "NouveauMdp1!", confirmPassword: "NouveauMdp1!" })
|
||||
.expect(200);
|
||||
|
||||
const login = await request(app).post("/api/auth/login").send({ email: NON_VERIFIE, password: "NouveauMdp1!" });
|
||||
expect(login.status).toBe(200);
|
||||
const enBase = await prisma.user.findUnique({ where: { email: NON_VERIFIE } });
|
||||
expect(enBase.isEmailVerified).toBe(true);
|
||||
expect(enBase.emailVerifyToken).toBeNull();
|
||||
});
|
||||
|
||||
test("le profil connecte recoit le lien sur l adresse du compte, sans captcha", async () => {
|
||||
await request(app).post("/api/auth/password-setup-link").expect(401);
|
||||
|
||||
const login = await request(app).post("/api/auth/login").send({ email: EMAIL, password: "NouveauMdp1!" }).expect(200);
|
||||
const res = await request(app)
|
||||
.post("/api/auth/password-setup-link")
|
||||
.set("Authorization", `Bearer ${login.body.token}`);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(mailer.sendMail.mock.calls[0][0].to).toBe(EMAIL);
|
||||
expect(tokenFromMail()).toHaveLength(64);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Emails normalises a l inscription et a la connexion", () => {
|
||||
test("un email saisi avec majuscules est stocke en minuscules et reconnu a la connexion", async () => {
|
||||
const res = await request(app).post("/api/auth/register").send({
|
||||
firstname: "Nora",
|
||||
lastname: "Blanc",
|
||||
email: ` ${INSCRIT.replace("forgot", "FORGOT")} `,
|
||||
password: "MotDePasse1!",
|
||||
confirmPassword: "MotDePasse1!",
|
||||
birthdate: "1990-05-14",
|
||||
});
|
||||
expect(res.status).toBe(201);
|
||||
|
||||
const enBase = await prisma.user.findUnique({ where: { email: INSCRIT } });
|
||||
expect(enBase).not.toBeNull();
|
||||
|
||||
// Même adresse avec une autre casse : doublon refusé.
|
||||
const doublon = await request(app).post("/api/auth/register").send({
|
||||
firstname: "Nora",
|
||||
lastname: "Blanc",
|
||||
email: INSCRIT.toUpperCase(),
|
||||
password: "MotDePasse1!",
|
||||
confirmPassword: "MotDePasse1!",
|
||||
});
|
||||
expect(doublon.status).toBe(409);
|
||||
|
||||
await prisma.user.update({ where: { email: INSCRIT }, data: { isEmailVerified: true } });
|
||||
const login = await request(app).post("/api/auth/login").send({ email: INSCRIT.toUpperCase(), password: "MotDePasse1!" });
|
||||
expect(login.status).toBe(200);
|
||||
});
|
||||
});
|
||||
@@ -87,6 +87,11 @@ describe("VERIFICATION DES 3 CORRECTIFS DE PHASE", () => {
|
||||
log("phase :", vue.body.gameWindow.phase, "| participations ouvertes :", vue.body.gameWindow.isParticipationOpen);
|
||||
expect(vue.body.gameWindow.phase).toBe("NOT_CONFIGURED");
|
||||
|
||||
// Route publique de la page d'accueil : sans configuration, pas de date,
|
||||
// le front affiche alors les dates par défaut.
|
||||
const publique = await request(app).get("/api/game/window").expect(200);
|
||||
expect(publique.body).toMatchObject({ configured: false, startDate: null });
|
||||
|
||||
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);
|
||||
@@ -108,6 +113,12 @@ describe("VERIFICATION DES 3 CORRECTIFS DE PHASE", () => {
|
||||
log("PUT game-settings ->", r.status, "| phase :", r.body.gameWindow.phase);
|
||||
expect(r.body.gameWindow.phase).toBe("SCHEDULED");
|
||||
|
||||
// La page d'accueil reçoit aussitôt la nouvelle date, sans authentification.
|
||||
const publique = await request(app).get("/api/game/window").expect(200);
|
||||
expect(publique.body.configured).toBe(true);
|
||||
expect(publique.body.startDate).toBe(futur);
|
||||
expect(new Date(publique.body.participationDeadline).getTime()).toBe(new Date(futur).getTime() + 30 * DAY);
|
||||
|
||||
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));
|
||||
|
||||
@@ -87,7 +87,7 @@ decrire("Inscription Google et definition du mot de passe", () => {
|
||||
test("une confirmation qui ne correspond pas est refusee", async () => {
|
||||
const res = await request(app)
|
||||
.post("/api/auth/reset-password/" + ctxToken)
|
||||
.send({ password: "MonMotDePasse1", confirmPassword: "AutreMotDePasse2" });
|
||||
.send({ password: "MonMotDePasse1!", confirmPassword: "AutreMotDePasse2!" });
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.message).toMatch(/ne correspondent pas/);
|
||||
@@ -105,14 +105,23 @@ decrire("Inscription Google et definition du mot de passe", () => {
|
||||
expect(res.body.message).toMatch(/8 caractères/);
|
||||
});
|
||||
|
||||
test("un mot de passe sans caractere special est refuse", async () => {
|
||||
const res = await request(app)
|
||||
.post("/api/auth/reset-password/" + ctxToken)
|
||||
.send({ password: "MonMotDePasse1", confirmPassword: "MonMotDePasse1" });
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.message).toMatch(/caractère spécial/);
|
||||
});
|
||||
|
||||
test("le mot de passe defini permet ensuite de se connecter et de le changer", async () => {
|
||||
const ok = await request(app)
|
||||
.post("/api/auth/reset-password/" + ctxToken)
|
||||
.send({ password: "MonMotDePasse1", confirmPassword: "MonMotDePasse1" })
|
||||
.send({ password: "MonMotDePasse1!", confirmPassword: "MonMotDePasse1!" })
|
||||
.expect(200);
|
||||
expect(ok.body.message).toMatch(/réinitialisé/);
|
||||
|
||||
const login = await request(app).post("/api/auth/login").send({ email: EMAIL, password: "MonMotDePasse1" }).expect(200);
|
||||
const login = await request(app).post("/api/auth/login").send({ email: EMAIL, password: "MonMotDePasse1!" }).expect(200);
|
||||
expect(login.body.token).toBeDefined();
|
||||
|
||||
// Le jeton a bien ete consomme.
|
||||
@@ -125,8 +134,16 @@ decrire("Inscription Google et definition du mot de passe", () => {
|
||||
const change = await request(app)
|
||||
.patch("/api/auth/change-password")
|
||||
.set("Authorization", "Bearer " + login.body.token)
|
||||
.send({ currentPassword: "MonMotDePasse1", newPassword: "EncoreUnAutre2" });
|
||||
.send({ currentPassword: "MonMotDePasse1!", newPassword: "EncoreUnAutre2!" });
|
||||
expect(change.status).toBe(200);
|
||||
|
||||
// Même règle qu'à l'inscription pour le changement depuis le profil.
|
||||
const sansSpecial = await request(app)
|
||||
.patch("/api/auth/change-password")
|
||||
.set("Authorization", "Bearer " + login.body.token)
|
||||
.send({ currentPassword: "EncoreUnAutre2!", newPassword: "SansCaractere3" });
|
||||
expect(sansSpecial.status).toBe(400);
|
||||
expect(sansSpecial.body.message).toMatch(/caractère spécial/);
|
||||
});
|
||||
|
||||
test("une seconde connexion Google ne renvoie pas de nouveau lien", async () => {
|
||||
|
||||
@@ -15,7 +15,8 @@ const payload = {
|
||||
firstname: "Camille",
|
||||
lastname: "Martin",
|
||||
email: testEmail,
|
||||
password: "Password123",
|
||||
password: "Password123!",
|
||||
confirmPassword: "Password123!",
|
||||
birthdate: "1990-05-14",
|
||||
gender: "Femme",
|
||||
};
|
||||
|
||||
62
backend/tests/registerPassword.test.js
Normal file
62
backend/tests/registerPassword.test.js
Normal file
@@ -0,0 +1,62 @@
|
||||
const request = require("supertest");
|
||||
const app = require("../src/app");
|
||||
const prisma = require("../src/config/prisma");
|
||||
|
||||
jest.mock("../src/config/mailer", () => ({ sendMail: jest.fn().mockResolvedValue(true) }));
|
||||
|
||||
const EMAIL = "register-password-test@test.local";
|
||||
|
||||
const payload = (password, confirmPassword = password) => ({
|
||||
firstname: "Camille",
|
||||
lastname: "Martin",
|
||||
email: EMAIL,
|
||||
password,
|
||||
confirmPassword,
|
||||
birthdate: "1990-05-14",
|
||||
});
|
||||
|
||||
async function cleanup() {
|
||||
await prisma.user.deleteMany({ where: { email: EMAIL } });
|
||||
}
|
||||
|
||||
beforeEach(cleanup);
|
||||
afterAll(async () => {
|
||||
await cleanup();
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
describe("Regles du mot de passe a l inscription", () => {
|
||||
test.each([
|
||||
["trop court", "Abc12!", /8 caractères/],
|
||||
["sans caractere special", "MotDePasse123", /caractère spécial/],
|
||||
["accents et espaces ne comptent pas comme speciaux", "Mot de passé 123", /caractère spécial/],
|
||||
])("refuse un mot de passe %s", async (_, password, message) => {
|
||||
const res = await request(app).post("/api/auth/register").send(payload(password));
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.errors.password[0]).toMatch(message);
|
||||
expect(await prisma.user.findUnique({ where: { email: EMAIL } })).toBeNull();
|
||||
});
|
||||
|
||||
test("refuse une confirmation differente", async () => {
|
||||
const res = await request(app).post("/api/auth/register").send(payload("MotDePasse1!", "MotDePasse2!"));
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.errors.confirmPassword[0]).toMatch(/ne correspondent pas/);
|
||||
});
|
||||
|
||||
test("refuse une inscription sans confirmation", async () => {
|
||||
const { confirmPassword, ...sansConfirmation } = payload("MotDePasse1!");
|
||||
const res = await request(app).post("/api/auth/register").send(sansConfirmation);
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.errors.confirmPassword).toBeDefined();
|
||||
});
|
||||
|
||||
test("accepte 8 caracteres avec un caractere special", async () => {
|
||||
const res = await request(app).post("/api/auth/register").send(payload("abcdefg#"));
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
expect(await prisma.user.findUnique({ where: { email: EMAIL } })).not.toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user