betagouv/service-national-universel

View on GitHub
api/src/__tests__/young-auth.test.ts

Summary

Maintainability
F
1 wk
Test Coverage
import request from "supertest";
import getAppHelper from "./helpers/app";
import getNewYoungFixture from "./fixtures/young";
import { createYoungHelper, getYoungByIdHelper } from "./helpers/young";
import { dbConnect, dbClose } from "./helpers/db";
import { fakerFR as faker } from "@faker-js/faker";
import crypto from "crypto";
import { createCohortHelper } from "./helpers/cohort";
import getNewCohortFixture from "./fixtures/cohort";
import { createFixtureClasse } from "./fixtures/classe";
import { ClasseModel } from "../models";
import { YOUNG_SOURCE, YOUNG_STATUS } from "snu-lib";

const VALID_PASSWORD = faker.internet.password(16, false, /^[a-z]*$/, "AZ12/+");

jest.mock("../brevo", () => ({
  ...jest.requireActual("../brevo"),
  sendEmail: () => Promise.resolve(),
}));

beforeAll(dbConnect);
afterAll(dbClose);

describe("Young Auth", () => {
  let res;
  describe("POST /young/signin", () => {
    it("should return 400 when no email, no password or wrong email", async () => {
      res = await request(getAppHelper()).post("/young/signin");
      expect(res.status).toBe(400);

      res = await request(getAppHelper()).post("/young/signin").send({ email: "foo@bar.fr" });
      expect(res.status).toBe(400);

      res = await request(getAppHelper()).post("/young/signin").send({ email: "foo", password: "bar" });
      expect(res.status).toBe(400);

      res = await request(getAppHelper()).post("/young/signin").send({ password: "foo" });
      expect(res.status).toBe(400);
    });
    it("should return 401 when user does not exists", async () => {
      const res = await request(getAppHelper()).post("/young/signin").send({ email: "foo@bar.fr", password: "bar" });
      expect(res.status).toBe(401);
    });
    it("should return 401 when user is deleted", async () => {
      const user = await createYoungHelper({ ...getNewYoungFixture(), status: "DELETED", password: "bar" });
      const res = await request(getAppHelper()).post("/young/signin").send({ email: user.email, password: "bar" });
      expect(res.status).toBe(401);
    });
    it("should return 401 if password does not match", async () => {
      const user = await createYoungHelper({ ...getNewYoungFixture(), password: "bar" });
      const res = await request(getAppHelper()).post("/young/signin").send({ email: user.email, password: "foo" });
      expect(res.status).toBe(401);
    });
    it("should return 200 and a token when user exists and password match", async () => {
      const fixture = getNewYoungFixture();
      const user = await createYoungHelper({ ...fixture, password: "bar", email: fixture.email?.toLowerCase() });
      const res = await request(getAppHelper()).post("/young/signin").send({ email: user.email, password: "bar" });
      expect(res.status).toBe(200);
    });
  });
  describe("POST /young/signup", () => {
    it("should return 400 when all the fields are note defined or not well informed", async () => {
      const fixture = getNewYoungFixture();
      res = await request(getAppHelper()).post("/young/signup");
      expect(res.status).toBe(400);

      res = await request(getAppHelper()).post("/young/signup").send({ email: "foo@bar.fr" });
      expect(res.status).toBe(400);

      res = await request(getAppHelper()).post("/young/signup").send({ email: "foo@bar.fr" });
      expect(res.status).toBe(400);

      res = await request(getAppHelper()).post("/young/signup").send({ email: "foo", password: "bar" });
      expect(res.status).toBe(400);

      res = await request(getAppHelper()).post("/young/signup").send({ email: "foo", password: "bar" });
      expect(res.status).toBe(400);

      res = await request(getAppHelper()).post("/young/signup").send({
        email: "foo",
        password: "bar",
        birthdateAt: fixture.birthdateAt,
      });
      expect(res.status).toBe(400);

      res = await request(getAppHelper()).post("/young/signup").send({
        email: "foo",
        password: "bar",
        birthdateAt: fixture.birthdateAt,
        frenchNationality: "false",
        schooled: "false",
        cohort: "false",
      });
      expect(res.status).toBe(400);
    });

    it("should return 400 when password does not match requirments", async () => {
      const fixture = getNewYoungFixture();
      const email = fixture.email?.toLowerCase();
      res = await request(getAppHelper()).post("/young/signup").send({ email, password: "bar", firstName: "foo", lastName: "bar", birthdateAt: fixture.birthdateAt });
      expect(res.status).toBe(400);
    });

    // TODO - Put this code back next time a cohort is opened

    it.skip("should return 200", async () => {
      const fixture = getNewYoungFixture();
      const email = fixture.email?.toLowerCase();
      res = await request(getAppHelper()).post("/young/signup").send({
        email: email,
        firstName: "foo",
        lastName: "bar",
        password: VALID_PASSWORD,
        birthdateAt: fixture.birthdateAt,
        schoolRegion: fixture.schoolRegion,
        grade: fixture.grade,
        frenchNationality: fixture.frenchNationality,
        schooled: fixture.schooled,
        cohort: fixture.cohort,
      });
      expect(res.status).toBe(200);
      expect(res.body.token).toBeTruthy();
    });

    it.skip("should transform firstName and lastName", async () => {
      const fixture = getNewYoungFixture();
      res = await request(getAppHelper()).post("/young/signup").send({
        email: fixture.email,
        firstName: "foo",
        lastName: "bar",
        password: VALID_PASSWORD,
        birthdateAt: fixture.birthdateAt,
        schoolRegion: fixture.schoolRegion,
        grade: fixture.grade,
        frenchNationality: fixture.frenchNationality,
        schooled: fixture.schooled,
        cohort: fixture.cohort,
      });
      expect(res.body.user.firstName).toBe("Foo");
      expect(res.body.user.lastName).toBe("BAR");
      expect(res.body.user.email).toBe(fixture.email?.toLowerCase());
    });

    it("should return 409 when user already exists", async () => {
      const fixture = getNewYoungFixture();
      const email = fixture.email?.toLowerCase();
      const young = await createYoungHelper({ ...fixture, email });
      const cohort = await createCohortHelper({ ...getNewCohortFixture(), name: young.cohort });
      res = await request(getAppHelper()).post("/young/signup").set("x-user-timezone", "-60").send({
        email: email,
        phone: fixture.phone,
        phoneZone: fixture.phoneZone,
        firstName: "foo",
        lastName: "bar",
        password: VALID_PASSWORD,
        birthdateAt: fixture.birthdateAt,
        schoolRegion: fixture.schoolRegion,
        grade: fixture.grade,
        frenchNationality: fixture.frenchNationality,
        schooled: fixture.schooled,
        cohort: cohort.name,
      });
      expect(res.status).toBe(409);
    });

    it("should return 409 when the number of users in the class exceeds the total seats", async () => {
      const fixture = getNewYoungFixture();
      const email = fixture.email?.toLowerCase();
      const classe = await ClasseModel.create({ ...createFixtureClasse(), totalSeats: 1 });
      await createYoungHelper({ ...fixture, email, classeId: classe._id, status: YOUNG_STATUS.VALIDATED });
      res = await request(getAppHelper()).post("/young/signup").send({
        email: "newuser@example.com",
        phone: fixture.phone,
        phoneZone: fixture.phoneZone,
        firstName: "new",
        lastName: "user",
        password: VALID_PASSWORD,
        birthdateAt: fixture.birthdateAt,
        grade: fixture.grade,
        frenchNationality: fixture.frenchNationality,
        classeId: classe._id,
        source: YOUNG_SOURCE.CLE,
      });
      expect(res.status).toBe(409);
    });
  });
  describe("POST /young/logout", () => {
    it("should return 200", async () => {
      const young = await createYoungHelper({ ...getNewYoungFixture(), password: VALID_PASSWORD });
      const passport = require("passport");
      const previous = passport.user;
      passport.user = young;
      const res = await request(getAppHelper()).post("/young/logout");
      expect(res.status).toBe(200);
      passport.user = previous;
    });
  });

  describe("GET /young/signin_token", () => {
    it("should return 200", async () => {
      const young = await createYoungHelper(getNewYoungFixture());
      const passport = require("passport");
      const previous = passport.user;
      passport.user = young;
      passport.user.set = jest.fn();
      passport.user.save = jest.fn();
      const res = await request(getAppHelper()).get("/young/signin_token").set("Cookie", ["jwt_young=blah"]);
      expect(res.status).toBe(200);
      expect(passport.user.set).toHaveBeenCalled();
      expect(passport.user.save).toHaveBeenCalled();
      passport.user = previous;
    });
  });

  describe("POST /young/reset_password", () => {
    it("should return return 400 when missing password", async () => {
      res = await request(getAppHelper()).post("/young/reset_password");
      expect(res.status).toBe(400);

      res = await request(getAppHelper()).post("/young/reset_password").send({ password: "bar" });
      expect(res.status).toBe(400);

      res = await request(getAppHelper()).post("/young/reset_password").send({ password: "bar", newPassword: "baz" });
      expect(res.status).toBe(400);

      res = await request(getAppHelper()).post("/young/reset_password").send({ verifyPassword: "bar", newPassword: "baz" });
      expect(res.status).toBe(400);
    });

    it("should return return 400 when password does not meet security requirement", async () => {
      res = await request(getAppHelper()).post("/young/reset_password").send({ password: "bar", verifyPassword: "baz", newPassword: "baz" });
      expect(res.status).toBe(400);
    });

    it("should return 401 when new password is identical as last password", async () => {
      const young = await createYoungHelper({ ...getNewYoungFixture(), password: VALID_PASSWORD });
      const passport = require("passport");
      const previous = passport.user;
      passport.user = young;
      res = await request(getAppHelper()).post("/young/reset_password").send({ password: VALID_PASSWORD, verifyPassword: VALID_PASSWORD, newPassword: VALID_PASSWORD });
      expect(res.status).toBe(401);
      passport.user = previous;
    });

    it("should return return 401 when original password does not match", async () => {
      const young = await createYoungHelper({ ...getNewYoungFixture(), password: "foo" });
      const passport = require("passport");
      const previous = passport.user;
      passport.user = young;
      res = await request(getAppHelper()).post("/young/reset_password").send({ password: "bar", verifyPassword: VALID_PASSWORD, newPassword: VALID_PASSWORD });
      expect(res.status).toBe(401);
      passport.user = previous;
    });

    it("should return return 422 when verifyPassword !== newPassword", async () => {
      const young = await createYoungHelper({ ...getNewYoungFixture(), password: "foo" });
      const passport = require("passport");
      const previous = passport.user;
      passport.user = young;
      res = await request(getAppHelper())
        .post("/young/reset_password")
        .send({ password: "foo", verifyPassword: VALID_PASSWORD, newPassword: VALID_PASSWORD + "HOP" });
      expect(res.status).toBe(422);
      passport.user = previous;
    });

    it("should return return 200 when password is changed", async () => {
      const young = await createYoungHelper({ ...getNewYoungFixture(), password: "foo" });
      const passport = require("passport");
      const previous = passport.user;
      passport.user = young;
      res = await request(getAppHelper()).post("/young/reset_password").send({ password: "foo", verifyPassword: VALID_PASSWORD, newPassword: VALID_PASSWORD });
      expect(res.status).toBe(200);
      passport.user = previous;
    });
  });

  describe("POST /young/forgot_password", () => {
    it("should return return 404 when missing email", async () => {
      res = await request(getAppHelper()).post("/young/forgot_password");
      expect(res.status).toBe(404);
    });
    it("should return 200 when user does not exist", async () => {
      const res = await request(getAppHelper()).post("/young/forgot_password").send({ email: "foo@bar.fr" });
      expect(res.status).toBe(200);
    });
    it("should return return 200 when user exists", async () => {
      const fixture = getNewYoungFixture();
      const young = await createYoungHelper({ ...fixture, email: fixture.email?.toLowerCase() });
      const res = await request(getAppHelper()).post("/young/forgot_password").send({ email: young.email });
      expect(res.status).toBe(200);
    });
  });

  describe("POST /young/forgot_password_reset", () => {
    it("should return return 400 when missing token or password", async () => {
      res = await request(getAppHelper()).post("/young/forgot_password_reset").send({ token: "foo" });
      expect(res.status).toBe(400);

      res = await request(getAppHelper()).post("/young/forgot_password_reset").send({ password: "bar" });
      expect(res.status).toBe(400);
    });
    it("should return return 400 when password is not secure", async () => {
      const res = await request(getAppHelper()).post("/young/forgot_password_reset").send({ password: "bar", token: "foo" });
      expect(res.status).toBe(400);
    });
    it("should return return 400 when user is not found", async () => {
      const res = await request(getAppHelper()).post("/young/forgot_password_reset").send({ password: VALID_PASSWORD, token: "foo" });
      expect(res.status).toBe(400);
    });
    it("should return return 400 when forgotPasswordResetExpires is expired", async () => {
      const fixture = getNewYoungFixture();
      const token = await crypto.randomBytes(20).toString("hex");
      await createYoungHelper({
        ...fixture,
        email: fixture.email?.toLowerCase(),
        forgotPasswordResetExpires: Date.now() - 1000 * 60 * 60 * 24 * 7,
        forgotPasswordResetToken: token,
      });
      const res = await request(getAppHelper()).post("/young/forgot_password_reset").send({ password: VALID_PASSWORD, token: token });
      expect(res.status).toBe(400);
      expect(res.body.code).toBe("PASSWORD_TOKEN_EXPIRED_OR_INVALID");
    });

    it("should return 401 when new password is identical as last password", async () => {
      const fixture = getNewYoungFixture();
      const token = await crypto.randomBytes(20).toString("hex");
      await createYoungHelper({
        ...fixture,
        email: fixture.email?.toLowerCase(),
        forgotPasswordResetExpires: Date.now() + 1000 * 60 * 60 * 24 * 7,
        forgotPasswordResetToken: token,
        password: VALID_PASSWORD,
      });
      const res = await request(getAppHelper()).post("/young/forgot_password_reset").send({ password: VALID_PASSWORD, token });
      expect(res.status).toBe(401);
    });

    it("should return return 200 otherwise", async () => {
      const fixture = getNewYoungFixture();
      const token = await crypto.randomBytes(20).toString("hex");
      const young = await createYoungHelper({
        ...fixture,
        email: fixture.email?.toLowerCase(),
        forgotPasswordResetExpires: Date.now() + 1000 * 60 * 60 * 24 * 7,
        forgotPasswordResetToken: token,
      });
      const res = await request(getAppHelper()).post("/young/forgot_password_reset").send({ password: VALID_PASSWORD, token });
      expect(res.status).toBe(200);

      const updatedYoung = await getYoungByIdHelper(young.id);
      expect(updatedYoung?.forgotPasswordResetExpires).toBeFalsy();
      expect(updatedYoung?.forgotPasswordResetToken).toBeFalsy();
    });
  });
});