- 누적 작업트리 커밋: 회기 평가 복구·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
117 lines
4.5 KiB
TypeScript
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);
|
|
});
|
|
});
|