vignette/apps/web/e2e/session-persistence.spec.ts
2026-06-26 14:47:00 +09:00

95 lines
3.5 KiB
TypeScript

import { expect, test, type Page } from "@playwright/test";
import { fetchAvailablePersona, signInAsLearner, signInAsTeacher } from "./support";
interface SessionStartResponse {
session_id: string;
degraded: boolean;
}
async function expectResponseOk(response: { ok: () => boolean; text: () => Promise<string> }) {
if (!response.ok()) {
expect(response.ok(), await response.text()).toBeTruthy();
}
}
async function createSessionWithTurn(page: Page) {
const persona = await fetchAvailablePersona(page);
const startedResponse = await page.request.post("/api/sessions", {
data: {
persona_code: persona.code,
theory_mode: "humanistic",
},
});
await expectResponseOk(startedResponse);
const started = (await startedResponse.json()) as SessionStartResponse;
expect(started.degraded).toBe(false);
let turnResponse = await page.request.post(`/api/sessions/${started.session_id}/turn`, {
data: {
text: "오늘 많이 버거웠겠어요. 지금 가장 크게 남아 있는 마음은 어떤 건가요?",
},
});
for (let attempt = 0; attempt < 2 && !turnResponse.ok(); attempt += 1) {
await page.waitForTimeout(1000);
turnResponse = await page.request.post(`/api/sessions/${started.session_id}/turn`, {
data: {
text: "조금 천천히 이야기해도 괜찮습니다. 지금 마음에 남는 장면이 있나요?",
},
});
}
let expectedMinTurns = 2;
if (!turnResponse.ok()) {
expect(turnResponse.status(), await turnResponse.text()).toBe(503);
expectedMinTurns = 1;
}
const endedResponse = await page.request.post(`/api/sessions/${started.session_id}/end`);
await expectResponseOk(endedResponse);
return { sessionId: started.session_id, expectedMinTurns };
}
test.describe("session persistence", () => {
test("persists learner turns into DB-backed review and teacher dashboard @single-run", async ({ page }) => {
await signInAsLearner(page);
const { sessionId, expectedMinTurns } = await createSessionWithTurn(page);
const reviewResponse = await page.request.get(`/api/sessions/${sessionId}/review`);
await expectResponseOk(reviewResponse);
const review = await reviewResponse.json();
expect(review.session_id).toBe(sessionId);
expect(review.turns.length).toBeGreaterThanOrEqual(expectedMinTurns);
expect(review.turns[0].speaker).toBe("learner");
if (review.reviewReady) {
expect(review.degraded).toBe(false);
expect(review.summary).toContain("평가 AI");
} else {
expect(review.degraded).toBe(true);
expect(review.rubric).toHaveLength(0);
expect(review.goodMoments).toHaveLength(0);
expect(review.growthPoints).toHaveLength(0);
expect(review.summary).not.toContain("잘한 구체적");
}
const hasClientTurn = review.turns.some(
(turn: { speaker: string }) => turn.speaker === "client",
);
if (hasClientTurn) {
expect(review.clientFeedback).toEqual(expect.any(String));
expect(review.clientFeedback.length).toBeGreaterThan(0);
}
await signInAsTeacher(page);
const dashboardResponse = await page.request.get("/api/teacher/dashboard");
await expectResponseOk(dashboardResponse);
const dashboard = await dashboardResponse.json();
expect(dashboard.source).toBe("database");
expect(
dashboard.recent_sessions.some(
(session: { session_id: string; turn_count: number }) =>
session.session_id === sessionId && session.turn_count >= expectedMinTurns,
),
).toBe(true);
});
});