# Phase 1 테스팅 체크리스트

실제 구현 기준 재작성 (2026-03-12).
Stock Oracle(localhost:18001)을 data intermediary로 사용하는 아키텍처 기준.
`[x]` = 테스트 통과 확인, `[ ]` = 미커버 또는 미통과.

---

## 1. 테스트 레벨

| 레벨 | 실행 명령 | 상태 |
|---|---|---|
| 단위 테스트 | `make test-unit` | 100 tests pass |
| 리플레이 테스트 | `make test-replay` | 5 tests pass |
| 통합 테스트 | `make test-integration` | Docker postgres 필요 |
| 운영 전 수동 점검 | 아래 섹션 참조 | — |

---

## 2. 단위 테스트 체크리스트

### 2.1 config (`tests/unit/test_config.py`)

- [x] Settings가 기본값을 올바르게 로드한다 — `test_settings_defaults`
- [x] log_level이 대문자로 정규화된다 — `test_log_level_normalized`
- [x] exhibit_cache_dir이 data_root 기반 경로를 반환한다 — `test_exhibit_cache_dir`
- [x] parquet_dir이 data_root 기반 경로를 반환한다 — `test_parquet_dir`
- [x] get_symbols가 YAML에서 심볼 목록을 반환한다 — `test_get_symbols`

### 2.2 ids (`tests/unit/test_ids.py`)

- [x] document_id 포맷이 규칙(DOC::source::issuer_id::date::acc)을 만족한다 — `test_document_id_format`
- [x] event_id 포맷이 규칙(EVT::...)을 만족하며 event_type이 포함된다 — `test_event_id_format`
- [x] CIK에서 issuer_id가 0-padded 10자리로 생성된다 — `test_issuer_id_from_cik`
- [x] ticker에서 symbol_id가 기본 venue(XNYS)로 생성된다 — `test_symbol_id_from_ticker`
- [x] ticker가 대문자로 정규화되고 custom venue가 반영된다 — `test_symbol_id_uppercase`
- [x] sha256_checksum이 동일 입력에 대해 deterministic 하다 — `test_sha256_checksum`
- [x] 다른 입력에 대해 다른 checksum이 생성된다 — `test_sha256_different`
- [x] new_job_run_id가 유효한 UUID를 반환한다 — `test_new_job_run_id`

### 2.3 time_utils (`tests/unit/test_time_utils.py`)

- [x] utc_now()가 tz-aware datetime을 반환한다 — `test_utc_now`
- [x] UTC → Eastern 변환이 정확하다 (21:05 UTC = 16:05 ET) — `test_to_eastern`
- [x] Eastern → UTC 변환이 정확하다 — `test_to_utc`
- [x] naive datetime은 변환 시 ValueError가 발생한다 — `test_naive_datetime_rejected`
- [x] 21:05 UTC는 "post_market"으로 분류된다 — `test_filing_time_bucket_post_market`
- [x] 12:00 UTC (07:00 ET)는 "pre_market"으로 분류된다 — `test_filing_time_bucket_pre_market`
- [x] 15:00 UTC (10:00 ET)는 "regular_hours"로 분류된다 — `test_filing_time_bucket_regular`
- [x] naive datetime은 "unknown"으로 분류된다 — `test_filing_time_bucket_naive`

### 2.4 file_store (`tests/unit/test_file_store.py`)

- [x] exhibit 쓰기/읽기가 정상 작동하고 checksum을 반환한다 — `test_write_and_read_exhibit`
- [x] exists_exhibit이 존재 여부를 정확히 반환한다 — `test_exists_exhibit`
- [x] get_checksum이 동일 파일에 대해 deterministic 하다 — `test_checksum_deterministic`
- [x] exhibit 경로에 안전한 파일명(EX-99.1.txt)이 사용된다 — `test_exhibit_path_safe_chars`

### 2.5 retries (`tests/unit/test_retries.py`)

- [x] 예외 계층 구조(RetryableError/NonRetryableError/ValidationError/DependencyError)가 올바르다 — `test_exception_hierarchy`
- [x] 에러 필드(source/entity/context)가 올바르게 저장된다 — `test_error_fields`
- [x] 첫 시도에 성공하면 1회만 호출된다 — `test_with_retry_succeeds_on_first_attempt`
- [x] RetryableError 발생 시 최대 횟수까지 재시도한다 — `test_with_retry_retries_on_retryable_error`
- [x] NonRetryableError는 1회만 호출되고 즉시 raise된다 — `test_with_retry_does_not_retry_non_retryable`
- [x] max_attempts 모두 소진 후 RetryableError가 최종 raise된다 — `test_with_retry_exhaustion`

### 2.6 logging (`tests/unit/test_logging.py`)

- [x] configure_logging("DEBUG") 호출 시 예외가 발생하지 않는다 — `test_configure_logging_no_error`
- [x] bind_job_run_id 후 ContextVar에 run_id가 설정된다 — `test_bind_job_run_id_in_context`
- [x] get_logger가 info/debug/warning/error 메서드를 가진 객체를 반환한다 — `test_get_logger_returns_bound_logger`

### 2.7 oracle_client

#### 기존 테스트 (`tests/unit/test_oracle_client.py`)

- [x] FilingsService.search_filings가 FilingSearchResponse를 반환한다 — `test_search_filings`
- [x] FilingsService.get_exhibit가 ExhibitResponse를 반환한다 — `test_get_exhibit`
- [x] PriceService.get_daily_bars가 PriceDataResponse를 반환한다 — `test_get_daily_bars`
- [x] 404 응답 시 OracleNotFoundError가 발생한다 — `test_not_found_raises_oracle_not_found`
- [x] 500 응답 시 OracleServerError가 발생한다 — `test_server_error_raises_oracle_server_error`
- [x] 일시적 500 후 성공 시 정상 결과를 반환한다 — `test_get_retries_on_transient_error_then_succeeds`
- [x] FinraService.get_short_volume가 ShortVolumeResponse를 반환한다 — `test_get_short_volume`
- [x] FinancialService.get_financial_data가 FinancialDataResponse를 반환한다 — `test_get_financial_data`
- [x] ConnectError 시 OracleConnectionError가 발생한다 (3회 재시도 후) — `test_connection_error_raises_oracle_connection_error`
- [x] ReadTimeout 시 OracleTimeoutError가 발생한다 (3회 재시도 후) — `test_timeout_raises_oracle_timeout_error`
- [x] context manager 없이 get() 호출 시 RuntimeError가 발생한다 — `test_client_without_context_manager_raises`

#### FredService (`tests/unit/test_fred_service.py`)

- [x] FredService.get_observations가 FredProxyResponse(series_id, observations)를 반환한다 — `test_get_observations`
- [x] FredService.get_series_info가 FredSeriesInfo(id, frequency)를 반환한다 — `test_get_series_info`

### 2.8 parser

#### text_normalizer (`tests/unit/test_text_normalizer.py`)

- [x] HTML 태그가 제거된다
- [x] 연속 공백/개행이 정규화된다
- [x] 빈 문자열 입력이 처리된다

#### rule_parser (`tests/unit/test_rule_parser.py`)

- [x] Item 2.02가 earnings_release로 분류된다
- [x] Item 7.01이 analyst_day로 분류된다
- [x] Item 1.01이 agreement_signed으로 분류된다
- [x] Item 8.01이 other_disclosure로 분류된다
- [x] 긍정 키워드로 bullish 방향이 감지된다
- [x] 부정 키워드로 bearish 방향이 감지된다
- [x] Guidance raised/lowered 구문이 올바르게 분류된다
- [x] non_gaap_heavy risk flag가 감지된다
- [x] financing_related risk flag가 감지된다
- [x] evidence에 매칭 rule_id가 포함된다
- [x] 메타데이터(filing_date, accepted_at_utc)가 event_date/filing_time_bucket에 반영된다

#### schema_validator (`tests/unit/test_schema_validator.py`)

- [x] 유효한 parser output이 validate 통과한다
- [x] 잘못된 event_type enum은 invalid 처리된다
- [x] confidence가 범위(0~1)를 벗어나면 invalid 처리된다
- [x] 필수 필드 누락 시 invalid 처리된다

#### llm_parser_stub (`tests/unit/test_llm_parser_stub.py`)

- [x] enabled=False 인 경우 parse()가 None을 반환한다 — `test_disabled_returns_none`
- [x] enabled=True 인 경우 parse()가 NotImplementedError를 발생시킨다 — `test_enabled_raises_not_implemented`

### 2.9 features

#### market_features (`tests/unit/test_market_features.py`)

- [x] event_date 기준 reaction_day_return이 계산된다
- [x] 전일 대비 pre_event_return이 계산된다
- [x] bars가 없으면 빈 dict가 반환된다
- [x] event_date가 bars에 없어도 가장 가까운 날짜로 폴백한다
- [x] avg_volume_20d가 bars 수에 맞게 계산된다

#### event_features (`tests/unit/test_event_features.py`)

- [x] bullish 방향 시 guidance_direction_score > 0이다
- [x] bearish 방향 시 guidance_direction_score < 0이다
- [x] risk_flags 비율이 올바르게 계산된다
- [x] confidence.overall이 feature에 포함된다
- [x] signals dict가 올바르게 변환된다

#### financial_features (`tests/unit/test_financial_features.py`)

- [x] 최신 period의 eps/gross_margin/operating_margin이 추출된다
- [x] period가 2개 이상일 때 eps_growth_qoq가 계산된다
- [x] period가 2개 이상일 때 revenue_growth_qoq가 계산된다
- [x] periods가 비어 있으면 빈 dict가 반환된다
- [x] prior eps가 0일 때 eps_growth_qoq가 None이다

### 2.10 db models (`tests/unit/test_db_models.py`)

- [x] IssuerMaster 필드(issuer_id, issuer_name, ticker, is_active)가 올바르게 설정된다 — `test_issuer_master_fields`
- [x] Document 기본값(parsed_status="pending")이 올바르게 설정된다 — `test_document_defaults`
- [x] Event 기본값(status="pending")이 올바르게 설정된다 — `test_event_defaults`
- [x] JobRun 필드(records_seen, records_written)가 올바르게 설정된다 — `test_job_run_fields`
- [x] DB enum 값(JobStatus/ParsedStatus/EventDirection/EventType/ParserKind)이 올바르게 정의된다 — `test_enums`

---

## 3. 통합 테스트 체크리스트

Docker postgres 필요 (`docker compose up postgres`). `make test-integration`으로 실행.

### 3.1 Filing Pipeline (`tests/integration/test_filing_pipeline.py`)

- [x] Document upsert helper가 중복 없이 idempotent 하다 — `test_document_upsert_idempotency_via_helpers`
- [x] 동일 document_id 재삽입 시 중복 row가 생기지 않는다 — `test_document_upsert_idempotency`
- [x] Event + EventParse lifecycle이 정상 작동한다 — `test_event_parse_lifecycle`

### 3.2 Sync Jobs (`tests/integration/test_sync_jobs.py`)

- [x] MacroSync: macro_observations 테이블에 적재된다 — `test_macro_series_insert`
- [x] ShortVolumeSync: short_sale_daily 테이블에 적재된다 — `test_short_sale_daily_insert`
- [ ] IssuerSync: issuer_master 테이블이 갱신된다 (테스트 미작성)

### 3.3 DB Migration (`tests/integration/test_db_migration.py`)

- [x] 14개 테이블이 모두 생성된다 — `test_all_tables_exist`
- [x] DB 연결이 정상이다 — `test_db_health`

### 3.4 Feature Pipeline (`tests/integration/test_feature_pipeline.py`)

- [x] market_v1/event_v1 스냅샷이 정상 생성된다 — `test_feature_snapshot_created`
- [x] financial_service 제공 시 financial_v1 스냅샷이 추가 생성된다 — `test_financial_v1_snapshot_created`

---

## 4. 리플레이 테스트 체크리스트 (`tests/replay/`)

### 4.1 결정성 (`test_determinism.py`)

- [x] 동일 텍스트에 대해 동일 parser version이면 event_type/guidance/confidence가 동일하다 — `test_parser_determinism`
- [x] 부정/혼합 텍스트에서도 동일 결과가 나온다 — `test_parser_determinism_negative_text`
- [x] event feature 계산이 deterministic 하다 — `test_feature_determinism`

### 4.2 Idempotency (`test_idempotency.py`)

- [x] 동일 document_id로 parser를 두 번 실행해도 동일한 결과가 나온다
- [x] checksum이 동일 내용에 대해 항상 동일하다

---

## 5. 운영 전 수동 점검

### 5.1 환경

- [ ] `docker compose up postgres` 후 `make bootstrap` 성공
- [ ] `make migrate` 실행 후 14개 테이블 생성 확인
- [ ] `python -m apps.pipeline.filing_poller.main --dry-run` 실행 가능

### 5.2 관측 가능성

- [ ] 모든 job에 job_run_id가 JSON 로그에 찍힌다
- [ ] 실패 시 로그만 보고 실패 지점을 식별 가능하다
- [ ] records_seen / records_written / records_skipped가 job_runs에 기록된다

### 5.3 데이터 품질

- [ ] Oracle에서 실제 filing 1건을 fetch해 문서 내용 확인
- [ ] parser 결과 5건을 수동 검수해 event_type/guidance 품질 확인
- [ ] feature snapshot 1건을 조회해 필드값이 합리적인지 확인

### 5.4 실패 안전성

- [ ] Stock Oracle 응답 없을 때 OracleConnectionError → retry → 로그 확인
- [ ] parser invalid output이 Event 테이블로 흘러가지 않음을 확인
- [ ] DB 장애 시 JobRun status가 "failed"로 기록됨을 확인

---

## 6. CI 최소 요구 사항

- [x] `make test-unit` 통과 (105 tests)
- [x] `make test-replay` 통과 (5 tests)
- [x] `ruff check` 통과 (format check는 기존 파일 formatting 필요)
- [x] `make test-integration` 통과 (9 tests pass)
- [x] migration smoke test 통과

---

## 7. Phase 1 승인 기준

| 항목 | 상태 | 비고 |
|---|---|---|
| 단위 테스트 자동화 세트 | ✅ | 105 tests pass |
| 리플레이 테스트 | ✅ | 5 tests pass |
| OracleClient 에러 처리 | ✅ | Connection/Timeout/ServerError 모두 커버 |
| parser schema validation | ✅ | JSON schema 기반 validation 테스트 완료 |
| feature snapshot 생성 | ✅ | market_v1/event_v1/financial_v1 코드 완료 |
| retry/backoff 정책 | ✅ | with_retry exhaustion 테스트 포함 |
| 로깅 구성 | ✅ | configure_logging/bind/get_logger 테스트 완료 |
| 통합 테스트 | ✅ | 9 tests pass (Docker postgres 실제 연동, Oracle은 httpx_mock) |
| 운영 전 수동 점검 | ⬜ | 실제 Oracle 연동 환경에서 수행 필요 |
