import { ErrorCode } from "@batch-cooking/shared"; // Mocks the API via cy.intercept — this job doesn't run a live backend (see // .github/workflows/ci.yml), and it keeps these specs focused on frontend // behavior. Backend behavior itself is covered by apps/api's Mocha/Cucumber // suites against a real database. describe("Signup", () => { it("creates a profile and starts the onboarding wizard (regime/household/allergens)", () => { cy.intercept("GET", "**/auth/me", { statusCode: 401 }); // The onboarding wizard's first step (see onboarding.cy.ts for the full // walkthrough) is the regime step, which fetches the reference list. cy.intercept("GET", "**/reference/diets", { statusCode: 200, body: [] }); cy.intercept("POST", "**/auth/signup", { statusCode: 201, body: { id: 1, firstName: "Alice", lastName: "Martin", email: "alice@example.com", tokenVersion: 0, houseId: null, dietId: null, }, }).as("signup"); cy.visit("/signup"); cy.get("#firstName").type("Alice"); cy.get("#lastName").type("Martin"); cy.get("#email").type("alice@example.com"); cy.get("#password").type("correct-horse-battery-staple"); cy.contains("button", "Créer mon profil").click(); cy.wait("@signup"); // Not the home page directly — signup hands off to the onboarding // wizard first (RedirectIfAuthenticated no longer applies here, it's a // RequireAuth-gated route of its own, see App.tsx). cy.url().should("include", "/onboarding/regime"); cy.contains("Étape 1 sur 3").should("be.visible"); }); it("shows a client-side validation error without calling the API", () => { cy.intercept("GET", "**/auth/me", { statusCode: 401 }); cy.intercept("POST", "**/auth/signup").as("signup"); cy.visit("/signup"); cy.get("#firstName").type("A"); cy.get("#lastName").type("B"); cy.get("#email").type("a@example.com"); cy.get("#password").type("short"); cy.contains("button", "Créer mon profil").click(); cy.contains("8 caractères minimum").should("be.visible"); cy.get("@signup.all").should("have.length", 0); }); it("shows the API's error when the email is already taken", () => { cy.intercept("GET", "**/auth/me", { statusCode: 401 }); cy.intercept("POST", "**/auth/signup", { statusCode: 409, body: { code: ErrorCode.EMAIL_ALREADY_IN_USE, message: "Email already in use" }, }).as("signup"); cy.visit("/signup"); cy.get("#firstName").type("Alice"); cy.get("#lastName").type("Martin"); cy.get("#email").type("alice@example.com"); cy.get("#password").type("correct-horse-battery-staple"); cy.contains("button", "Créer mon profil").click(); cy.wait("@signup"); cy.contains("Cet email est déjà utilisé").should("be.visible"); }); }); describe("Login", () => { it("logs in and lands on the home page", () => { cy.intercept("GET", "**/auth/me", { statusCode: 401 }); cy.intercept("GET", "**/planning/current", { statusCode: 200, body: null }); cy.intercept("POST", "**/auth/login", { statusCode: 200, body: { id: 1, firstName: "Alice", lastName: "Martin", email: "alice@example.com", tokenVersion: 0, houseId: 1, dietId: null, }, }).as("login"); cy.visit("/login"); cy.get("#email").type("alice@example.com"); cy.get("#password").type("correct-horse-battery-staple"); cy.contains("button", "Se connecter").click(); cy.wait("@login"); cy.contains("Bonjour Alice").should("be.visible"); }); it("shows an error on invalid credentials", () => { cy.intercept("GET", "**/auth/me", { statusCode: 401 }); cy.intercept("POST", "**/auth/login", { statusCode: 401, body: { code: ErrorCode.INVALID_CREDENTIALS, message: "Invalid email or password" }, }).as("login"); cy.visit("/login"); cy.get("#email").type("alice@example.com"); cy.get("#password").type("wrong-password"); cy.contains("button", "Se connecter").click(); cy.wait("@login"); cy.contains("Email ou mot de passe incorrect").should("be.visible"); }); }); describe("Already authenticated", () => { it("redirects away from /login to the home page", () => { cy.intercept("GET", "**/auth/me", { statusCode: 200, body: { id: 1, firstName: "Alice", lastName: "Martin", email: "alice@example.com", tokenVersion: 0, houseId: 1, dietId: null, }, }); cy.intercept("GET", "**/planning/current", { statusCode: 200, body: null }); cy.visit("/login"); cy.url().should("not.include", "/login"); cy.contains("Bonjour Alice").should("be.visible"); }); it("logs out and returns to the login page", () => { cy.intercept("GET", "**/auth/me", { statusCode: 200, body: { id: 1, firstName: "Alice", lastName: "Martin", email: "alice@example.com", tokenVersion: 0, houseId: 1, dietId: null, }, }); cy.intercept("GET", "**/planning/current", { statusCode: 200, body: null }); cy.intercept("POST", "**/auth/logout", { statusCode: 204 }).as("logout"); cy.visit("/"); // "Se déconnecter" lives inside the account menu, opened by clicking // the greeting button — see AppLayout.tsx's AccountMenu. cy.contains("button", "Bonjour Alice").click(); cy.contains("button", "Se déconnecter").click(); cy.wait("@logout"); cy.url().should("include", "/login"); }); });