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 }) { 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); }); });