Files

101 lines
5.3 KiB
JavaScript

import { test, expect } from "@playwright/test";
const core = "http://localhost:17324";
const cookieName = "research_reader_session";
const account = page => page.locator(".account-name");
const logout = page => page.getByRole("button", { name: "退出登录", exact: true });
const loginLink = page => page.getByRole("link", { name: "登录", exact: true });
async function control(request, data) { await request.post(core + "/__test/control", { data }); }
async function state(request) { return (await request.get(core + "/__test/state")).json(); }
async function login(page) {
await page.goto("/login");
await page.getByLabel("邮箱地址").fill("reader@example.test");
await page.getByLabel("密码", { exact: true }).fill("fixture-password-123");
await page.getByRole("button", { name: "登录", exact: true }).click();
await expect(account(page)).toHaveText("reader@example.test");
}
test.beforeEach(async ({ request }) => { expect((await request.post(core + "/__test/reset")).ok()).toBe(true); });
test("HttpOnly session survives reload/new tabs and logout propagates", async ({ page, context, request }) => {
const responsePromise = page.waitForResponse(r => r.url().endsWith("/api/foundry/auth/login"));
await login(page);
const response = await responsePromise;
expect(await response.json()).not.toHaveProperty("access_token");
const cookie = (await context.cookies()).find(c => c.name === cookieName);
expect(cookie).toMatchObject({ httpOnly: true, sameSite: "Lax", path: "/" });
expect(cookie.expires).toBeGreaterThan(Date.now() / 1000 + 29 * 86400);
expect(await page.evaluate(() => document.cookie)).not.toContain(cookieName);
expect(await page.evaluate(() => [localStorage.length, sessionStorage.length])).toEqual([0, 0]);
await page.reload();
await expect(account(page)).toHaveText("reader@example.test");
const other = await context.newPage();
await other.goto("/");
await expect(account(other)).toHaveText("reader@example.test");
await logout(other).click();
await expect(loginLink(other)).toBeVisible();
await expect(loginLink(page)).toBeVisible();
expect((await context.cookies()).find(c => c.name === cookieName)).toBeUndefined();
expect((await state(request)).sessions).toBe(0);
});
test("temporary Core failure preserves cookie and retry restores the account", async ({ page, context, request }) => {
await login(page);
await control(request, { meFailure: true });
await page.reload();
await expect(page.getByRole("button", { name: "重试账户" })).toBeVisible();
expect((await context.cookies()).find(c => c.name === cookieName)).toBeDefined();
await control(request, { meFailure: false });
await page.getByRole("button", { name: "重试账户" }).click();
await expect(account(page)).toHaveText("reader@example.test");
});
test("revoked session is cleared and cannot restore on reload", async ({ page, context, request }) => {
await login(page);
await control(request, { expire: true });
await page.reload();
await expect(loginLink(page)).toBeVisible();
expect((await context.cookies()).find(c => c.name === cookieName)).toBeUndefined();
await page.reload();
await expect(loginLink(page)).toBeVisible();
expect((await state(request)).sessions).toBe(0);
});
test("failed logout retains the session until a successful retry", async ({ page, context, request }) => {
await login(page);
await control(request, { logoutFailure: true });
await logout(page).click();
await expect(page.getByText("退出失败,请重试", { exact: true })).toBeVisible();
await expect(account(page)).toHaveText("reader@example.test");
expect((await context.cookies()).find(c => c.name === cookieName)).toBeDefined();
await control(request, { logoutFailure: false });
await logout(page).click();
await expect(loginLink(page)).toBeVisible();
expect((await state(request)).logout).toBe(1);
});
for (const provider of ["Google", "Slack"]) test(`${provider} synthetic PKCE callback exchanges exactly once`, async ({ page, context, request }) => {
await page.goto("/login");
await page.getByRole("button", { name: provider, exact: true }).click();
await expect(account(page)).toHaveText("reader@example.test");
await expect(page).toHaveURL("http://localhost:17323/");
expect(await page.evaluate(() => sessionStorage.length)).toBe(0);
const stats = await state(request);
expect(stats.exchange).toBe(1);
expect(stats[provider.toLowerCase()]).toBe(1);
expect((await context.cookies()).find(c => c.name === cookieName)?.httpOnly).toBe(true);
});
test("registration requests verification and callback consumes its token once", async ({ page, request }) => {
await page.goto("/login");
await page.getByRole("button", { name: "创建账户", exact: true }).click();
await page.getByLabel("邮箱地址").fill("reader@example.test");
await page.getByLabel("密码", { exact: true }).fill("fixture-password-123");
await page.getByRole("button", { name: "创建账户", exact: true }).first().click();
await expect(page.getByRole("status")).toContainText("验证邮件已请求发送");
await expect(loginLink(page)).toBeVisible();
await page.goto("/auth/callback#verify_email=fixture-email-verification");
await expect(account(page)).toHaveText("reader@example.test");
await expect(page).toHaveURL("http://localhost:17323/");
expect((await state(request)).verified).toBe(1);
});