Skip to content

Commit d0442e8

Browse files
committed
e2e: org-slug routing scenario (cross-target browser)
Pins the slug-URL contract end to end: /account/me advertises a grammar-valid slug, bare deep links canonicalize onto it, unknown slugs snap back to the active org, and in-shell navigation keeps the prefix. Runs against cloud and selfhost.
1 parent d494aa8 commit d0442e8

1 file changed

Lines changed: 64 additions & 0 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Cross-target (browser): org-slug console URLs. Console routes live under an
2+
// optional `{-$orgSlug}` segment and the authenticated shell canonicalizes
3+
// the URL onto the ACTIVE organization's slug — this scenario pins that
4+
// contract end to end through the real web UI:
5+
//
6+
// - /account/me advertises the org's URL slug (valid grammar)
7+
// - a bare deep link (/policies) canonicalizes to /<slug>/policies
8+
// - an unknown slug (/zz-no-such-org/policies) snaps back to the active
9+
// org's URL instead of rendering as if it were that org
10+
// - in-shell navigation keeps the slug prefix on every link
11+
//
12+
// Cloud's switch-into-another-org-by-URL behavior is covered separately by
13+
// cloud/org-switcher.test.ts; this scenario only uses slugs no identity owns.
14+
import { expect } from "@effect/vitest";
15+
import { Effect } from "effect";
16+
import { AccountHttpApi, isValidOrgSlug } from "@executor-js/api";
17+
18+
import { scenario } from "../src/scenario";
19+
import { Api, Browser, Target } from "../src/services";
20+
21+
scenario(
22+
"Org URLs · console paths carry the organization slug",
23+
{},
24+
Effect.gen(function* () {
25+
const target = yield* Target;
26+
const browser = yield* Browser;
27+
const { client: apiClient } = yield* Api;
28+
const identity = yield* target.newIdentity();
29+
30+
// The slug the URL must canonicalize onto, from the same account surface
31+
// the shell reads.
32+
const client = yield* apiClient(AccountHttpApi, identity);
33+
const me = yield* client.account.me();
34+
const slug = me.organization?.slug;
35+
expect(slug, "the active organization advertises a URL slug").toBeTruthy();
36+
expect(isValidOrgSlug(slug!) || slug === "default", "the slug fits the URL grammar").toBe(true);
37+
38+
yield* browser.session(identity, async ({ page, step }) => {
39+
await step("A bare deep link canonicalizes onto the org slug", async () => {
40+
await page.goto("/policies", { waitUntil: "networkidle" });
41+
await page.waitForURL((url) => url.pathname === `/${slug}/policies`, {
42+
timeout: 30_000,
43+
});
44+
await page.getByText("Policies").first().waitFor();
45+
});
46+
47+
await step("An unknown org slug snaps back to the active org", async () => {
48+
await page.goto("/zz-no-such-org/policies", { waitUntil: "networkidle" });
49+
await page.waitForURL((url) => url.pathname === `/${slug}/policies`, {
50+
timeout: 30_000,
51+
});
52+
});
53+
54+
await step("In-shell navigation keeps the slug prefix", async () => {
55+
await page.getByRole("link", { name: "Integrations" }).first().click();
56+
await page.waitForURL((url) => url.pathname === `/${slug}`, { timeout: 30_000 });
57+
await page.getByRole("link", { name: "Policies" }).first().click();
58+
await page.waitForURL((url) => url.pathname === `/${slug}/policies`, {
59+
timeout: 30_000,
60+
});
61+
});
62+
});
63+
}),
64+
);

0 commit comments

Comments
 (0)