Files
THE-TIP-TOP/backend/tests/googlePassword.test.js
Emma781227 2201446ee6 changement
2026-09-14 22:30:40 +02:00

160 lines
6.0 KiB
JavaScript

const request = require("supertest");
const app = require("../src/app");
const prisma = require("../src/config/prisma");
const mailer = require("../src/config/mailer");
const { passport, googleEnabled } = require("../src/config/passport");
jest.mock("../src/config/mailer", () => ({ sendMail: jest.fn() }));
const EMAIL = "google-pwd-test@test.local";
const PROFILE = {
id: "sim-google-id-123456",
displayName: "Chloe Martin",
name: { givenName: "Chloe", familyName: "Martin" },
emails: [{ value: EMAIL }],
photos: [{ value: "https://example.test/avatar.png" }],
};
async function cleanup() {
await prisma.user.deleteMany({ where: { email: EMAIL } });
}
// Rejoue le callback de la strategie Google sans sortir sur le reseau.
function verifierProfilGoogle() {
const strategy = passport._strategy("google");
return new Promise((resolve, reject) => {
strategy._verify("access-token", "refresh-token", PROFILE, (err, user) => {
if (err) reject(err);
else resolve(user);
});
});
}
beforeAll(cleanup);
afterAll(async () => {
await cleanup();
await prisma.$disconnect();
});
const decrire = googleEnabled ? describe : describe.skip;
decrire("Inscription Google et definition du mot de passe", () => {
beforeEach(() => jest.clearAllMocks());
test("un compte Google est cree sans mot de passe et recoit un lien", async () => {
mailer.sendMail.mockResolvedValue(true);
const user = await verifierProfilGoogle();
expect(user.provider).toBe("GOOGLE");
expect(user.password).toBeNull();
expect(user.isEmailVerified).toBe(true);
expect(mailer.sendMail).toHaveBeenCalledTimes(1);
const envoi = mailer.sendMail.mock.calls[0][0];
expect(envoi.to).toBe(EMAIL);
expect(envoi.subject).toMatch(/mot de passe/i);
// Le lien doit viser le formulaire du front, pas la route POST de l API.
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:5173";
expect(envoi.html).toContain(`${frontendUrl}/definir-mot-de-passe/`);
const enBase = await prisma.user.findUnique({ where: { email: EMAIL } });
expect(enBase.resetPasswordToken).toHaveLength(64);
expect(enBase.resetPasswordExpires.getTime()).toBeGreaterThan(Date.now());
ctxToken = enBase.resetPasswordToken;
});
test("le profil signale que le compte n a pas de mot de passe", async () => {
const jwt = require("jsonwebtoken");
const u = await prisma.user.findUnique({ where: { email: EMAIL } });
const token = jwt.sign({ id: u.id, role: u.role }, process.env.JWT_SECRET, { expiresIn: "1h" });
const res = await request(app).get("/api/auth/me").set("Authorization", "Bearer " + token).expect(200);
expect(res.body.user.hasPassword).toBe(false);
expect(res.body.user.provider).toBe("GOOGLE");
// Le hash ne doit jamais sortir de l API.
expect(res.body.user.password).toBeUndefined();
});
test("la connexion par mot de passe est refusee tant qu il n en existe pas", async () => {
const res = await request(app).post("/api/auth/login").send({ email: EMAIL, password: "PeuImporte123" });
expect(res.status).toBe(401);
expect(res.body.message).toMatch(/connexion Google/);
});
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!" });
expect(res.status).toBe(400);
expect(res.body.message).toMatch(/ne correspondent pas/);
const u = await prisma.user.findUnique({ where: { email: EMAIL } });
expect(u.password).toBeNull();
});
test("un mot de passe trop court est refuse", async () => {
const res = await request(app)
.post("/api/auth/reset-password/" + ctxToken)
.send({ password: "court", confirmPassword: "court" });
expect(res.status).toBe(400);
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!" })
.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);
expect(login.body.token).toBeDefined();
// Le jeton a bien ete consomme.
const u = await prisma.user.findUnique({ where: { email: EMAIL } });
expect(u.resetPasswordToken).toBeNull();
const me = await request(app).get("/api/auth/me").set("Authorization", "Bearer " + login.body.token).expect(200);
expect(me.body.user.hasPassword).toBe(true);
const change = await request(app)
.patch("/api/auth/change-password")
.set("Authorization", "Bearer " + login.body.token)
.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 () => {
mailer.sendMail.mockResolvedValue(true);
const user = await verifierProfilGoogle();
expect(user.email).toBe(EMAIL);
expect(mailer.sendMail).not.toHaveBeenCalled();
});
});
let ctxToken;