// Mocks the admin API via cy.intercept — no live backend. const adminBody = { id: 1, email: "ops@example.com", name: "Ops", createdAt: "2026-08-01T00:00:00.000Z", lastLoginAt: "2026-08-28T09:00:00.000Z", }; /** A 3-day series helper for the fixture. */ function series(counts: number[]) { return counts.map((count, i) => ({ date: `2026-08-${String(10 + i).padStart(2, "0")}`, count, })); } function metricsFixture() { return { generatedAt: "2026-08-28T09:00:00.000Z", rangeDays: 30, snapshot: { admins: 2, users: 42, households: 15, activeHouseholds: 9, recipes: 120, recipesManual: 30, recipesImported: 90, recipesBySource: [ { key: "themealdb", label: "TheMealDB", count: 60 }, { key: "marmiton", label: "Marmiton", count: 30 }, ], plannings: 18, planningItems: 210, steps: 640, detectedTechniques: 900, favorites: 55, corrections: 12, correctionsUnconsumed: 4, correctionsRemoval: 2, trainingSuggestions: 8, trainingSuggestionsByStatus: [{ key: "pending", label: "pending", count: 8 }], trainingSuggestionsBySourceType: [{ key: "correction", label: "correction", count: 8 }], }, series: { signups: series([1, 3, 2]), recipesCreated: series([0, 2, 1]), planningItemsAdded: series([4, 1, 5]), correctionsSubmitted: series([0, 0, 1]), trainingSuggestions: series([0, 1, 0]), }, events: [{ type: "user.signup", buckets: series([1, 3, 2]) }], }; } describe("Admin dashboard", () => { beforeEach(() => { cy.viewport(1400, 900); cy.intercept("GET", "**/admin/auth/me", { statusCode: 200, body: adminBody }); }); it("renders KPI tiles, a chart per series, and the breakdown lists", () => { cy.intercept("GET", "**/admin/metrics*", { statusCode: 200, body: metricsFixture() }).as( "getMetrics", ); cy.visit("/admin"); cy.wait("@getMetrics").its("request.url").should("include", "days=30"); // KPI tiles — value + label. cy.contains(".kpi-tile", "Utilisateurs").should("contain.text", "42"); cy.contains(".kpi-tile", "Recettes importées").should("contain.text", "90"); cy.contains(".kpi-tile", "Corrections à traiter").should("contain.text", "4"); // One chart card per instrumented series. cy.get(".chart-card").should("have.length", 5); cy.contains(".chart-card", "Inscriptions").should("contain.text", "6 sur 30 j"); // Breakdown lists. cy.contains(".breakdown", "Recettes importées par source") .should("contain.text", "TheMealDB") .and("contain.text", "Marmiton"); cy.contains(".breakdown", "Évènements enregistrés").should("contain.text", "user.signup"); }); it("shows an error state when the metrics request fails", () => { cy.intercept("GET", "**/admin/metrics*", { statusCode: 500, body: { code: 5000, message: "x" }, }); cy.visit("/admin"); cy.contains("Impossible de charger").should("be.visible"); }); });