- PWA (§16): hand-rolled 60-line service worker (network-first pages with offline fallback, cache-first hashed statics, /api NEVER touched — SSE and approvals stay live), app manifest with §2 identity, stdlib- generated coral claw icons, prod-only registration. E2E asserts manifest, real PNG icons, an ACTIVATED service worker, and the /api bypass. (Serwist was tried and dropped: its webpack plugin fights Next 16's Turbopack builds; sixty lines we own beat a plugin we fight.) - Route motion (§3): (workspace) template re-mounts per navigation with a quiet fade-rise, zeroed under prefers-reduced-motion. The a11y sweep now settles running animations before scanning — axe was reading mid-fade opacity as contrast failures - OAuth browser flow vs REAL dex: the e2e harness boots dexidp/dex with static client + password; the journey drives the actual dex login form from /api/apps/oauth/start through the callback 303 and asserts the app reads connected (closing the P4 deferral honestly) - release.yml: tag-triggered — builds all four images + postgres, saves tarballs, assembles the SIGNED air-gapped bundle (compose, config, migrations, seccomp profile, installer, bundler binary), derives the public key via the new Could not find command "pubkey". subcommand (tested), verifies the bundle customer-style with the public half only, attaches tarball + public key to the GitHub release 153 Rust + 63 frontend tests + 29 Playwright journeys. Co-Authored-By: Claude Fable 5 <[email protected]>
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:8080";
|
|
|
|
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);
|
|
});
|