// 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", }; function monitoringFixture() { return { generatedAt: "2026-08-28T09:15:00.000Z", services: [ { key: "postgres", status: "up", latencyMs: 3.2, detail: null, checkedAt: "2026-08-28T09:15:00.000Z", }, { key: "api", status: "up", latencyMs: 0, detail: "uptime 3 h 12 min · RSS 120 Mo", checkedAt: "2026-08-28T09:15:00.000Z", }, { key: "intent-service", status: "down", latencyMs: null, detail: "fetch failed", checkedAt: "2026-08-28T09:15:00.000Z", }, { key: "tech-step-llm-worker", status: "degraded", latencyMs: null, detail: "dernier battement il y a 9 j", checkedAt: "2026-08-28T09:15:00.000Z", lastRunAt: "2026-08-19T03:00:00.000Z", lastResult: { job: "audit-low-confidence", ok: true, counts: { suggestions: 2 } }, }, ], }; } describe("Admin monitoring", () => { beforeEach(() => { cy.viewport(1400, 900); cy.intercept("GET", "**/admin/auth/me", { statusCode: 200, body: adminBody }); }); // The page route `/admin/monitoring` and the API path `GET /admin/monitoring` // are identical — a plain `**/admin/monitoring` glob also matches the // `cy.visit()` document request (and whether that's same-origin or not // depends on `VITE_API_URL`, which isn't set in CI). // // This used to be disambiguated with a declarative `resourceType: "fetch"` // route matcher, but that stopped working (Cypress 13.17.0 on this // machine's Chrome 152): the *declarative* `resourceType` matcher no // longer catches this request at all — confirmed by logging every request // Cypress hands to a `{resourceType: "fetch"}` intercept, which only ever // saw `/admin/auth/me`, never `/admin/monitoring` — even though the very // same request's own `req.resourceType`, read *inside* a handler matched // by url/method alone, reliably reads back `"fetch"`. So the request *is* // correctly typed; only the pre-filtering by that type is broken. Reading // `req.resourceType` inside the handler and branching manually (as below) // isn't affected by that bug and reproduced correctly in every check. function interceptMonitoringApi(response: { statusCode: number; body: unknown }) { return cy.intercept("GET", "**/admin/monitoring", (req) => { if (req.resourceType === "document") { req.continue(); return; } req.reply(response); }); } it("renders one card per service with its status and details", () => { interceptMonitoringApi({ statusCode: 200, body: monitoringFixture() }).as("getMonitoring"); cy.visit("/admin/monitoring"); cy.wait("@getMonitoring"); cy.get(".monitoring-card").should("have.length", 4); cy.contains(".monitoring-card", "Base de données") .should("have.class", "monitoring-card--up") .and("contain.text", "3.2 ms"); cy.contains(".monitoring-card", "Service NLP (spaCy)") .should("have.class", "monitoring-card--down") .and("contain.text", "Hors service"); cy.contains(".monitoring-card", "Worker LLM") .should("have.class", "monitoring-card--degraded") .and("contain.text", "audit-low-confidence"); }); it("shows an error state when the request fails", () => { // Aliased and waited on (unlike the other tests' plain intercepts) so // this genuinely proves the 500 came from the mock — without a live API // server, an unmatched request fails at the network level too, which // `MonitoringPage`'s `.catch()` can't tell apart from a real 500 (see // the comment above `interceptMonitoringApi`, this is exactly the class // of bug that let this test pass for the wrong reason before). interceptMonitoringApi({ statusCode: 500, body: { code: 5000, message: "x" } }).as( "getMonitoringError", ); cy.visit("/admin/monitoring"); cy.wait("@getMonitoringError"); cy.contains("Impossible de charger").should("be.visible"); }); });