Stabilize runtime auth and E2E coverage

This commit is contained in:
Yun Chan 2026-06-26 14:47:00 +09:00
parent 6a3e3b541c
commit 188e899394
133 changed files with 55987 additions and 6775 deletions

View file

@ -0,0 +1,79 @@
import { expect, test, type APIResponse, type Page } from "@playwright/test";
import { expectNoHorizontalOverflow } from "./support";
async function expectResponseOk(response: APIResponse) {
if (!response.ok()) {
expect(response.ok(), await response.text()).toBeTruthy();
}
}
async function signIn(
page: Page,
role: "learner" | "admin",
email: string,
displayName: string,
) {
const res = await page.request.post("/api/auth/dev-login", {
data: { email, role, display_name: displayName },
});
await expectResponseOk(res);
}
test.describe("production readiness gates", () => {
test("learner-facing data comes from server persistence, not browser or seed mocks", async ({
page,
}, testInfo) => {
const email = `readiness.${testInfo.project.name}.${testInfo.workerIndex}.${Date.now()}@hs.ac.kr`;
await signIn(page, "learner", email.toLowerCase(), "Readiness Learner");
const personaResponse = await page.request.get("/api/personas");
await expectResponseOk(personaResponse);
expect(personaResponse.headers()["x-vignette-catalog-source"]).toBe("database");
const personas = (await personaResponse.json()) as Array<{
source: string;
degraded: boolean;
}>;
expect(personas.length).toBeGreaterThan(0);
expect(personas.every((persona) => persona.source === "database" && !persona.degraded)).toBe(
true,
);
const sessionsResponse = await page.request.get("/api/sessions");
await expectResponseOk(sessionsResponse);
const sessions = (await sessionsResponse.json()) as { source: string; sessions: unknown[] };
expect(sessions).toMatchObject({ source: "database", sessions: [] });
await page.goto("/learn");
await expect(page.getByText("지금까지 12회 연습했어요")).toHaveCount(0);
await expect(page.getByText("최근 8회")).toHaveCount(0);
await expect(page.getByText("기존 회기")).toBeVisible();
await expect(page.locator(".lh-activity__stats")).toContainText("누적 회기");
await expect(page.locator(".lh-activity__stats")).toContainText("0");
await expect(page.getByText("저장된 기존 회기가 없습니다.")).toBeVisible();
await expect(page.getByRole("button", { name: "새 회기 시작" })).toBeInViewport();
await expectNoHorizontalOverflow(page);
});
test("admin-owned runtime controls are durable database state", async ({ page }) => {
await signIn(page, "admin", "readiness-admin@twentyoz.kr", "Readiness Admin");
const usersResponse = await page.request.get("/api/admin/users");
await expectResponseOk(usersResponse);
const users = (await usersResponse.json()) as { source: string; durable: boolean };
expect(users).toMatchObject({ source: "database", durable: true });
const engineResponse = await page.request.get("/api/admin/engine-config");
await expectResponseOk(engineResponse);
const engine = (await engineResponse.json()) as {
durable: boolean;
source: string;
engine_mode: string;
engine_url: string;
model: string;
};
expect(engine).toMatchObject({ durable: true, source: "database" });
expect(["claude_cli", "claude_api", "openai", "solar"]).toContain(engine.engine_mode);
expect(engine.engine_url).toMatch(/^https?:\/\//);
expect(engine.model.trim().length).toBeGreaterThan(0);
});
});