feat: 운영 안정성과 세션 음성 경험 개선
This commit is contained in:
parent
facc4ad2d9
commit
c788343467
95 changed files with 8431 additions and 1785 deletions
|
|
@ -152,6 +152,7 @@ interface AdminEngineConfigResponse {
|
|||
engine_mode: string;
|
||||
engine_url: string;
|
||||
model: string;
|
||||
reasoning_effort?: string | null;
|
||||
}
|
||||
|
||||
interface PrepostMeasureItem {
|
||||
|
|
@ -1430,124 +1431,36 @@ test.describe("session persistence", () => {
|
|||
}
|
||||
});
|
||||
|
||||
test("runs manual AI evaluation retry from teacher review UI into durable DB state @single-run", async ({
|
||||
test("rejects an unreachable AI gateway before it can poison the evaluation runtime @single-run", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.setTimeout(240_000);
|
||||
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 || !health.engine, "manual evaluation retry requires DB and engine");
|
||||
test.skip(!health.db || !health.engine, "engine configuration validation requires DB and engine");
|
||||
|
||||
await signInAsLearner(page);
|
||||
const sessionId = await createActiveSessionWithTurn(page);
|
||||
const endedResponse = await page.request.post(`/api/sessions/${sessionId}/end`);
|
||||
await expectResponseOk(endedResponse);
|
||||
|
||||
await withGlobalEngineConfigLock("manual-evaluation-retry-ui", async () => {
|
||||
await withGlobalEngineConfigLock("engine-config-fail-closed", async () => {
|
||||
await signInAsAdmin(page);
|
||||
const engineResponse = await page.request.get("/api/admin/engine-config");
|
||||
await expectResponseOk(engineResponse);
|
||||
const currentEngine = (await engineResponse.json()) as AdminEngineConfigResponse;
|
||||
|
||||
try {
|
||||
const brokenEnginePatch = await page.request.patch("/api/admin/engine-config", {
|
||||
data: {
|
||||
engine_mode: currentEngine.engine_mode,
|
||||
engine_url: "http://127.0.0.1:9",
|
||||
model: currentEngine.model,
|
||||
},
|
||||
});
|
||||
await expectResponseOk(brokenEnginePatch);
|
||||
const brokenEnginePatch = await page.request.patch("/api/admin/engine-config", {
|
||||
data: {
|
||||
engine_mode: currentEngine.engine_mode,
|
||||
engine_url: "http://127.0.0.1:9",
|
||||
model: currentEngine.model,
|
||||
reasoning_effort: currentEngine.reasoning_effort,
|
||||
},
|
||||
});
|
||||
expect(brokenEnginePatch.status()).toBe(422);
|
||||
|
||||
await signInAsTeacher(page);
|
||||
const failedReevaluationResponse = await page.request.post(
|
||||
`/api/eval/sessions/${sessionId}/reevaluate`,
|
||||
{ data: { scope: "session_end" } },
|
||||
);
|
||||
expect(
|
||||
failedReevaluationResponse.ok(),
|
||||
"broken engine should create a failed durable evaluation seed",
|
||||
).toBe(false);
|
||||
|
||||
await signInAsAdmin(page);
|
||||
const restoreResponse = await page.request.patch("/api/admin/engine-config", {
|
||||
data: {
|
||||
engine_mode: currentEngine.engine_mode,
|
||||
engine_url: currentEngine.engine_url,
|
||||
model: currentEngine.model,
|
||||
},
|
||||
});
|
||||
await expectResponseOk(restoreResponse);
|
||||
|
||||
await signInAsTeacher(page);
|
||||
await page.goto(`/teach/session/${sessionId}/review`);
|
||||
await expect(page.getByText("평가 실패")).toBeVisible({ timeout: 15_000 });
|
||||
const retryButton = page.getByRole("button", { name: "AI 평가 재시도" });
|
||||
await expect(retryButton).toBeVisible();
|
||||
|
||||
const reevaluateResponsePromise = page.waitForResponse((response) => {
|
||||
const url = new URL(response.url());
|
||||
return (
|
||||
response.request().method() === "POST" &&
|
||||
url.pathname.endsWith(`/eval/sessions/${sessionId}/reevaluate`)
|
||||
);
|
||||
});
|
||||
await retryButton.click();
|
||||
const reevaluateResponse = await reevaluateResponsePromise;
|
||||
await expectResponseOk(reevaluateResponse);
|
||||
const reevaluation = (await reevaluateResponse.json()) as {
|
||||
scope?: string;
|
||||
error?: string | null;
|
||||
turns_evaluated?: number;
|
||||
};
|
||||
expect(reevaluation.scope).toBe("session_end");
|
||||
expect(reevaluation.error ?? "").toBe("");
|
||||
expect(reevaluation.turns_evaluated ?? 0).toBeGreaterThan(0);
|
||||
|
||||
await expect(page.locator(".sr-head__stats")).toContainText("평가 완료", {
|
||||
timeout: 30_000,
|
||||
});
|
||||
await expect(page.locator('[aria-label="리뷰 생성 상태"]')).toContainText("준비됨");
|
||||
await expect(page.getByRole("button", { name: "AI 평가 재시도" })).toHaveCount(0);
|
||||
|
||||
const evaluationResponse = await page.request.get(`/api/eval/sessions/${sessionId}/evaluation`);
|
||||
await expectResponseOk(evaluationResponse);
|
||||
const evaluation = (await evaluationResponse.json()) as EvaluationSummaryResponse;
|
||||
expect(evaluation.status).toBe("ready");
|
||||
expect(evaluation.durable).toBe(true);
|
||||
expect(evaluation.deep?.scope).toBe("session_end");
|
||||
expect(evaluation.deep?.turns_evaluated).toBe(reevaluation.turns_evaluated);
|
||||
|
||||
const reviewResponse = await page.request.get(`/api/sessions/${sessionId}/review`);
|
||||
await expectResponseOk(reviewResponse);
|
||||
const review = (await reviewResponse.json()) as SessionReviewResponse;
|
||||
expect(review.reviewReady).toBe(true);
|
||||
expect(review.supervisorState).toBe("평가 완료");
|
||||
|
||||
const dashboardResponse = await page.request.get("/api/teacher/dashboard");
|
||||
await expectResponseOk(dashboardResponse);
|
||||
const dashboard = (await dashboardResponse.json()) as TeacherDashboardResponse;
|
||||
expect(dashboard.source).toBe("database");
|
||||
const dashboardSession = [...(dashboard.pending_reviews ?? []), ...(dashboard.recent_sessions ?? [])].find(
|
||||
(session) => session.session_id === sessionId,
|
||||
);
|
||||
expect(dashboardSession, "teacher dashboard should expose the retried session").toBeTruthy();
|
||||
expect(dashboardSession?.evaluation_status).toBe("ready");
|
||||
expect(dashboardSession?.review_ready).toBe(true);
|
||||
expect(dashboardSession?.supervisor_state).toBe("평가 완료");
|
||||
} finally {
|
||||
await signInAsAdmin(page);
|
||||
const restoreResponse = await page.request.patch("/api/admin/engine-config", {
|
||||
data: {
|
||||
engine_mode: currentEngine.engine_mode,
|
||||
engine_url: currentEngine.engine_url,
|
||||
model: currentEngine.model,
|
||||
},
|
||||
});
|
||||
await expectResponseOk(restoreResponse);
|
||||
}
|
||||
const persistedResponse = await page.request.get("/api/admin/engine-config");
|
||||
await expectResponseOk(persistedResponse);
|
||||
const persisted = (await persistedResponse.json()) as AdminEngineConfigResponse;
|
||||
expect(persisted).toMatchObject(currentEngine);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue