# 08. 테스트 가이드

## 테스트 종류

이 프로젝트에는 두 가지 종류의 테스트가 있습니다:

| 종류 | 위치 | 도구 | 용도 |
|------|------|------|------|
| **pytest 단위/통합 테스트** | `tests/test_*.py` | pytest + httpx | 자동화된 테스트. Mock 사용 |
| **HTTP 수동 테스트** | `tests/http/*.http` | VS Code REST Client | 실제 서버 대상 수동 E2E 테스트 |

---

## Part 1: pytest 테스트

### 실행 방법

```bash
# 가상환경 활성화
source .venv/bin/activate

# 전체 테스트 실행
pytest tests/ -v

# 특정 파일만
pytest tests/test_health.py -v

# 특정 테스트만
pytest tests/test_garment.py::test_garment_crud_lifecycle -v

# 더 자세한 출력
pytest tests/ -v -s
```

### 사전 조건

- PostgreSQL 실행 중 (`ootd_poc` 데이터베이스 존재)
- 마이그레이션 적용 완료 (`alembic upgrade head`)
- `.env` 파일에 `DATABASE_URL` 설정
- **LLM API 키 불필요** (Mock 사용)

---

### Mock 전략 (`tests/conftest.py`)

테스트는 외부 의존성(LLM API, insightface 모델)을 **Mock으로 대체**합니다.

#### Mock LLM Provider

```python
class MockLLMProvider(BaseLLMProvider):
    async def analyze_garment(self, image):
        # 항상 동일한 "검정 반팔 티셔츠" 반환
        return [GarmentAnalysis(
            category_main="top",
            category_sub="t-shirt",
            description="검정 반팔 티셔츠",
            tags={"color": ["black"], "pattern": "solid", ...},
        )]

    async def generate_ghost_mannequin(self, image, item, mode):
        # 1x1 투명 PNG 반환
        return b"\x89PNG\r\n..."
```

- 실제 LLM API를 호출하지 않으므로 **API 키 없이 테스트 가능**
- 항상 동일한 결과를 반환하므로 **결과가 예측 가능**

#### Mock Face Analyzer

```python
def _make_mock_face_analyzer():
    analyzer = MagicMock()
    # 고정된 512차원 임베딩 반환
    fixed_embedding = np.random.default_rng(42).standard_normal(512).astype(np.float32)
    fixed_embedding /= np.linalg.norm(fixed_embedding)
    analyzer.get_largest_face_embedding.return_value = fixed_embedding
    return analyzer
```

- insightface 모델 로딩 없이 테스트 가능
- 항상 동일한 임베딩 반환 → 등록 후 식별 시 매칭됨

#### 의존성 오버라이드

```python
@pytest.fixture
async def client():
    app.dependency_overrides[get_db] = _override_get_db
    app.dependency_overrides[get_llm_provider] = _override_get_llm_provider
    app.dependency_overrides[get_garment_service] = _override_get_garment_service
    app.dependency_overrides[get_face_service] = _override_get_face_service

    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as ac:
        yield ac

    app.dependency_overrides.clear()
```

- FastAPI의 `dependency_overrides`로 실제 서비스를 Mock으로 교체
- `ASGITransport` + `httpx.AsyncClient`로 서버 시작 없이 테스트
- 테스트 후 오버라이드 초기화

---

### 테스트 파일별 설명

#### `test_health.py` (1개 테스트)

```python
async def test_health_check_returns_ok(client):
    resp = await client.get("/api/v1/health")
    assert resp.status_code == 200
    assert data["status"] == "ok"
    assert data["db"] == "connected"
```

#### `test_face.py` (4개 테스트)

| 테스트 | 검증 내용 |
|--------|-----------|
| `test_face_register_new_user` | 얼굴 등록 → user_id, confidence 반환 확인 |
| `test_face_register_without_name` | user_name 없이도 등록 성공 |
| `test_face_identify` | 등록 후 같은 이미지로 식별 시 매칭 확인 |
| `test_face_register_missing_image` | 이미지 없이 요청 → 422 검증 에러 |

#### `test_garment.py` (7개 테스트)

| 테스트 | 검증 내용 |
|--------|-----------|
| `test_analyze_garment_global_user` | user_id 없이 분석 → 의류 결과 + face_status="not_used" 확인 |
| `test_analyze_garment_with_user_id` | user_id 지정 분석 |
| `test_analyze_with_face` | use_face_id=true → 얼굴 식별 + 분석 통합, face_status 확인 |
| `test_analyze_with_face_disabled` | use_face_id=false → 글로벌 사용자, face_status="not_used" |
| `test_get_garment_not_found` | 존재하지 않는 ID → 404 |
| `test_delete_garment_not_found` | 존재하지 않는 ID 삭제 → 404 |
| `test_garment_crud_lifecycle` | 분석→조회→삭제→삭제확인 전체 사이클 |
| `test_ghost_generate` | POST /garments/{id}/ghost → 고스트 마네킹 생성 확인 |
| `test_garment_reassign` | PATCH /garments/{id} → 다른 사용자에게 재할당 |
| `test_garment_bulk_reassign` | PATCH /garments/bulk → 벌크 재할당 |

#### `test_wardrobe.py` (5개 테스트)

> 참고: 옷장 조회는 `GET /api/v1/garments?user_id=...`로 변경되었습니다.
> `user_id`를 생략하면 전체 사용자의 의류를 조회합니다.

| 테스트 | 검증 내용 |
|--------|-----------|
| `test_wardrobe_empty` | 의류 없는 사용자 → `GET /api/v1/garments?user_id=...` → 빈 목록 |
| `test_wardrobe_after_analyze` | 분석 후 `GET /api/v1/garments?user_id=...`에 아이템 존재 확인 |
| `test_wardrobe_filter_category` | category_main 필터링 동작 확인 |
| `test_wardrobe_pagination` | page, size 파라미터 동작 확인 |
| `test_wardrobe_invalid_page_size` | 잘못된 값 → 422 에러 |

#### `test_user.py` — 사용자 목록

| 테스트 | 검증 내용 |
|--------|-----------|
| `test_list_users` | GET /api/v1/users → 등록된 사용자 목록 + garment_count 확인 |

---

### 테스트 DB 격리

- 테스트는 **실제 DB를 사용**합니다 (Mock DB 아님)
- `NullPool`을 사용하여 비동기 세션 충돌 방지
- **트랜잭션 롤백 방식**으로 테스트 데이터가 자동 격리됩니다 — 테스트 데이터가 영속되지 않음
- 테스트 간 데이터 오염이 없으므로 정확한 assertion 사용 가능

---

## Part 2: HTTP 수동 테스트 (VS Code REST Client)

### 준비

1. VS Code에 **REST Client** 확장 설치
2. 서버 시작: `uvicorn app.main:app --reload --host 0.0.0.0 --port 8000`
3. `tests/http/` 폴더의 `.http` 파일 열기
4. 각 요청 위의 "Send Request" 클릭

### 필요한 테스트 이미지

`tests/fixtures/` 폴더에 다음 파일이 필요합니다:
- `face_sample_1.jpg` — 얼굴 사진 1
- `face_sample_2.jpg` — 다른 사람 얼굴 사진
- `person_tshirt_jeans.jpg` — 옷을 입은 전신 사진

### 파일별 테스트 시나리오

#### `01_face_register.http` — 얼굴 등록/식별

1. 새 얼굴 등록 (사용자 생성)
2. 같은 얼굴 재등록 (기존 사용자 매칭 확인)
3. 얼굴 식별
4. 다른 얼굴 등록 (두 번째 사용자 생성)
5. 헬스 체크

#### `02_garment_analyze.http` — 의류 분석

1. 의류 분석 (글로벌 사용자, POST /api/v1/garments/analyze)
2. 의류 분석 (특정 user_id)
3. 통합 분석 + 얼굴 식별 (use_face_id=true)
4. 얼굴 비활성화 분석 (use_face_id=false)
5. 고스트 마네킹 생성 (POST /api/v1/garments/{id}/ghost)
6. 의류 상세 조회 (ID 필요)
7. 의류 재할당 (PATCH /api/v1/garments/{id})
8. 벌크 재할당 (PATCH /api/v1/garments/bulk)
9. 의류 삭제 (ID 필요)

#### `03_wardrobe_query.http` — 옷장 조회

> 참고: `GET /api/v1/wardrobe/{user_id}` → `GET /api/v1/garments?user_id=...` 로 변경

1. 전체 조회 (user_id 생략 → 모든 사용자)
2. 특정 사용자 조회 (user_id 쿼리 파라미터)
3~6. 각종 필터 (카테고리, 시즌, 스타일, 색상)
7. 복합 필터
8~9. 페이지네이션
10. 사용자 목록 조회 (GET /api/v1/users)

#### `04_global_user.http` — 글로벌 사용자 시나리오

1. 글로벌 옷장 조회 (GET /api/v1/garments?user_id=00000000-...)
2. user_id 없이 분석 → 글로벌에 저장 확인
3. FaceID 실패 시 글로벌 fallback 테스트

#### `05_cleanup.http` — E2E 전체 라이프사이클

1~12. 헬스체크 → 얼굴 등록 → 식별 → 분석 → 고스트 생성 → 의류 목록 → 재할당 → 삭제

### HTTP 파일 변수

```
@base_url = http://localhost:8000
@global_user_id = 00000000-0000-0000-0000-000000000000
```

- `{{base_url}}`, `{{global_user_id}}`로 참조
- `_variables.http`에서 공통 변수 정의

---

## 테스트 순서 추천

### 첫 번째: pytest로 기본 검증

```bash
# 1. DB + 마이그레이션 확인
pytest tests/test_health.py -v

# 2. 얼굴 기능 확인 (Mock)
pytest tests/test_face.py -v

# 3. 의류 분석 확인 (Mock)
pytest tests/test_garment.py -v

# 4. 옷장 조회 확인 (GET /api/v1/garments)
pytest tests/test_wardrobe.py -v

# 5. 사용자 목록 확인
pytest tests/test_user.py -v
```

### 두 번째: 실서버 수동 테스트

```bash
# 서버 시작
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

# VS Code에서 HTTP 파일 실행
# 또는 curl로 직접 테스트 (07-api-reference.md 참조)
```

### 디버깅 팁

```bash
# 서버 로그 실시간 확인 (uvicorn 출력)
# 모든 요청의 메서드, 경로, 상태코드, 응답시간이 로그에 출력됩니다

# DB 직접 확인
sudo -u postgres psql ootd_poc
SELECT * FROM users;
SELECT * FROM garments ORDER BY created_at DESC LIMIT 5;
SELECT count(*) FROM face_embeddings;

# 이미지 파일 확인
ls -la storage/originals/
ls -la storage/ghost_mannequin/
ls -la storage/faces/
```

