feat: implement TASK-WP1-02 login sessions
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { createServer, type ViteDevServer } from "vite";
|
||||
|
||||
let vite: ViteDevServer;
|
||||
let webUrl: string;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
vite = await createServer({
|
||||
configFile: resolve("apps/web/vite.config.ts"),
|
||||
root: resolve("apps/web"),
|
||||
server: { host: "127.0.0.1", port: 0 },
|
||||
});
|
||||
await vite.listen();
|
||||
const address = vite.httpServer?.address();
|
||||
if (!address || typeof address === "string") throw new Error("Vite did not expose a test port.");
|
||||
webUrl = `http://127.0.0.1:${address.port}`;
|
||||
});
|
||||
|
||||
test.afterAll(async () => vite.close());
|
||||
|
||||
test("TDD-WP1-AUTH-003 keeps registration and login as separate keyboard entries", async ({ page }) => {
|
||||
await page.goto(webUrl);
|
||||
const loginTab = page.getByRole("tab", { name: "登录" });
|
||||
const registerTab = page.getByRole("tab", { name: "注册" });
|
||||
|
||||
await expect(loginTab).toHaveAttribute("aria-selected", "true");
|
||||
await expect(page.getByRole("textbox", { name: "邀请码" })).toHaveCount(0);
|
||||
await loginTab.focus();
|
||||
await page.keyboard.press("ArrowRight");
|
||||
await expect(registerTab).toBeFocused();
|
||||
await expect(registerTab).toHaveAttribute("aria-selected", "true");
|
||||
await expect(page.getByRole("textbox", { name: "邀请码" })).toBeVisible();
|
||||
await page.keyboard.press("ArrowLeft");
|
||||
await expect(loginTab).toBeFocused();
|
||||
await expect(page.getByRole("textbox", { name: "邀请码" })).toHaveCount(0);
|
||||
await expect(page.getByRole("link", { name: "管理员登录" })).toHaveAttribute("href", "/admin");
|
||||
await expect(page.getByText("测试数据仅保存在本机,不自动备份,也不会迁移到正式系统。")).toBeVisible();
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { createServer, type ViteDevServer } from "vite";
|
||||
|
||||
let vite: ViteDevServer;
|
||||
let webUrl: string;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
vite = await createServer({
|
||||
configFile: resolve("apps/web/vite.config.ts"),
|
||||
root: resolve("apps/web"),
|
||||
server: { host: "127.0.0.1", port: 0 },
|
||||
});
|
||||
await vite.listen();
|
||||
const address = vite.httpServer?.address();
|
||||
if (!address || typeof address === "string") throw new Error("Vite did not expose a test port.");
|
||||
webUrl = `http://127.0.0.1:${address.port}`;
|
||||
});
|
||||
|
||||
test.afterAll(async () => vite.close());
|
||||
|
||||
test("TDD-WP1-AUTH-004 purges the opened surface on session invalidation", async ({ page }) => {
|
||||
await page.goto(webUrl);
|
||||
await expect(page.getByRole("heading", { name: "邮箱验证码登录" })).toBeVisible();
|
||||
const email = page.getByLabel("邮箱");
|
||||
await email.fill("private-local-state");
|
||||
await expect(email).toHaveValue("private-local-state");
|
||||
|
||||
await page.evaluate(() => {
|
||||
window.dispatchEvent(new Event("dada:session-invalid"));
|
||||
});
|
||||
|
||||
await expect(page.getByRole("heading", { name: "邮箱验证码登录" })).toBeVisible();
|
||||
await expect(page.getByLabel("邮箱")).toHaveValue("");
|
||||
|
||||
const evidenceDirectory = process.env.DADA_EVIDENCE_DIR_AUTH_REVOKE;
|
||||
if (evidenceDirectory) {
|
||||
mkdirSync(resolve(evidenceDirectory, "screenshots"), { recursive: true });
|
||||
await page.screenshot({ fullPage: true, path: resolve(evidenceDirectory, "screenshots", "revoked.png") });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { createServer, type ViteDevServer } from "vite";
|
||||
|
||||
let vite: ViteDevServer;
|
||||
let webUrl: string;
|
||||
|
||||
// Raw traces for these mocked auth requests would retain the submitted email.
|
||||
test.use({ trace: "off" });
|
||||
|
||||
test.beforeAll(async () => {
|
||||
vite = await createServer({
|
||||
configFile: resolve("apps/web/vite.config.ts"),
|
||||
root: resolve("apps/web"),
|
||||
server: { host: "127.0.0.1", port: 0 },
|
||||
});
|
||||
await vite.listen();
|
||||
const address = vite.httpServer?.address();
|
||||
if (!address || typeof address === "string") throw new Error("Vite did not expose a test port.");
|
||||
webUrl = `http://127.0.0.1:${address.port}`;
|
||||
});
|
||||
|
||||
test.afterAll(async () => vite.close());
|
||||
|
||||
test("TDD-WP1-AUTH-003 renders Z0pf8 login states without silently switching entry", async ({ page }) => {
|
||||
await page.route("**/api/v1/auth/login/send", async (route) => {
|
||||
await route.fulfill({
|
||||
contentType: "application/json",
|
||||
status: 409,
|
||||
body: JSON.stringify({
|
||||
error: {
|
||||
code: "AUTH_ENTRY_REJECTED",
|
||||
correlation_id: "00000000-0000-4000-8000-000000000001",
|
||||
details: { field_errors: [{ field: "email", message_key: "auth.account.suspended" }] },
|
||||
message_key: "auth.entry.rejected",
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
await page.goto(webUrl);
|
||||
await expect(page.getByRole("heading", { name: "邮箱验证码登录" })).toBeVisible();
|
||||
await expect(page.getByText("测试数据仅保存在本机,不自动备份,也不会迁移到正式系统。")).toBeVisible();
|
||||
await expect(page.getByRole("link", { name: "管理员登录" })).toHaveAttribute("href", "/admin");
|
||||
await expect(page.getByLabel("邀请码")).toHaveCount(0);
|
||||
await expect(page.getByLabel("创作署名")).toHaveCount(0);
|
||||
await expect(page.getByLabel("社交 ID")).toHaveCount(0);
|
||||
|
||||
await page.getByRole("tab", { name: "登录" }).focus();
|
||||
await page.keyboard.press("Tab");
|
||||
await expect(page.getByLabel("邮箱")).toBeFocused();
|
||||
await page.getByLabel("邮箱").fill("suspended@example.invalid");
|
||||
await page.getByRole("button", { name: "获取验证码" }).click();
|
||||
await expect(page.getByRole("alert")).toContainText("账号已暂停");
|
||||
await expect(page.getByRole("tab", { name: "登录" })).toHaveAttribute("aria-selected", "true");
|
||||
await page.getByLabel("邮箱").fill("");
|
||||
|
||||
const evidenceDirectory = process.env.DADA_EVIDENCE_DIR_AUTH_MATRIX;
|
||||
if (evidenceDirectory) {
|
||||
mkdirSync(resolve(evidenceDirectory, "screenshots"), { recursive: true });
|
||||
await page.screenshot({ fullPage: true, path: resolve(evidenceDirectory, "screenshots", "entry-state.png") });
|
||||
}
|
||||
});
|
||||
|
||||
test("TDD-WP1-AUTH-004 login controls remain stable on mobile and loading states", async ({ page }) => {
|
||||
await page.setViewportSize({ height: 844, width: 390 });
|
||||
await page.route("**/api/v1/auth/login/send", async (route) => {
|
||||
await new Promise((resolveDelay) => setTimeout(resolveDelay, 150));
|
||||
await route.fulfill({
|
||||
contentType: "application/json",
|
||||
status: 200,
|
||||
body: JSON.stringify({
|
||||
challenge_expires_at: "2026-07-28T08:10:00.000Z",
|
||||
registration_id: "00000000-0000-4000-8000-000000000002",
|
||||
resend_available_at: "2026-07-28T08:01:00.000Z",
|
||||
status: "verification_sent",
|
||||
}),
|
||||
});
|
||||
});
|
||||
await page.goto(webUrl);
|
||||
await page.getByLabel("邮箱").fill("mobile@example.invalid");
|
||||
const send = page.getByRole("button", { name: "获取验证码" });
|
||||
await send.click();
|
||||
await expect(page.getByRole("button", { name: "发送中" })).toBeDisabled();
|
||||
await expect(page.getByText(/验证码已发送至/)).toBeVisible();
|
||||
await expect(page.locator("body")).not.toHaveCSS("overflow-x", "scroll");
|
||||
});
|
||||
Reference in New Issue
Block a user