# 테스트·검증 가이드 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를 함께 검증한다. 브라우저 `