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).
52 lines
2.0 KiB
TypeScript
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);
|
|
});
|