Files
edr-platform/e2e/freight/cypress/e2e/flows/onboarding_guards.cy.ts
Nathnael 6469c7fa52 test(e2e): cover every onboarding route across portal and backoffice
Five journeys, one file each, every one of them crossing from the portal into
the backoffice and cross-checking the database rather than the screen:

- ethiopian     eTrade verified, Fayda, approved, contract wizard reachable —
                including the dead end a TIN with no trade licence is for an
                ordinary company
- investor      foreign investment licence: nothing on file at eTrade, typed
                registration, passport identity, per-role licence still owed,
                and the backoffice's manual-entry badge and banner
- cooperative   no foreign option, no freight-forwarder role, the co-operative
                document set, no licence cards, its own badge and banner
- switch_back   settings → switch to eTrade → registration cleared, company
                pending, wizard reopened on the company step → re-run through
                eTrade → the flag is gone from the backoffice
- guards        the refused combinations, and the mid-wizard un-tick that has
                to clear the typed registration

Replaces the old onboarding.cy.ts (removed a commit earlier by accident of a
staged deletion): it drove a wizard shape that no longer exists — Fayda before
the company step, a "Personnel" step — so it could only ever have been red.

Notes for whoever edits these next. Attach files to the FIRST empty dropzone,
never by index — SmartFileInput removes the input once a file is on it. Resolve
the company from the database after any cross-origin hop, never from module
state: Cypress re-evaluates the spec bundle and Date.now() with it, which is
what latestJourney's run-stamp cutoff is for. And the deliberate eTrade 400 is
ignored as an uncaught exception — the portal handles that outcome on screen
but leaves the rejected request unhandled at the promise level.
2026-08-18 11:38:37 +00:00

172 lines
6.1 KiB
TypeScript

/**
* The states onboarding must refuse, and the one it must undo.
*
* Two halves. The API guards are cheap `cy.request` checks against the seeded
* demo customer — combinations the portal never offers, which is exactly why
* they have to be refused server-side rather than merely hidden. The second
* half is the expensive one and the reason this spec exists at all: going back
* in the wizard and un-ticking the investment licence has to cost what the
* settings switch costs, or a company finishes onboarding on registration data
* nobody verified, with no flag left to say so.
*/
import {
apiRequest,
companyByEmail,
fillAria,
portalToken,
signupCustomer,
signupIdentity,
typeRegistration,
noLicenceTin,
vatNumber,
wizardClick,
} from "./onboarding-utils";
const stamp = Date.now();
const who = signupIdentity("toggle", stamp);
/** The seeded demo customer: an ordinary company that came through eTrade. */
const DEMO_CUSTOMER = "user@gmail.com";
const demoPassword = () => Cypress.env("demoPassword") as string;
describe("onboarding — refused combinations", { retries: 0 }, () => {
it("refuses an investment licence for an Ethiopian company", () => {
portalToken(DEMO_CUSTOMER, demoPassword()).then((token) =>
apiRequest(
token,
"POST",
"/api/companies/onboarding/start",
{
companyType: "customer",
roles: ["importer"],
nationality: "ethiopian",
investorLicence: true,
},
false,
).then((res) => {
expect(res.status).to.eq(400);
expect(JSON.stringify(res.body)).to.contain(
"Only a foreign company can onboard on an investment licence",
);
}),
);
});
it("refuses a co-operative that also claims an investment licence", () => {
// Ethiopian on purpose: a co-op sent as foreign is refused by the older
// co-operative guard, which would pass this test without the new one ever
// running. Ethiopian gets past that guard and lands on this one.
portalToken(DEMO_CUSTOMER, demoPassword()).then((token) =>
apiRequest(
token,
"POST",
"/api/companies/onboarding/start",
{
companyType: "customer",
roles: ["importer"],
nationality: "ethiopian",
cooperative: true,
investorLicence: true,
},
false,
).then((res) => {
expect(res.status).to.eq(400);
expect(JSON.stringify(res.body)).to.contain(
"it cannot also onboard on a foreign investment licence",
);
}),
);
});
it("refuses to switch a company that never took the route", () => {
portalToken(DEMO_CUSTOMER, demoPassword()).then((token) =>
apiRequest(
token,
"POST",
"/api/companies/onboarding/revert-to-etrade",
undefined,
false,
).then((res) => {
expect(res.status).to.eq(400);
expect(JSON.stringify(res.body)).to.contain(
"already registered through eTrade",
);
}),
);
});
});
describe("onboarding — un-ticking the box mid-wizard", { retries: 0 }, () => {
it("clears the typed registration and reopens on the company step", () => {
signupCustomer(who, "Toggle");
cy.contains("button", "Foreign Company").click();
cy.contains("We operate on a foreign investment licence").click();
cy.contains("button", "Importer").click();
wizardClick("Continue");
cy.contains("Confirm your VAT number", { timeout: 20000 }).should(
"be.visible",
);
// A TIN eTrade knows but which holds no trade licence — the manual route's
// own shape. (A TIN eTrade has never heard of surfaces as an outage rather
// than as "nothing on file": the API wraps its 404 as "Failed to fetch",
// which ETradeInfo reads as unreachable. Different message, different
// test.)
fillAria('[aria-label^="TIN Number"]', noLicenceTin(stamp));
cy.contains("Nothing on file at eTrade for this TIN", {
timeout: 20000,
}).should("be.visible");
typeRegistration(`E2E Toggle Trading ${stamp}`);
fillAria('[aria-label="VAT Number"]', vatNumber(stamp));
wizardClick("Continue");
// The typed registration is now on file.
cy.contains("Company Owner", { timeout: 20000 }).should("be.visible");
companyByEmail(who.email).then((company) => {
expect(company.region, "typed address saved").to.eq("Addis Ababa");
expect((company.attributes ?? {})["investorLicence"]).to.eq(true);
});
// Back to the company step, then back again to the nationality phase.
wizardClick("Back");
cy.contains("Confirm your VAT number", { timeout: 20000 }).should(
"be.visible",
);
wizardClick("Back");
// `exist`, not `visible`: the heading scrolls under the modal's sticky
// header, and where the dialog happens to be scrolled says nothing about
// whether we are back on the nationality phase. The checkbox below is the
// thing this test actually needs to reach.
cy.contains("Where is your company registered?", { timeout: 20000 }).should(
"exist",
);
// Change of mind: this company is an ordinary foreign company after all.
cy.contains("We operate on a foreign investment licence").click();
wizardClick("Continue");
// Whatever was typed under the flag is gone, and the resume target is the
// company step — not the furthest step reached, which would skip the
// eTrade lookup the customer has just opted back into.
companyByEmail(who.email).then((company) => {
expect((company.attributes ?? {})["investorLicence"]).to.eq(false);
expect(company.region, "typed address cleared").to.be.null;
expect(company.licence_number).to.be.null;
expect(company.etrade_phone).to.be.null;
expect(company.onboarding_step).to.eq("company");
});
cy.contains("Confirm your VAT number", { timeout: 20000 }).should(
"be.visible",
);
// No typed-registration block any more: eTrade owns these fields again.
cy.contains("Nothing on file at eTrade for this TIN").should("not.exist");
cy.contains("Registration details").should("not.exist");
});
});
export {};