Stabilize runtime auth and E2E coverage
This commit is contained in:
parent
6a3e3b541c
commit
188e899394
133 changed files with 55987 additions and 6775 deletions
79
apps/web/e2e/readiness.spec.ts
Normal file
79
apps/web/e2e/readiness.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue