79 lines · 3.7 KB · TypeScript
RawDownload
// Cloud-specific (browser): an admin permanently deletes their organization.// A fresh user creates an org through onboarding, opens Organization settings,// and uses the danger-zone "Delete organization" flow — which requires// re-typing the org name to confirm. Deleting tears the org down in WorkOS +// billing + the tenant database and clears the session, so the app drops the// user out of the (now gone) workspace.import { expect } from "@effect/vitest";import { Effect } from "effect"; import { scenario } from "../src/scenario";import { Browser, Target } from "../src/services";import { visit, settle } from "../src/surfaces/browser"; scenario(  "Organizations · an admin deletes the organization from settings",  {},  Effect.gen(function* () {    const target = yield* Target;    const browser = yield* Browser;    const identity = yield* target.newIdentity({ org: false });     yield* browser.session(identity, async ({ page, step }) => {      const ORG = "Doomed Org";       await step("Fresh user creates an org via onboarding", async () => {        await visit(page, "/");        await page.getByPlaceholder("Northwind Labs").fill(ORG);        await page.getByRole("button", { name: "Create organization" }).click();        await page.getByText("Connect your MCP client").waitFor();        await page.getByRole("button", { name: "Continue to app" }).click();        await page.getByText("Integrations").first().waitFor();        // The console canonicalizes onto the org's URL slug (/doomed-org).        await page.waitForURL((url) => /^\/[a-z0-9-]+\/?$/.test(url.pathname), {          timeout: 30_000,        });        await settle(page);      });       const slug = new URL(page.url()).pathname.split("/").filter(Boolean)[0]!;       await step("Open Organization settings and find the danger zone", async () => {        await visit(page, `/${slug}/org`);        // The admin-only danger zone renders (a member would not see it).        await page.getByText("Permanently delete this organization").waitFor();      });       await step("The confirm button stays disabled until the name matches", async () => {        // Open the confirmation dialog from the danger-zone Delete button.        await page.getByRole("button", { name: "Delete", exact: true }).click();        const dialog = page.getByRole("dialog");        await dialog.waitFor();        const confirm = dialog.getByRole("button", { name: "Delete organization" });        expect(await confirm.isDisabled(), "cannot delete before typing the name").toBe(true);         // A wrong value keeps it disabled; the exact org name enables it.        await page.getByLabel(/to confirm/i).fill("not the name");        expect(await confirm.isDisabled()).toBe(true);        await page.getByLabel(/to confirm/i).fill(ORG);        expect(await confirm.isEnabled(), "the exact org name unlocks deletion").toBe(true);      });       await step("Confirming deletes the org and drops the user out of it", async () => {        await page.getByRole("dialog").getByRole("button", { name: "Delete organization" }).click();         // Success clears the session and hard-navigates to "/", which the SSR        // auth gate then redirects (the org and session are gone). That nav        // chain aborts any waitForURL, so instead assert the org console itself        // is torn down: its danger zone detaches and never comes back.        await page          .getByText("Permanently delete this organization")          .waitFor({ state: "detached", timeout: 30_000 });        expect(          new URL(page.url()).pathname.startsWith(`/${slug}/org`),          "left the org console",        ).toBe(false);      });    });  }),);