vignette/apps/web/e2e/kb-source-packs.spec.ts
Yun Chan 778e8526d4 세션 평가·라이브코치·교수자 분석 라운드 마감 + 문서 정리 + 코드품질 리팩터
- 누적 작업트리 커밋: 회기 평가 복구·durable 저장, 라이브 코치 이력/근거, 교수자 학생분석, 음성 비언어 메타, PII 마스킹, 운영 티켓/헬스 등
- 문서: 완료 기록 docs/archive/ 냉동 보관, docs/ 단일 인덱스(docs/README.md)+통합 TODO(docs/TODO.md)로 정리
- 리팩터(행위 보존): Stage enum SSOT(taxonomy 소유·state_machine re-export), store recent/masked_turns 중복 제거, speaker_ko_label 단일 헬퍼, _list_sessions N+1 제거(state/turns 배치 + 턴평가 하이드레이션 배치)
- 검증: 백엔드 pytest 352 passed, _list_sessions E2E chromium-single-run 2 passed
2026-07-02 02:50:36 +09:00

117 lines
4.5 KiB
TypeScript

import { expect, test, type APIResponse, type Page } from "@playwright/test";
import { completeOnboarding } from "./support";
interface HealthResponse {
db?: boolean;
}
interface SourcePackSyncItem {
source_id: string;
doc_id: number | null;
chunks_indexed: number;
skipped_unchanged: boolean;
embedded: boolean;
degraded: boolean;
}
interface SourcePackSyncResponse {
sources_upserted: number;
chunks_indexed: number;
skipped_unchanged: number;
embedded: boolean;
degraded: boolean;
items: SourcePackSyncItem[];
}
interface EvalGroundingResponse {
chunks: Array<{
source_id?: string | null;
}>;
policy: string;
}
async function expectResponseOk(response: APIResponse) {
if (!response.ok()) {
expect(response.ok(), await response.text()).toBeTruthy();
}
}
async function signInForSourcePackSync(page: Page, role: "admin" | "learner") {
const response = await page.request.post("/api/auth/dev-login", {
data: {
email: role === "admin" ? "source-pack-admin@twentyoz.kr" : "source-pack-learner@hs.ac.kr",
role,
display_name: role === "admin" ? "Source Pack Admin" : "Source Pack Learner",
},
});
await expectResponseOk(response);
await completeOnboarding(page, {
legal_name: role === "admin" ? "Source Pack Admin" : "Source Pack Learner",
affiliation: "한신대학교",
department: role === "admin" ? "운영" : "상담심리학과",
grade_level: role === "admin" ? "관리자" : "3학년",
phone: role === "admin" ? "010-3333-3333" : "010-4444-4444",
contact_address: "경기도 오산시 한신대학교",
});
}
test.describe("live coach source packs", () => {
test("syncs licensed source packs into DB-backed evaluator RAG with admin-only access @single-run", async ({
page,
}) => {
test.setTimeout(180_000);
const healthResponse = await page.request.get("/api/health");
await expectResponseOk(healthResponse);
const health = (await healthResponse.json()) as HealthResponse;
test.skip(!health.db, "source pack sync requires DB");
const anonymousSync = await page.request.post("/api/kb/live-coach/source-packs/sync");
expect(anonymousSync.status()).toBe(401);
await signInForSourcePackSync(page, "learner");
const learnerSync = await page.request.post("/api/kb/live-coach/source-packs/sync");
expect(learnerSync.status()).toBe(403);
await signInForSourcePackSync(page, "admin");
const syncResponse = await page.request.post("/api/kb/live-coach/source-packs/sync");
expect(syncResponse.status(), await syncResponse.text()).toBe(202);
const sync = (await syncResponse.json()) as SourcePackSyncResponse;
const sourceIds = new Set(sync.items.map((item) => item.source_id));
expect(sync.sources_upserted).toBeGreaterThanOrEqual(4);
expect(sync.items.length).toBeGreaterThanOrEqual(4);
expect(sourceIds).toContain("workbook_0615_case_conceptualization");
expect(sourceIds).toContain("dsm5tr_case_formulation");
expect(sourceIds).toContain("official_counseling_guideline_seed");
expect(sourceIds).toContain("official_suicide_risk_guidelines");
expect(sync.chunks_indexed).toBeGreaterThanOrEqual(0);
expect(sync.skipped_unchanged).toBeGreaterThanOrEqual(0);
expect(typeof sync.embedded).toBe("boolean");
expect(typeof sync.degraded).toBe("boolean");
for (const item of sync.items) {
expect(typeof item.source_id).toBe("string");
expect(item.source_id.length).toBeGreaterThan(0);
expect(item.doc_id === null || typeof item.doc_id === "number").toBe(true);
expect(item.chunks_indexed).toBeGreaterThanOrEqual(0);
expect(typeof item.skipped_unchanged).toBe("boolean");
expect(typeof item.embedded).toBe("boolean");
expect(typeof item.degraded).toBe("boolean");
expect(item.skipped_unchanged || item.chunks_indexed > 0).toBe(true);
}
const groundingResponse = await page.request.post("/api/kb/eval-grounding", {
data: {
query: "안전계획 보호요인 위기전화",
k: 3,
rerank: false,
source_id: ["official_suicide_risk_guidelines"],
},
});
expect(groundingResponse.status(), await groundingResponse.text()).toBe(200);
const grounding = (await groundingResponse.json()) as EvalGroundingResponse;
expect(grounding.policy).toMatch(/^evaluator:/);
expect(grounding.chunks.length).toBeGreaterThan(0);
expect(grounding.chunks.every((chunk) => chunk.source_id === "official_suicide_risk_guidelines")).toBe(true);
});
});