/// interface MailpitAddress { Address: string; Name: string; } interface MailpitMessageSummary { ID: string; From: MailpitAddress; To: MailpitAddress[]; Subject: string; Snippet: string; Created: string; } interface MailpitMessages { total: number; unread: number; count: number; messages: MailpitMessageSummary[]; } interface MailpitMessageBody { ID: string; Text: string; HTML: string; } interface SignupArgs { email: string; displayName: string; } interface ConfirmArgs { token: string; password: string; } interface LoginArgs { email: string; password: string; } interface SeedConfirmedUserArgs { email: string; displayName: string; password: string; } type SeedAdminArgs = SeedConfirmedUserArgs; interface SeedPostArgs { email: string; password: string; title: string; body: string; } interface CreatedPost { id: number; userId: number; authorDisplayName: string; title: string; body: string; createdAt: string; featureSlot: number | null; } interface SeedFeaturedPostArgs { adminEmail: string; adminPassword: string; postId: number; slot: number; } declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { interface Chainable { resetDb(): Chainable; seedDb(): Chainable; promoteAdmin(email: string): Chainable; clearMail(): Chainable; getMail(): Chainable; getMailBody(id: string): Chainable; signupViaApi(args: SignupArgs): Chainable; fetchLatestConfirmToken(email: string): Chainable; confirmViaApi(args: ConfirmArgs): Chainable; loginViaApi(args: LoginArgs): Chainable; logoutViaApi(): Chainable; seedConfirmedUser(args: SeedConfirmedUserArgs): Chainable; seedAdmin(args: SeedAdminArgs): Chainable; seedPostAs(args: SeedPostArgs): Chainable; seedFeaturedPost(args: SeedFeaturedPostArgs): Chainable; } } } const apiBase = "/api"; Cypress.Commands.add("resetDb", function () { return cy.task("db:reset"); }); Cypress.Commands.add("seedDb", function () { return cy.task("db:seed"); }); Cypress.Commands.add("promoteAdmin", function (email: string) { return cy.task("db:promote", email); }); Cypress.Commands.add("clearMail", function () { return cy.task("mailpit:clear"); }); Cypress.Commands.add("getMail", function () { return cy.task("mailpit:messages"); }); Cypress.Commands.add("getMailBody", function (id: string) { return cy.task("mailpit:message", id); }); Cypress.Commands.add("signupViaApi", function (args: SignupArgs) { cy.request({ method: "POST", url: `${apiBase}/signup`, body: { email: args.email, displayName: args.displayName }, }).then(function (response) { expect(response.status).to.equal(201); }); }); Cypress.Commands.add("fetchLatestConfirmToken", function (email: string) { return cy.getMail().then(function (inbox) { const match = inbox.messages.find(function (message) { return message.To.some(function (to) { return to.Address.toLowerCase() === email.toLowerCase(); }); }); if (!match) { throw new Error(`no mailpit message for ${email}`); } return cy.getMailBody(match.ID).then(function (body) { const text = body.Text || body.HTML; const tokenMatch = text.match(/token=([a-f0-9]+)/); if (!tokenMatch) { throw new Error("confirmation token not found in mail body"); } return tokenMatch[1]; }); }); }); Cypress.Commands.add("confirmViaApi", function (args: ConfirmArgs) { cy.request({ method: "POST", url: `${apiBase}/confirm-email`, body: { token: args.token, password: args.password }, }).then(function (response) { expect(response.status).to.equal(200); }); }); Cypress.Commands.add("loginViaApi", function (args: LoginArgs) { cy.request({ method: "POST", url: `${apiBase}/login`, body: { email: args.email, password: args.password }, }).then(function (response) { expect(response.status).to.equal(200); }); }); Cypress.Commands.add("logoutViaApi", function () { cy.request({ method: "POST", url: `${apiBase}/logout`, failOnStatusCode: false, }); }); Cypress.Commands.add( "seedConfirmedUser", function (args: SeedConfirmedUserArgs) { cy.signupViaApi({ email: args.email, displayName: args.displayName }); cy.fetchLatestConfirmToken(args.email).then(function (token) { cy.confirmViaApi({ token, password: args.password }); }); cy.logoutViaApi(); }, ); Cypress.Commands.add("seedAdmin", function (args: SeedAdminArgs) { cy.seedConfirmedUser({ email: args.email, displayName: args.displayName, password: args.password, }); cy.promoteAdmin(args.email); }); Cypress.Commands.add("seedPostAs", function (args: SeedPostArgs) { cy.loginViaApi({ email: args.email, password: args.password }); return cy .request({ method: "POST", url: `${apiBase}/posts`, body: { title: args.title, body: args.body }, }) .then(function (response) { expect(response.status).to.equal(201); cy.logoutViaApi(); return response.body.post as CreatedPost; }); }); Cypress.Commands.add( "seedFeaturedPost", function (args: SeedFeaturedPostArgs) { cy.loginViaApi({ email: args.adminEmail, password: args.adminPassword }); cy.request({ method: "POST", url: `${apiBase}/admin/posts/feature`, body: { postId: args.postId, slot: args.slot }, }).then(function (response) { expect(response.status).to.equal(200); }); cy.logoutViaApi(); }, ); export {};