안전 이벤트 대시보드 보강

This commit is contained in:
Yun Chan 2026-07-01 12:34:46 +09:00
parent fe2796f05a
commit 51053af536
15 changed files with 1081 additions and 423 deletions

View file

@ -11,6 +11,17 @@ interface HealthResponse {
engine?: boolean;
}
interface TeacherSafetyAlert {
session_id: string;
resource_title: string;
resource_number: string;
}
interface TeacherDashboardResponse {
source: string;
safety_alerts: TeacherSafetyAlert[];
}
interface ReviewTurn {
speaker: string;
text: string;
@ -492,6 +503,72 @@ test.describe("session persistence", () => {
).toBe(true);
});
test("persists crisis safety event into DB-backed teacher dashboard @single-run", async ({
page,
}) => {
test.setTimeout(120_000);
const healthResponse = await page.request.get("/api/health");
await expectResponseOk(healthResponse);
const health = (await healthResponse.json()) as HealthResponse;
test.skip(!health.db, "crisis safety queue persistence requires DB");
await signInAsLearner(page);
const persona = await fetchAvailablePersona(page, 1);
const crisisText = "저 지금 자살하고 싶어요. 도와주세요.";
await page.goto(`/learn/session/${persona.code}`);
await page.getByRole("button", { name: "회기 시작" }).click();
await expect(page.locator(".sx-page.sx-page--active")).toBeVisible({ timeout: 15_000 });
await expect(page).toHaveURL(/\/learn\/session\/[0-9a-f-]+$/i);
const sessionId = new URL(page.url()).pathname.split("/").at(-1);
expect(sessionId).toMatch(/^[0-9a-f-]+$/i);
const streamResponsePromise = page.waitForResponse((response) => {
const url = new URL(response.url());
return (
response.request().method() === "POST" &&
url.pathname.endsWith(`/api/sessions/${sessionId}/stream`)
);
});
const input = page.getByLabel("학습자 발화 입력");
await input.fill(crisisText);
await page.getByRole("button", { name: "보내기" }).click();
const streamResponse = await streamResponsePromise;
await expectResponseOk(streamResponse);
await expect(page.locator(".sx-crisis-resource")).toContainText("자살예방상담전화 109", {
timeout: 15_000,
});
await expect(page.getByRole("link", { name: "109" })).toBeVisible();
await expect(input).toBeDisabled();
await signInAsTeacher(page);
await expect
.poll(
async () => {
const dashboardResponse = await page.request.get("/api/teacher/dashboard");
await expectResponseOk(dashboardResponse);
const dashboard = (await dashboardResponse.json()) as TeacherDashboardResponse;
expect(dashboard.source).toBe("database");
return dashboard.safety_alerts.some(
(alert) => alert.session_id === sessionId && alert.resource_number === "109",
);
},
{
timeout: 30_000,
intervals: [500, 1_000, 2_000],
message: "crisis safety event should reach the DB-backed teacher dashboard",
},
)
.toBe(true);
await page.goto("/teach");
await expect(page.getByText("109 안전 확인 큐")).toBeVisible({ timeout: 15_000 });
await expect(page.locator(".pf-alert").filter({ hasText: sessionId ?? "" })).toContainText("109");
});
test("persists AI tutor coaching history through reload @single-run", async ({ page }) => {
test.setTimeout(150_000);