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 = "owner@acme.test"; 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("admin@acme.test"); 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); });