// Mocks the admin API via cy.intercept — no live backend (see // .github/workflows/ci.yml); apps/api's Mocha suite covers // `GET /admin/api/contact` and the Forgejo routes against a real database. const adminBody = { id: 1, email: "ops@example.com", name: "Ops", createdAt: "2026-08-01T00:00:00.000Z", lastLoginAt: "2026-08-28T09:00:00.000Z", }; // Matches the list endpoint (`/contact`, `/contact?type=…`) but NOT the // nested `/contact/forgejo/status` or `/contact/:id/forgejo-issue`. const LIST_URL = /\/admin\/api\/contact(\?|$)/; function messages() { return [ { id: 2, type: "bug", message: "Le bouton Envoyer ne réagit pas sur la page planning.", pageUrl: "/", userAgent: "Mozilla/5.0 (Linux; Android 14) BatchCookingApp/0.2.1", appVersion: "0.2.1", createdAt: "2026-09-07T10:00:00.000Z", author: { id: 9, firstName: "Alice", lastName: "Martin", email: "alice@example.com" }, forgejoIssueNumber: null, forgejoIssueUrl: null, }, { id: 1, type: "suggestion", message: "Ajoutez un mode hors-ligne pour la liste de courses.", pageUrl: "/liste-de-courses", userAgent: null, appVersion: null, createdAt: "2026-09-06T08:30:00.000Z", author: null, forgejoIssueNumber: null, forgejoIssueUrl: null, }, ]; } describe("Admin contact messages (/admin/messages)", () => { beforeEach(() => { cy.viewport(1400, 1000); cy.intercept("GET", "**/admin/api/auth/me", { statusCode: 200, body: adminBody }); // Default: Forgejo integration off — individual tests override. cy.intercept("GET", "**/admin/api/contact/forgejo/status", { statusCode: 200, body: { enabled: false }, }).as("forgejoStatus"); }); it("lists the messages read-only: sender or « Compte supprimé », type badge, message", () => { cy.intercept("GET", LIST_URL, { statusCode: 200, body: messages() }).as("getMessages"); cy.visit("/admin/messages"); cy.wait("@getMessages"); cy.get(".messages-table tbody tr").should("have.length", 2); cy.contains(".messages-table tr", "page planning").should("contain.text", "alice@example.com"); cy.contains(".messages-table tr", "mode hors-ligne").should("contain.text", "Compte supprimé"); // Integration off ⇒ no create button, and the "not configured" note shows. cy.get(".messages-table__ticket-button").should("not.exist"); cy.contains(".messages-forgejo-note", "non configurée").should("be.visible"); }); it("filters by type through the query param", () => { cy.intercept("GET", "**/admin/api/contact", { statusCode: 200, body: messages() }).as("all"); cy.intercept("GET", "**/admin/api/contact?type=suggestion", { statusCode: 200, body: [messages()[1]], }).as("filtered"); cy.visit("/admin/messages"); cy.wait("@all"); cy.get(".messages-filters select").select("Suggestion"); cy.wait("@filtered"); cy.get(".messages-table tbody tr").should("have.length", 1); cy.contains(".messages-table tr", "mode hors-ligne").should("be.visible"); }); it("creates a Forgejo issue from a message through the pre-filled modal", () => { cy.intercept("GET", "**/admin/api/contact/forgejo/status", { statusCode: 200, body: { enabled: true }, }).as("forgejoStatus"); cy.intercept("GET", LIST_URL, { statusCode: 200, body: messages() }).as("getMessages"); cy.intercept("POST", "**/admin/api/contact/2/forgejo-issue", { statusCode: 200, body: { ...messages()[0], forgejoIssueNumber: 101, forgejoIssueUrl: "https://git.example/kyuno/batchCooking/issues/101", }, }).as("createIssue"); cy.visit("/admin/messages"); cy.wait("@getMessages"); cy.contains(".messages-table tr", "page planning") .find(".messages-table__ticket-button") .click(); // Modal opens pre-filled from the message. cy.get("#forgejoIssueTitle").should( "have.value", "[Bug] Le bouton Envoyer ne réagit pas sur la page planning.", ); cy.get("#forgejoIssueBody").should("contain.value", "Signalé par"); cy.contains(".dialog-panel__footer button", "Créer le ticket").click(); cy.wait("@createIssue").its("request.body").should("have.keys", ["title", "body"]); // Modal closed, the row now shows the link instead of the button. cy.get("#forgejoIssueTitle").should("not.exist"); cy.contains(".messages-table tr", "page planning") .find("a") .should("have.attr", "href", "https://git.example/kyuno/batchCooking/issues/101") .and("contain.text", "#101"); }); it("shows the existing link and no create button for an already-linked message", () => { cy.intercept("GET", "**/admin/api/contact/forgejo/status", { statusCode: 200, body: { enabled: true }, }); const linked = messages().map((m) => m.id === 2 ? { ...m, forgejoIssueNumber: 55, forgejoIssueUrl: "https://git.example/kyuno/batchCooking/issues/55", } : m, ); cy.intercept("GET", LIST_URL, { statusCode: 200, body: linked }).as("getMessages"); cy.visit("/admin/messages"); cy.wait("@getMessages"); cy.contains(".messages-table tr", "page planning").within(() => { cy.contains("a", "#55").should( "have.attr", "href", "https://git.example/kyuno/batchCooking/issues/55", ); cy.get(".messages-table__ticket-button").should("not.exist"); }); // The still-unlinked suggestion row keeps its button. cy.contains(".messages-table tr", "mode hors-ligne") .find(".messages-table__ticket-button") .should("be.visible"); }); });