92 lines
3.9 KiB
TypeScript
92 lines
3.9 KiB
TypeScript
import { expect, test, type APIResponse, type Page } from "@playwright/test";
|
|
import { completeOnboarding, 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);
|
|
await completeOnboarding(page, {
|
|
legal_name: displayName,
|
|
affiliation: "한신대학교",
|
|
department: role === "admin" ? "운영팀" : "상담심리학과",
|
|
grade_level: role === "admin" ? "관리자" : "3학년",
|
|
phone: "010-7777-7777",
|
|
contact_address: "경기도 오산시 한신대학교",
|
|
nickname: displayName,
|
|
self_introduction: `${displayName} readiness E2E 사용자입니다.`,
|
|
});
|
|
}
|
|
|
|
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.getByRole("heading", { name: "오늘 이어갈 회기를 먼저 봅니다." })).toBeVisible();
|
|
const statusRegion = page.getByRole("region", { name: "학습 현황" });
|
|
await expect(statusRegion.getByRole("button", { name: /진행 회기 0회/ })).toBeVisible();
|
|
await expect(statusRegion.getByRole("button", { name: /리뷰 대기 0건/ })).toBeVisible();
|
|
await expect(page.getByText("최근 기록이 없습니다.")).toBeVisible();
|
|
await expect(
|
|
page.locator(".lh-head").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);
|
|
});
|
|
});
|