359 lines
32 KiB
Markdown
359 lines
32 KiB
Markdown
# 테스트·검증 가이드
|
||
|
||
Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타입체크/빌드, Playwright E2E)을
|
||
"무엇을 어떻게 실행하고, 어떤 의존이 필요한가" 기준으로 정리한다. 이 문서는 실제
|
||
`apps/web/package.json`, `apps/web/playwright.config.ts`, `apps/web/e2e/`,
|
||
`apps/api/app/test_*.py`, `apps/api/engine_gateway/test_*.py`,
|
||
`infra/docker-compose.yml`을 읽고 작성했으며, 명령·경로는 그대로 따라 할 수 있다.
|
||
|
||
주 환경은 Windows 11 + PowerShell이다. 아래 명령은 셸 공통(`python -m ...`, `npm run ...`)
|
||
형태로 적었고, 환경변수 지정은 PowerShell/Bash 양쪽 예시를 병기한다.
|
||
|
||
---
|
||
|
||
## 0. 한눈에 보는 검증 매트릭스
|
||
|
||
| 검증 | 작업 디렉터리 | 명령 | DB | API(8000) | 웹(5173) | 엔진GW(9099) | 브라우저 | 현재 통과 |
|
||
|---|---|---|---|---|---|---|---|---|
|
||
| 백엔드 단위 테스트 | `apps/api` | `python -m pytest app/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 현재 collect-only 349 tests, 최신 focused pass |
|
||
| 엔진 게이트웨이 테스트 | `apps/api` | `python -m pytest engine_gateway/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 현재 27 pass |
|
||
| API 타입 생성 체크 | `apps/web` | `npm run check:api-types` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
||
| 웹 타입체크 | `apps/web` | `npm run typecheck` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
||
| 웹 빌드 | `apps/web` | `npm run build` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
||
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 194 tests / 19 files |
|
||
|
||
핵심 원칙: **단위 테스트(pytest)와 타입체크/빌드는 외부 서비스 없이 단독 실행된다.**
|
||
**E2E만 풀스택(DB+API+웹+브라우저)을 요구한다.** 아래 각 절에서 근거와 절차를 설명한다.
|
||
|
||
---
|
||
|
||
## 1. 백엔드 단위 테스트 (pytest)
|
||
|
||
### 1.1 대상과 구성
|
||
|
||
- 작업 디렉터리: `apps/api`
|
||
- 테스트는 FastAPI `TestClient`와 인메모리 `store.py` 폴백으로 돌기 때문에 **Postgres/엔진
|
||
게이트웨이가 없어도 통과한다.** 별도 `pytest.ini`/`pyproject.toml` 설정은 없고 기본 수집
|
||
규칙(`test_*.py`)을 그대로 쓴다.
|
||
- `pydantic-settings`가 `apps/api/.env`를 자동 로드한다(있으면). 단위 테스트는 `.env` 없이도
|
||
돈다.
|
||
|
||
### 1.2 실행
|
||
|
||
```sh
|
||
# apps/api
|
||
python -m pytest app/ -q # 앱 단위 테스트: 현재 collect-only 349 tests, 최신 focused pass
|
||
python -m pytest engine_gateway/ -q # 게이트웨이 단위 테스트 현재 27 pass
|
||
```
|
||
|
||
수집만 빠르게 확인하려면:
|
||
|
||
```sh
|
||
python -m pytest app/ --collect-only -q # 현재: "349 tests collected"
|
||
python -m pytest engine_gateway/ --collect-only -q # 현재: "27 tests collected"
|
||
```
|
||
|
||
> 참고: 실행 중 `PendingDeprecationWarning: Please use 'import python_multipart'`
|
||
> 경고가 보일 수 있으나 무해하며 통과 결과에 영향을 주지 않는다.
|
||
|
||
### 1.3 `app/` 테스트 파일 (보안·상태머신·평가 중심)
|
||
|
||
| 파일 | 검증 영역 |
|
||
|---|---|
|
||
| `app/test_runtime_policy.py` | 런타임 정책(공개/dev 모드, dev-login 가드) |
|
||
| `app/test_session_turn_persistence.py` | 세션 턴 영속화(DB/인메모리 양쪽) |
|
||
| `app/test_session_share.py` | 회기 리뷰 공유 URL, sanitized 공개 payload, 토큰 폐기 |
|
||
| `app/test_evaluation_persistence.py` | fast-loop 평가 정규화 적재/복원 매핑 |
|
||
| `app/test_orchestrator_masking.py` | 오케스트레이터 PII 마스킹 게이트 |
|
||
| `app/test_state_machine_resistance.py` | 저항엔진 상태머신(openness 전이) |
|
||
| `app/test_rbac_idor.py` | RBAC / IDOR 권한 경계 |
|
||
| `app/test_auth_providers.py` | 인증 프로바이더(SSO/allowlist) |
|
||
| `app/test_admin_ops.py` | 관리자 콘솔 운영 저장 모델(헬스·티켓) |
|
||
| `app/test_persona_review.py` | 페르소나 리뷰 워크플로 |
|
||
| `app/test_voice_service.py` | 음성 캐스케이드 서비스(STT/TTS) |
|
||
| `app/test_voice_ws.py` | 음성 WebSocket 경계 |
|
||
| `app/test_session_digest_worker.py` | M2 session digest worker 요청 계약, accepted-only 적용, raw text 차단, 재실행 방지 |
|
||
| `scripts/check-dev-dashboard-ssot.py --json` | `docs/dev_dashboard.html` 상태 카운트·M2 검증 수치·DONE/GATE stale 문구 guard |
|
||
|
||
### 1.4 `engine_gateway/` 테스트
|
||
|
||
| 파일 | 검증 영역 |
|
||
|---|---|
|
||
| `engine_gateway/test_gateway_model.py` | 게이트웨이 모델 선택·공유 engine contract·SSE 요청/응답 계약(ENGINE_MODE별) |
|
||
|
||
---
|
||
|
||
### 1.5 로컬 DB smoke — 저항엔진 openness 곡선
|
||
|
||
저항엔진은 단위 테스트와 별도로 실제 API 경로와 Postgres 저장 상태를 함께 확인할 수 있다.
|
||
이 smoke는 dev-login으로 학습자 세션을 만들고, P1 상담 2개에 공감 발화/조언점프 발화를 각각
|
||
5턴씩 넣은 뒤 `app.session_state`, `app.turns`, `app.turn_client_state`를 직접 조회한다.
|
||
|
||
사전 조건:
|
||
|
||
- API가 DB 연결 상태로 떠 있어야 한다.
|
||
- `apps/api/.env` 또는 환경변수에 `DATABASE_URL`이 있어야 한다.
|
||
- API는 dev-login과 onboarding 저장이 가능한 dev 런타임이어야 한다.
|
||
|
||
```sh
|
||
# repo root
|
||
python scripts/smoke-resistance-openness-db.py \
|
||
--api-base-url http://127.0.0.1:8000 \
|
||
--timeout 240 \
|
||
--out docs/ops/resistance-openness-db-smoke-YYYY-MM-DD.json
|
||
```
|
||
|
||
통과 기준:
|
||
|
||
- 공감 세션은 5턴 안에 `stage=탐색`으로 열리고 `effective_openness`가 0보다 커진다.
|
||
- 조언점프 세션은 `stage=라포`, `effective_openness=0.0`을 유지한다.
|
||
- DB의 `turn_seq`와 `app.turns` 저장 행 수가 API 턴 수와 일치한다.
|
||
|
||
## 2. 웹 타입체크 / 빌드
|
||
|
||
### 2.1 대상과 구성
|
||
|
||
- 작업 디렉터리: `apps/web`
|
||
- `package.json` 스크립트(실측):
|
||
- `generate:api-types` → FastAPI OpenAPI export 후 `src/lib/api.gen.ts` 재생성
|
||
- `check:api-types` → FastAPI OpenAPI export 후 생성 타입 stale 여부 확인
|
||
- `typecheck` → `tsc -b`
|
||
- `lint` → `tsc -b` (현재 lint는 타입체크와 동일)
|
||
- `build` → `tsc -b && vite build`
|
||
- `dev` → `vite`
|
||
- `e2e` → `playwright test`
|
||
- 타입체크/빌드는 **순수 정적 검사**라 DB·API·브라우저가 필요 없다.
|
||
|
||
### 2.2 실행
|
||
|
||
```sh
|
||
# apps/web
|
||
npm install # 최초 1회 (devDependencies: typescript, vite, @playwright/test 등)
|
||
npm run check:api-types # FastAPI OpenAPI ↔ src/lib/api.gen.ts 동기화 확인
|
||
npm run typecheck # tsc -b — 타입 오류 0 확인
|
||
npm run build # tsc -b && vite build — 프로덕션 번들 생성까지 확인
|
||
```
|
||
|
||
API DTO를 바꿨다면 먼저 생성 산출물을 갱신한다.
|
||
|
||
```sh
|
||
# apps/web
|
||
npm run generate:api-types
|
||
```
|
||
|
||
### 2.3 CI 계약 게이트
|
||
|
||
GitHub Actions `.github/workflows/api-contract.yml`은 API/Web 계약 관련 파일이 바뀌는
|
||
`pull_request`와 `master` push에서 Python 3.11 API 의존성, Node 22 web 의존성을 설치한 뒤
|
||
`apps/web`의 `npm run check:api-types`를 실행한다. 이 게이트는 FastAPI OpenAPI와
|
||
`src/lib/api.gen.ts`의 drift만 막는 좁은 CI이며, DB·API 서버·브라우저는 필요 없다.
|
||
|
||
---
|
||
|
||
## 3. Playwright E2E (풀스택)
|
||
|
||
### 3.1 의존성 — 왜 풀스택인가
|
||
|
||
Playwright suite에는 **Vite `/api` 프록시를 통해 실제 로컬 FastAPI/DB를 호출하는 full-stack
|
||
E2E**와, 특정 UI/error 상태를 고정하는 **route fixture UI 회귀 테스트**가 함께 있다
|
||
(`apps/web/e2e/README.md`). DB-backed/real-API 증거는 해당 spec이 검증 대상 endpoint를
|
||
`page.route().fulfill()`로 대체하지 않고 실제 API 응답 또는 persisted read-model을 확인한
|
||
경우로 한정한다. 특히 공용 헬퍼 `apps/web/e2e/support.ts`의 `fetchAvailablePersonas()`는
|
||
`source === "database" && !degraded` 페르소나만 사용 가능으로 간주한다.
|
||
|
||
```ts
|
||
// support.ts
|
||
const usable = personas.filter((p) => p.source === "database" && !p.degraded);
|
||
expect(usable.length, ...).toBeGreaterThan(0);
|
||
```
|
||
|
||
즉 **인메모리 degraded 페르소나로는 대부분의 E2E가 통과하지 못한다.** 따라서 E2E는:
|
||
|
||
1. **Postgres(pgvector pg16)** — 실제 DB가 떠 있어야 함
|
||
2. **시드 페르소나** — `AUTO_SEED_PERSONAS=true`(필요 시 `ALLOW_SEED_PERSONA_FALLBACK=true`)
|
||
3. **FastAPI** — `127.0.0.1:8000`에서 수동 기동
|
||
4. **Vite 웹 서버** — Playwright가 자동 기동(아래 3.3)
|
||
5. **Chromium** — `npx playwright install chromium`로 1회 설치
|
||
6. **엔진 게이트웨이(9099)** — **일부 테스트만** 필요(3.5 참고). 레이아웃/시각 게이트는 불필요.
|
||
|
||
### 3.2 사전 준비
|
||
|
||
```sh
|
||
# (1) 브라우저 바이너리 설치 — 최초 1회
|
||
cd apps/web
|
||
npx playwright install chromium
|
||
|
||
# (2) DB 기동 — Docker Desktop 필요. infra/docker-compose.yml의 db 서비스
|
||
# (pgvector/pgvector:pg16). 전체 스택을 띄우려면:
|
||
# docker compose -f infra/docker-compose.yml up -d db
|
||
```
|
||
|
||
### 3.3 API 서버 기동 (시드 포함)
|
||
|
||
PowerShell:
|
||
|
||
```powershell
|
||
# apps/api
|
||
$env:AUTH_DEV_LOGIN_ENABLED = "true" # dev-login 허용 (support.ts가 사용)
|
||
$env:ENVIRONMENT = "dev"
|
||
$env:AUTO_SEED_PERSONAS = "true" # DB에 SEED P1~P3 적재
|
||
$env:ALLOW_SEED_PERSONA_FALLBACK = "true"
|
||
python -m uvicorn app.main:app --host 127.0.0.1 --port 8000
|
||
```
|
||
|
||
Bash:
|
||
|
||
```sh
|
||
cd apps/api
|
||
AUTH_DEV_LOGIN_ENABLED=true ENVIRONMENT=dev AUTO_SEED_PERSONAS=true \
|
||
ALLOW_SEED_PERSONA_FALLBACK=true \
|
||
python -m uvicorn app.main:app --host 127.0.0.1 --port 8000
|
||
```
|
||
|
||
> `.env`에 동일 값을 넣어두면 매번 환경변수를 줄 필요가 없다. DB 연결이 실패하면 main.py
|
||
> lifespan이 인메모리 degraded로 기동되지만(`/health` → `{"status":"degraded","db":false}`),
|
||
> 이 상태에서는 E2E 페르소나 헬퍼가 실패하므로 **E2E용으로는 반드시 DB를 붙여야 한다.**
|
||
|
||
### 3.4 E2E 실행
|
||
|
||
웹 서버는 Playwright `webServer`가 자동으로 띄운다(`PLAYWRIGHT_BASE_URL` 미설정이고
|
||
`PLAYWRIGHT_SKIP_WEB_SERVER`가 없을 때, `npm run dev -- --host localhost --port 5173`).
|
||
|
||
```sh
|
||
# apps/web — API(8000)와 DB가 떠 있는 상태에서
|
||
npm run e2e # = playwright test (전체)
|
||
npm run e2e:headed # 브라우저 표시
|
||
npm run e2e:ui # Playwright UI 모드
|
||
```
|
||
|
||
특정 스펙/그룹만:
|
||
|
||
```sh
|
||
npx playwright test e2e/session-layout.spec.ts
|
||
npx playwright test --grep "@single-run" # 직렬 실행 시나리오만
|
||
npx playwright test --grep-invert "@single-run" # 병렬 시나리오만
|
||
npx playwright test --list # 실행하지 않고 목록·개수만
|
||
```
|
||
|
||
유용한 오버라이드(`playwright.config.ts` 실측):
|
||
|
||
```sh
|
||
PLAYWRIGHT_PORT=5174 npm run e2e # 웹 포트 변경
|
||
PLAYWRIGHT_BASE_URL=http://localhost:5173 npm run e2e # 외부에 이미 뜬 웹 사용(자동기동 끔)
|
||
VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API 직접 지정
|
||
```
|
||
|
||
### 3.5 프로젝트(브라우저 프로파일) 구성
|
||
|
||
`playwright.config.ts`는 `testDir: ./e2e`, `timeout: 30s`, `expect.timeout: 5s`,
|
||
`fullyParallel: true`로 다음 프로젝트를 정의한다.
|
||
|
||
| 프로젝트 | 뷰포트/디바이스 | 대상 grep |
|
||
|---|---|---|
|
||
| `chromium-desktop` | 1440×900 | `@single-run`·`@public-auth` 제외 전부 |
|
||
| `chromium-mobile` | Pixel 5 | `@single-run`·`@public-auth` 제외 전부 |
|
||
| `chromium-single-run` | 1280×800 | `@single-run`만(직렬) |
|
||
| `chromium-public-auth` | 1440×900 | `E2E_PUBLIC_AUTH=1`일 때만 활성, 공개 사이트 대상 |
|
||
|
||
- CI(`process.env.CI`)에서는 `retries: 2`, `workers: 1`, `list`+`html` 리포터를 쓴다.
|
||
- 실패 시 trace(첫 재시도)·스크린샷·비디오를 `node_modules/.tmp/`에 남긴다.
|
||
|
||
### 3.6 실측 테스트 개수 (현재)
|
||
|
||
`npx playwright test --list` 기준 **총 194 tests / 19 files**.
|
||
|
||
- **병렬 시나리오**: 150 tests (desktop 75 + mobile 75)
|
||
- **`@single-run` 직렬 시나리오**: 44 tests (DB 영속화·세션 MVP·음성 성공경로·회기말 평가 저장·교수자 명시 재평가 저장·교수자 턴 재평가 저장·교수자 UI 평가 재시도·교수자 사용자별 분석·source pack sync·이론모드 저장·동의 철회 후 voice 차단·브라우저 stream PII 마스킹·음성 transcript 저장 실패 UI 표면화 등)
|
||
- `e2e/voice-success.spec.ts`는 직접 `/voice/ws` 캐스케이드, Session 텍스트 턴의
|
||
`POST /voice/speech`, Session 마이크 UI를 함께 검증한다. 브라우저 `<audio>.play()`가 차단된 조건에서도
|
||
Web Audio buffer source 재생이 시작되는지와 Session 마이크 UI가 `audio_end`에 browser voice
|
||
activity/silence 메타를 싣는지 확인한다.
|
||
- 2026-07-13 focused 검증: `PLAYWRIGHT_PORT=5271 npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run --workers=1` **2 passed**. 텍스트 턴 저장→소유 회기/턴 기반 `/voice/speech`→OpenAI speech 요청→Web Audio buffer 재생 시작을 확인하고, 별도 새 회기에서 마이크 PCM/STT→AI reply→TTS chunk/`tts_end` 경로가 유지되는지 검증한다. 백엔드 voice focused는 **32 passed**이며, 운영 키 직접 smoke는 `gpt-4o-mini-tts`가 98,133-byte MP3(11.68초)를 반환했다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5205 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1` **7 passed**. 실제 브라우저 `openSessionStream()` → `/sessions/{id}/stream` → DB-backed `/review` 축어록 저장 경로, AI 튜터 코칭 이력 저장/재로딩, WebSocket `stt_result` 음성 비언어 메타데이터, Session 마이크 UI가 생성한 voice activity/silence 메타, Phase 3 pre/post 점수의 DB-backed 저장/재조회, 그리고 세션 종료 background deep 평가가 durable DB row로 저장되어 교수자 리뷰가 `평가 완료`로 전환되는지 검증한다. AI 튜터 코칭 이력 검증은 `POST /live-coach` 응답과 DB-backed history payload의 `status=ready`, `latency_ms>0`도 확인해 규칙 기반 `degraded` fallback 200 응답이 정상 AI 코칭으로 통과하지 못하게 한다.
|
||
- 2026-07-01 추가 DB-backed 검증: `PLAYWRIGHT_PORT=5238 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "finishes session end evaluation"` **1 passed**. 같은 자동 종료 평가 row가 `/teacher/dashboard`의 `recent_sessions`에서도 `evaluation_status=ready`, `review_ready=true`, `supervisor_state=평가 완료`로 반영되는지 실제 DB/API/엔진으로 검증한다.
|
||
- 2026-07-01 추가 DB-backed 검증: `PLAYWRIGHT_PORT=5237 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "explicit teacher session reevaluation"` **1 passed**. 실제 DB/API/엔진에서 학습자 세션과 턴을 만든 뒤 교수자 `POST /eval/sessions/{id}/reevaluate`가 durable `app.session_evaluation` row를 저장하고, `/eval/.../evaluation` 및 `/review`가 `평가 완료`로 반영되는지 검증한다.
|
||
- 2026-07-01 추가 DB-backed 검증: `PLAYWRIGHT_PORT=5246 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "teacher turn reevaluation"` **1 passed**. 실제 DB/API/엔진에서 학습자 세션과 턴을 만든 뒤 교수자 `POST /eval/sessions/{id}/turn`이 기존 turn evaluation normalized row를 durable 교체하고, `/review`의 learner turn `techniques`가 재평가 응답과 같은 라벨로 hydrate되는지 검증한다.
|
||
- 2026-07-01 추가 DB-backed UI 검증: `PLAYWRIGHT_PORT=5245 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "manual AI evaluation retry"` **1 passed**. admin engine config를 잠깐 실패 endpoint로 바꿔 실제 `app.session_evaluation` 실패 row를 만든 뒤 원복하고, 교수자 리뷰 UI의 `AI 평가 재시도` 버튼 클릭이 실제 `POST /eval/sessions/{id}/reevaluate` → durable ready row → `/review` `평가 완료` → `/teacher/dashboard` `evaluation_status=ready`까지 이어지는지 검증한다.
|
||
- 2026-07-02 focused 검증: `PLAYWRIGHT_PORT=5255 npx playwright test e2e/kb-source-packs.spec.ts --project=chromium-single-run --workers=1` **1 passed**. 관리자 전용 `POST /kb/live-coach/source-packs/sync`가 인증 없이 401, learner 403, admin 202로 동작하고, 0615 워크북·DSM·공식 상담 지침·자살위험 지침 source pack을 DB-backed evaluator RAG에 sync한 뒤 source-scoped `/kb/eval-grounding`이 503 skip 없이 200을 반환하고 같은 `source_id`만 반환하는지 검증한다.
|
||
- 2026-07-02 focused 검증: `PLAYWRIGHT_PORT=5259 npx playwright test e2e/session-mvp.spec.ts --project=chromium-desktop --workers=1 --grep "AI tutor|stale empty AI tutor quota|quota exhaustion|degraded AI tutor|voice conversation stop"` **7 passed**. route-fixture UI에서 AI 튜터의 stale empty quota가 서버 `GET /live-coach` 재조회 뒤 실제 잔여 기회로 복구되는지, 최신 quota exhaustion 오류가 이전 코칭 카드에 가려지지 않는지, 음성 `reply.conversation_stopped`가 빈 client reply 대신 109 안전 게이트와 socket close를 유지하고 live-coach를 호출하지 않는지 고정한다. 이 fixture들은 UI 회귀 증거이며 DB-backed 엔진 성공 증거가 아니다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5251 npx playwright test e2e/session-mvp.spec.ts --project=chromium-desktop --workers=1 --grep "AI tutor|runtime AI tutor"` **4 passed**. mock MVP 코칭 카드에서 `근거 보기` 모달이 source title, `source_pack` kind, `2026-06-15` version, citation을 표시하는지 검증하고, `status=degraded` route fixture는 카드·근거 모달·이력 모달·턴 `C` 마커가 `대체 코칭`/`AI 응답 대체`를 노출하는지 고정한다. 추가 route fixture는 `GET /live-coach` 실패가 빈 이력으로 보이지 않고 alert로 표시되는지, `persistence_source/source=runtime`이 임시 저장 경고로 표시되는지도 고정한다. 이 fixture들은 UI 회귀 증거이며 DB-backed 엔진 성공 증거가 아니다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5252 npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1 --grep "pre/post"` **1 passed**. 기존 저장 pre/post 점수를 비운 입력이 `저장된 값 기준`으로 오인되지 않고 invalid 상태, alert, 저장 버튼 disabled로 표면화되는지 검증한다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5248 npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1 --grep "pending voice transcript"` **1 passed**. mock WebSocket이 `transcript final` 뒤 `turn_persistence_unavailable` error를 보내면 Session UI가 임시 학습자 발화를 정상 턴으로 확정하지 않고 `저장 실패` 배지와 alert로 표면화하며, 대기 중 내담자 말풍선을 제거하고 텍스트 입력을 복구하는지 검증한다.
|
||
- 2026-07-01 추가 DB-backed 검증: `PLAYWRIGHT_PORT=5241 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "AI tutor coaching history"` **1 passed**. 실제 DB/API/엔진 경로에서 AI 튜터 응답이 `status=ready`, `latency_ms>0`로 저장되고, 0615 source pack metadata가 `근거 보기` 모달, DB-backed `/live-coach` 이력, reload 후 `C` 마커 history dialog에 유지되는지 확인한다.
|
||
- 2026-07-01 fixture UI focused 검증: `PLAYWRIGHT_PORT=5229 npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1 --grep "retry a failed AI session evaluation|retry fails"` **2 passed**. 교수자 리뷰의 `평가 실패` 상태에서 `AI 평가 재시도` 버튼이 보이고, 재시도 성공 시 완료로 바뀌며 재시도 실패 시 기존 실패 리뷰와 새 실패 사유가 유지되는지 검증한다.
|
||
- 2026-07-01 추가 fixture UI/mixed focused 검증: `PLAYWRIGHT_PORT=5236 npx playwright test e2e/session-review.spec.ts e2e/teacher.spec.ts --project=chromium-desktop --workers=1 --grep "manual AI retry|retry a failed AI session evaluation|retry fails|failed AI session evaluation"` **4 passed**. 평가가 아직 `평가 대기`로 자동 polling 중일 때는 수동 `AI 평가 재시도` 버튼을 숨기고, `평가 실패`일 때만 재시도 버튼을 노출하며, 교수자 pending queue에서도 `하린`/`P6` 종료 회기의 `평가 실패` AI 상태가 수동 `검토 대기` 상태에 묻히지 않는지 검증한다.
|
||
- 2026-07-01 추가 fixture UI focused 검증: `PLAYWRIGHT_PORT=5253 npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1 --grep "long-running session evaluation"` **1 passed**. 긴 회기 평가가 기존 짧은 polling window를 넘겨 늦게 ready가 되어도 리뷰 화면이 `평가 대기`에 고착되지 않고 `평가 완료` 리뷰를 반영하는지 검증한다.
|
||
- 2026-07-02 추가 focused 검증: `C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 -m pytest -p no:cacheprovider apps/api/app/test_teacher_dashboard.py apps/api/app/test_session_turn_persistence.py -k "teacher_cannot_close_review_before_ai_session_evaluation_ready or teacher_can_mark_session_review_closed_with_note or end_session_does_not_reschedule_evaluation_for_already_ended_session or session_evaluation or stale_missing" -q` **8 passed** + `PLAYWRIGHT_PORT=5254 npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1 --grep "manual AI retry|long-running session evaluation|retry a failed AI session evaluation|retry fails"` **4 passed**. 오래된 missing session evaluation이 교수자 대시보드에서도 `평가 실패`로 보이고, 이미 종료된 세션의 중복 `/end`가 평가를 재예약하지 않으며, AI 평가가 ready가 아니면 교수자 리뷰를 `검토 완료`로 닫아 큐에서 숨길 수 없도록 검증한다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5229 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "selected CBT theory mode"` **1 passed**. 실제 Session UI에서 `CBT` 이론모드를 선택해 시작한 회기가 `POST /sessions` payload와 DB-backed `GET /sessions/{id}` 상세의 `theory_mode=cbt`로 보존되는지 검증한다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5231 npx playwright test e2e/voice.spec.ts --project=chromium-single-run --workers=1 --grep "consent withdrawal"` **1 passed**. 동의 철회 뒤 이미 생성된 `session_id`로 `/api/voice/ws`를 열어도 `consent_required`로 닫히는지 실제 브라우저 WebSocket으로 검증한다.
|
||
- 2026-07-01 focused 검증: `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_dataset_export.py app/test_phase3_artifact_checker.py -q` **16 passed**. X1 exporter가 consented/client-visible `text_masked` 턴만 고르고 raw `sc.text`를 선택하지 않는지, supervisor comment raw text를 JSONL에서 제거하는지, approved/dry-run JSONL shape·row count·privacy·PII gate가 `{}` false-positive를 막는지 검증한다.
|
||
- 2026-07-01 focused 검증: `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_evaluation_persistence.py app/test_session_turn_persistence.py app/test_live_coach_privacy.py -q` **50 passed**. H4 live coach prompt가 recent turns와 fast-loop evaluation dict의 문자열 leaf를 다시 마스킹하고, turn evaluation persistence가 rationale/comment/alternative utterance의 raw 이름·기관·전화번호를 DB insert 전 제거하며, legacy DB row의 빈 `text_masked` fallback과 session-end deep evaluation payload/read-model도 raw PII를 재마스킹하는지 검증한다. 같은 라운드에서 `test_evaluation_persistence.py`를 `IsolatedAsyncioTestCase`로 바꿔 async persistence 테스트가 실제 await되도록 고정했다.
|
||
- 2026-07-01 추가 backend focused 검증: `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_session_turn_persistence.py -k "fast_loop_evaluation" -q` **2 passed** + `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_learner_dashboard.py -k "failed_fast_loop" -q` **1 passed**. fast-loop 평가 훅 예외는 상담 턴 저장을 막지 않되 `evaluation.error`와 리뷰의 `턴 평가 실패` 노트로 표면화하고, evaluator가 반환한 error dict는 성장 점수·최근 피드백에서 제외해 실패가 neutral 0.5로 집계되지 않게 검증한다.
|
||
- 2026-07-01 추가 backend focused 검증: `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_eval_routes.py app/test_evaluation_persistence.py -q` **29 passed**. 단일 턴 `reevaluate_turn`이 결과를 응답 바디에만 두지 않고 `feedback_scores`/`turn_technique`/`turn_client_state`/`supervisor_comment`/`alternative_utterance` row를 교체 저장하며, 저장 실패는 503, 평가 error는 저장 후 502/503으로 표면화하는지 검증한다. normalized evaluation table의 delete RLS policy도 함께 고정한다.
|
||
- 2026-07-01 추가 backend focused 검증: `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_session_turn_persistence.py -k "session_evaluation or stale_missing" -q` **3 passed**. 오래된 종료 회기가 `app.session_evaluation` row 없이 무한 `평가 대기`로 남지 않고, timeout+grace 이후 교수자 read-model에서 `평가 실패`와 `AI 평가 재시도가 필요합니다` 사유로 표면화되는지 검증한다.
|
||
- 2026-07-01 추가 backend focused 검증: `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_notifications.py app/test_session_turn_persistence.py app/test_evaluation_persistence.py -k "session_evaluation or missing_session_evaluation or scheduled_session_evaluation" -q` **16 passed**. startup recovery가 종료됐지만 evaluation row가 없는 오래된 DB 세션을 evaluator AI context로 찾아 기존 session-end 평가를 재예약하고, 같은 session_id 중복 background 평가를 process-local in-flight set으로 막는지 검증한다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5228 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "case worksheet|crisis safety"` **2 passed**. C1 학습자 워크시트 저장→교수자 수정요청 검수와 C2 위기 신호→`app.safety_events`→DB-backed 교수자 안전 알림 큐를 실제 브라우저/API/DB로 검증한다.
|
||
- 2026-07-01 추가 DB-backed 검증: `PLAYWRIGHT_PORT=5244 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "Korean PII|crisis safety event"` **2 passed**. H4는 실제 Session UI stream으로 한국어 이름·기관·전화번호가 포함된 학습자 발화를 보내고, DB-backed `GET /sessions/{id}` 상세와 `/review` 모두 raw 값 없이 `[NAME]`/`[ORG]`/`[PHONE]`으로 마스킹되는지 검증한다. C2는 위기 발화가 교수자 검토용 learner turn으로 저장되지만 client AI 응답 turn은 저장되지 않고, DB-backed 교수자 안전 큐의 109 알림이 유지되는지 확인한다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5193 npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1` **2 passed**. MVP 종료/리뷰 흐름과 위기 안전 게이트가 빈 내담자 응답 신호에 덮이지 않는지 검증한다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5215 npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1` **9 passed**. 교수자 콘솔의 페르소나 검수, 검토 큐, 최근 회기 진입과 별도 `/teach/analysis` 학생 분석 메뉴, 학습자 검색 테이블, 행 펼침, 상세 드릴다운, 전체 회기 탭 전환을 검증한다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_BASE_URL=http://localhost:5210 npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --grep "shows selected learner analysis"` **1 passed** + `PLAYWRIGHT_BASE_URL=http://localhost:5210 npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --grep "professor (student analysis overview|learner detail analysis)"` **2 passed**. 실제 5210 로컬 스택에서 학생 분석 목록의 사용자 표시명, 상세 기본 페르소나별 회기 탭, 페르소나 행 펼침, 전체 회기 탭 전환, 7개 폭 레이아웃 containment를 검증한다.
|
||
- 2026-07-01 focused 검증: `C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 -m pytest -p no:cacheprovider app/test_teacher_dashboard.py -q` **10 passed**. 교수자 대시보드 API는 `session_persistence.list_all_sessions()`로 전체 담당 세션을 읽고, 학습자 일반 목록은 `list_recent_sessions()`로 최근 100개 제한을 명시해 한 학생의 최신 회기가 다른 학생 분석 목록을 밀어내지 않는지 검증한다. 또한 ended session summary가 `app.session_evaluation`의 `evaluation_status`, `review_ready`, `supervisor_state`, `evaluation_error`를 별도로 싣고, 평가 실패가 수동 review status와 섞이지 않는지 검증한다.
|
||
- 2026-07-01 focused 검증: `npx playwright test e2e/admin.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1 --grep "approved admin without onboarding"` **2 passed**. 승인된 `role=admin` 사용자의 세션에 `admin_access=false`, `onboarding_completed_at=null`이 남아도 `/admin`이 `/onboarding`으로 우회하지 않고 관리 콘솔의 `/admin/*` API를 호출하는지 fixture로 고정한다.
|
||
|
||
레이아웃·시각 회귀 게이트(핵심 합격선):
|
||
|
||
| 게이트 | 스펙 | 구성 | 개수 |
|
||
|---|---|---|---|
|
||
| 세션 레이아웃 | `e2e/session-layout.spec.ts` | 4 테스트 × (desktop+mobile) | **8 / 8** |
|
||
| 시각 레이아웃 게이트 | `e2e/layout-visual-gate.spec.ts` | `@single-run`, 12개 화면 × 7개 폭 검사 + 다크 테마 assertion | **12 / 12** |
|
||
| 레이아웃 포커스(재설계 화면) | `session-layout`·`session-review`·`admin`·`learner`·`settings`·`teacher`, `@single-run` 제외 | desktop+mobile 병렬 | **54** |
|
||
|
||
> `layout-visual-gate`는 7개 폭(390/720/861/900/1024/1280/1440)에서 12개 핵심 화면의 가로
|
||
> 오버플로·잘린 컨트롤·다크 테마 적용을 검사하고 전체 페이지 스크린샷을
|
||
> `node_modules/.tmp/layout-gate/`에 남긴다.
|
||
> `session-layout`은 회기 전/활성 화면이 뷰포트를 벗어나지 않는지, 우측 패널이 코어 영역을
|
||
> 침범하지 않는지, 시작 후 실제 `session_id` URL에서 새로고침해도 활성 회기 상세가 유지되는지,
|
||
> 스트림 실패 시 미저장 전사가 남지 않는지를 검증한다.
|
||
|
||
### 3.7 공개 인증 스모크(선택)
|
||
|
||
`e2e/public-auth-turn.spec.ts`는 공개 사이트(`https://vignette.chanpaca.net`)를 직접 타격하는
|
||
옵트인 스모크로, 로컬 웹 서버를 띄우지 않는다. 절차는 `apps/web/e2e/README.md` 참고
|
||
(`E2E_PUBLIC_AUTH=1`, `npx playwright codegen ... --save-storage`로 인증 상태 캡처 후
|
||
`E2E_PUBLIC_STORAGE_STATE` 재사용). 캡처한 storage state에는 API 세션 쿠키가 들어 있으니
|
||
민감 정보로 취급한다.
|
||
|
||
---
|
||
|
||
## 4. 전체 검증 순서 권장안 (로컬)
|
||
|
||
```sh
|
||
# 1) 외부 의존 없는 빠른 검증부터
|
||
cd apps/api && python -m pytest app/ -q && python -m pytest engine_gateway/ -q
|
||
cd apps/web && npm run typecheck && npm run build
|
||
|
||
# 2) 풀스택 E2E (DB+API 준비 후)
|
||
# 터미널 A: docker compose -f infra/docker-compose.yml up -d db
|
||
# 터미널 B: cd apps/api && (3.3의 env) python -m uvicorn app.main:app --host 127.0.0.1 --port 8000
|
||
# 터미널 C:
|
||
cd apps/web && npm run e2e
|
||
```
|
||
|
||
엔진 턴 생성까지 보려면(음성/세션 MVP 등 일부 `@single-run`) 별도로 엔진 게이트웨이를
|
||
포트 9099에 띄운다(`ENGINE_MODE=claude_cli`). 게이트웨이가 없으면 `/health`의 `engine:false`
|
||
이고, 실제 턴 생성 시나리오는 실패한다. 레이아웃/시각 게이트는 게이트웨이 없이도 통과한다.
|
||
|
||
---
|
||
|
||
## 5. 운영 원칙 — "가짜 DONE" 금지
|
||
|
||
검증 결과는 **실제로 실행해 통과한 명령**만 근거로 보고한다.
|
||
|
||
- "통과했다/완료다"라고 말하려면 그 자리에서 **실행 가능한 검증 명령과 관측된 결과(통과 개수)**를
|
||
함께 제시한다. 예: `python -m pytest app/ -q` → `100 passed`.
|
||
- 의존을 갖춘 검증을 우회하지 않는다. E2E를 DB/API 없이 돌려 놓고 "그린"이라고 보고하지
|
||
않는다. 풀스택을 못 띄웠으면 **"E2E 미실행"**이라고 명시한다.
|
||
- 인메모리 degraded 기동(`db:false`)에서 페르소나 헬퍼가 실패하는 것은 환경 문제이지 코드가
|
||
통과한 것이 아니다. 환경을 고친 뒤 재실행한 결과로만 판단한다.
|
||
- 수집(`--collect-only`)·목록(`--list`)은 "있다"는 근거일 뿐 "통과했다"는 근거가 아니다.
|
||
통과 주장은 실제 실행 출력으로 뒷받침한다.
|