mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 05:58:18 +00:00
feat(e2e): add freight e2e docker-compose stack with isolated services
This commit is contained in:
95
e2e/freight/cypress/support/commands.ts
Normal file
95
e2e/freight/cypress/support/commands.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Auth model (see apps/edr-freight-api + freight web apps):
|
||||
* - POST {api}/api/auth/login { email, password }
|
||||
* → flattened body { success, token, refreshToken } (response interceptor
|
||||
* flattens /api/auth responses — no .data nesting).
|
||||
* - Both web apps read cookies `auth-token` / `refresh-token` and attach
|
||||
* `Authorization: Bearer <token>`.
|
||||
* - Cookies are port-agnostic on localhost, so portal and backoffice share
|
||||
* one cookie jar. cy.session snapshots/restores cookies per session id,
|
||||
* which keeps staff and customer sessions from clobbering each other —
|
||||
* but inside a single test, switching apps requires re-invoking the
|
||||
* matching login command first (see flows specs).
|
||||
*/
|
||||
|
||||
export interface LoginBody {
|
||||
success: boolean;
|
||||
token: string;
|
||||
refreshToken: string;
|
||||
}
|
||||
|
||||
const apiUrl = () => Cypress.env("apiUrl") as string;
|
||||
const password = () => Cypress.env("defaultPassword") as string;
|
||||
|
||||
function apiLogin(email: string, pass?: string): Cypress.Chainable<LoginBody> {
|
||||
return cy
|
||||
.request<LoginBody>("POST", `${apiUrl()}/api/auth/login`, {
|
||||
email,
|
||||
password: pass ?? password(),
|
||||
})
|
||||
.then((response) => {
|
||||
expect(response.status).to.eq(201);
|
||||
expect(response.body.token, "login token").to.be.a("string");
|
||||
return cy.wrap(response.body, { log: false });
|
||||
});
|
||||
}
|
||||
|
||||
function sessionFor(app: "backoffice" | "portal", email: string, pass?: string) {
|
||||
cy.session(
|
||||
[app, email],
|
||||
() => {
|
||||
apiLogin(email, pass).then(({ token, refreshToken }) => {
|
||||
cy.setCookie("auth-token", token);
|
||||
cy.setCookie("refresh-token", refreshToken);
|
||||
});
|
||||
},
|
||||
{
|
||||
cacheAcrossSpecs: true,
|
||||
validate() {
|
||||
cy.getCookie("auth-token").then((cookie) => {
|
||||
expect(cookie, "auth-token cookie").to.exist;
|
||||
cy.request({
|
||||
url: `${apiUrl()}/api/me`,
|
||||
headers: { Authorization: `Bearer ${cookie!.value}` },
|
||||
})
|
||||
.its("status")
|
||||
.should("eq", 200);
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Cypress.Commands.add("apiLogin", (email: string, pass?: string) => apiLogin(email, pass));
|
||||
|
||||
Cypress.Commands.add("loginBackoffice", (email = "ceo@edr.local", pass?: string) => {
|
||||
sessionFor("backoffice", email, pass);
|
||||
});
|
||||
|
||||
Cypress.Commands.add("loginPortal", (email = "user@gmail.com", pass?: string) => {
|
||||
// Demo portal users are seeded with a hardcoded password (DemoUsersSeeder),
|
||||
// unlike staff users which use DEFAULT_PASSWORD.
|
||||
sessionFor("portal", email, pass ?? (Cypress.env("demoPassword") as string));
|
||||
});
|
||||
|
||||
Cypress.Commands.add("visitPortal", (path = "/") => {
|
||||
cy.visit(`${Cypress.env("portalUrl")}${path}`);
|
||||
});
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
/** POST /api/auth/login, returns the flattened token body. */
|
||||
apiLogin(email: string, pass?: string): Chainable<LoginBody>;
|
||||
/** Cached programmatic staff session (default ceo@edr.local). */
|
||||
loginBackoffice(email?: string, pass?: string): Chainable<void>;
|
||||
/** Cached programmatic customer session (default user@gmail.com). */
|
||||
loginPortal(email?: string, pass?: string): Chainable<void>;
|
||||
/** cy.visit against the portal origin (env.portalUrl). */
|
||||
visitPortal(path?: string): Chainable<void>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
18
e2e/freight/cypress/support/e2e.ts
Normal file
18
e2e/freight/cypress/support/e2e.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import "./commands";
|
||||
|
||||
// The API's user seeders are disabled in app code — the SQL fixture creates
|
||||
// the staff + demo test users instead. Idempotent, runs before each spec file.
|
||||
before(() => {
|
||||
cy.task("db:seedUsers");
|
||||
});
|
||||
|
||||
// Third-party noise (PostHog, Google Maps, socket.io reconnects) can throw
|
||||
// uncaught exceptions that are irrelevant to the assertion under test. App
|
||||
// errors still fail tests via failed assertions / failed intercepts.
|
||||
Cypress.on("uncaught:exception", (err) => {
|
||||
const ignorable = [/posthog/i, /google/i, /websocket/i, /socket\.io/i, /ResizeObserver/i];
|
||||
if (ignorable.some((pattern) => pattern.test(err.message))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
Reference in New Issue
Block a user