Files
clawmates/frontend/tests/e2e/p6-oauth.spec.ts
T
Omar Sobh 298eb8e20e
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 23s
ci / rust (push) Successful in 3m43s
ci / e2e (push) Failing after 14s
ci / publish (push) Successful in 36s
e2e: move backend/dex/frontend from 8080 to 18080 so runners don't collide
Two of the fleet's Gitea Actions runners (morpheus, architect) already
had 8080 permanently bound by unrelated services (nginx on morpheus,
envio-hasura on architect) — every e2e run scheduled there died at
playwright's webServer preflight with "http://127.0.0.1:8080/healthz is
already used". 18080 is unused across morpheus/tank/architect.

Swap 8080 → 18080 in the eight e2e-scoped sites: clawmates.e2e.toml
(listen_addr + slack base_url + oauth redirect_base), dex.yaml (client
redirect URIs must match backend), playwright.config.ts + tests
(p4-slack, p6-oauth), the http.ts dev-fallback origin, and the two
shell scripts (e2e-backend safety check, rehearse-install healthz probe).

Prod compose (/opt/clawmates/docker-compose.yml on gw-04) is untouched;
prod continues to expose the server on 8080 internally on the compose
network (that's per-network, not host-shared).
2026-07-05 20:25:27 -07:00

52 lines
2.0 KiB
TypeScript

import { expect, test } from "@playwright/test";
// P6: the OAuth connect round trip driven through a REAL browser against
// a REAL dex IdP — authorize redirect, dex's own login form, code
// exchange at dex's token endpoint, broker-held token, connected app.
const OWNER_EMAIL = "[email protected]";
const OWNER_PASSWORD = "e2e-password";
const BACKEND = "http://127.0.0.1:18080";
test("connecting an app via OAuth walks the real dex login", async ({
page,
request,
}) => {
// API session for start + verification.
const login = await request.post(`${BACKEND}/api/auth/login`, {
data: { email: OWNER_EMAIL, password: OWNER_PASSWORD },
});
const { token } = (await login.json()) as { token: string };
const auth = { Authorization: `Bearer ${token}` };
const claws = (await (
await request.get(`${BACKEND}/api/team/claws`, { headers: auth })
).json()) as { id: string; name: string }[];
const scout = claws.find((claw) => claw.name === "Scout")!;
const start = await request.post(`${BACKEND}/api/apps/oauth/start`, {
headers: auth,
data: { clawId: scout.id, provider: "linear" },
});
expect(start.status()).toBe(200);
const { authorize_url } = (await start.json()) as { authorize_url: string };
expect(authorize_url).toContain("/dex/auth");
// The REAL browser flow: dex serves its login form; sign in as the
// static user; dex redirects back through our callback.
await page.goto(authorize_url);
await page.getByPlaceholder("email address").fill("[email protected]");
await page.getByPlaceholder("password").fill("password");
await page.getByRole("button", { name: /Log ?in/i }).click();
// The callback 303s into the claw's Add Apps panel.
await page.waitForURL(/app=apps/);
// The connection exists and the app reads connected.
const directory = (await (
await request.get(`${BACKEND}/api/apps?clawId=${scout.id}`, {
headers: auth,
})
).json()) as { id: string; connected: boolean }[];
expect(directory.find((app) => app.id === "linear")?.connected).toBe(true);
});