Files
portfolio-hub/docs/batches/bat-021_20260628_req047-step2-integration-and-runbook/verification.md
T
genichin 3e1b571906 feat(bat-021): REQ-047 Step2 동반 코드 수정 + 실 DB 통합검증 + 런북
- ensure_schema() broker_credentials: daemon_id NOT NULL, PK=(daemon_id,broker)로 갱신
- _row_to_broker_credential: Step2 컬럼 순서(daemon_id 선두)로 갱신
- upsert_broker_credential: ON CONFLICT (daemon_id,broker)로 수정, fallback try/except 제거
- list_broker_credentials: fallback 제거, SELECT 컬럼 순서 갱신
- test_bat020_broker_credential_domain.py: AC-3/AC-4 행 순서를 Step2 포맷으로 갱신
- tests/test_bat021_step2_ensure_schema_and_upsert.py: 12개 신규 테스트
- docs/runbooks/bat-021-req047-step2-migration-runbook.md: 운영 적용 런북
- docs/batches/bat-021: batch 문서(index/planning/design/implementation/verification)

실 PostgreSQL 통합검증(AC-1~7 전건 PASS):
  Step1 upgrade → backfill → Step2 upgrade → downgrade 시나리오 포함
  NULL 사전검증(upgrade 거부) 및 downgrade 안전장치(중복 시 RuntimeError) 확인

521 passed, 0 failed (기존 509 + 신규 12)
2026-06-28 10:19:27 +09:00

165 lines
4.0 KiB
Markdown

# BAT-021 Verification
## pytest 실행 결과
```
python3 -m pytest tests/ --tb=short
521 passed, 11 warnings in 26.67s
```
기존 테스트: 509 passed (BAT-020 기준)
신규 테스트: 12 added (test_bat021_step2_ensure_schema_and_upsert.py)
기존 테스트 회귀: 0건
---
## REQ-047 AC 검증 (실 PostgreSQL)
### 검증 환경
- Docker: `portfolio-hub-timescaledb` (timescale/timescaledb:latest-pg16)
- DB: `portfolio_hub` (127.0.0.1:5432)
- 시작 alembic 상태: 20260601_0024 (ensure_schema()로 스키마 구성, alembic 미동기 상태)
- stamp 조치: `alembic stamp 20260605_0025` (DB 실제 스키마 = 0025 적용 상태 반영)
### AC-1: Step1 후 daemon_id TEXT NULL 컬럼 존재, PK (user_id,broker) 유지
```
alembic upgrade 20260628_0026
```
결과:
```
daemon_id | text | nullable=YES
PK: broker_credentials_pkey PRIMARY KEY btree (user_id, broker)
```
**PASS**
### AC-2: Step1 후 기존 데이터 daemon_id=NULL 보존
```sql
INSERT INTO broker_credentials ... VALUES ('test-user-1', 'kis', ...)
SELECT daemon_id FROM broker_credentials WHERE user_id='test-user-1';
-- 결과: NULL
```
**PASS**
### AC-3: Step2 후 PK (daemon_id, broker) 전환, daemon_id NOT NULL
```
# backfill: UPDATE broker_credentials SET daemon_id='test-daemon-1' WHERE daemon_id IS NULL
alembic upgrade 20260628_0027
```
결과:
```
daemon_id | text | nullable=NO
PK: broker_credentials_pkey PRIMARY KEY btree (daemon_id, broker)
```
**PASS**
### AC-4: Step2 후 user_id 컬럼 유지
```sql
SELECT column_name FROM information_schema.columns
WHERE table_name='broker_credentials' AND column_name='user_id';
-- 결과: user_id 존재
```
**PASS**
### AC-5: Step2 후 ensure_schema() 정합
```python
repo.ensure_schema() # 오류 없음
# DB 조회 결과:
# daemon_id: NOT NULL, PK=(daemon_id, broker), user_id 존재
```
**PASS**
### AC-6: Step1 downgrade — daemon_id 컬럼 제거, PK (user_id,broker) 복원
```
alembic downgrade 20260605_0025
```
결과:
```
broker_credentials: daemon_id 컬럼 없음
PK: broker_credentials_pkey PRIMARY KEY btree (user_id, broker)
```
**PASS**
### AC-7: Step2 downgrade 안전장치 — 중복 (user_id,broker) 존재 시 RuntimeError
```
# 2개 daemon이 같은 user/broker 사용하도록 데이터 구성 후 downgrade 시도
alembic downgrade 20260628_0026
```
결과:
```
RuntimeError: downgrade 불가: 1개 (user_id, broker) 조합이 중복됩니다.
같은 user의 여러 daemon이 동일 broker를 사용하고 있어
(user_id, broker) PK로 복원 시 데이터 손실이 발생합니다.
수동으로 데이터를 정리한 후 downgrade하세요.
```
**PASS**
---
## 동반 코드 검증
### upsert_broker_credential (ON CONFLICT + row mapper)
```python
cred = repo.upsert_broker_credential(
'bat021-test-user', 'kis',
account_ref='BAT021-ACCOUNT',
encrypted_payload='bat021-encrypted-payload',
daemon_id='bat021-daemon-1'
)
# 결과: daemon_id='bat021-daemon-1', user_id='bat021-test-user', broker='kis', account_ref='BAT021-ACCOUNT'
cred2 = repo.upsert_broker_credential(..., account_ref='BAT021-ACCOUNT-UPDATED')
# 결과: version=2, account_ref='BAT021-ACCOUNT-UPDATED' (idempotent update OK)
```
**PASS**
### list_broker_credentials (Step2 컬럼 순서)
```python
creds = repo.list_broker_credentials('bat021-test-user', daemon_id='bat021-daemon-1')
# 결과: 1건, daemon_id='bat021-daemon-1', broker='kis'
```
**PASS**
---
## Step2 NULL 사전 검증 (REQ-048 미수행 시 거부)
```
# daemon_id IS NULL 행 1건 존재 상태에서 Step2 upgrade 시도
RuntimeError: broker_credentials에 daemon_id IS NULL인 행이 1건 있습니다.
REQ-048 backfill-daemon-id CLI로 모든 행을 백필한 후 다시 실행하세요.
```
**PASS**
---
## 미해결/위험 사항
없음 — 이 batch의 TODO 2건 모두 해소됨:
1. ensure_schema() broker_credentials 갱신: **완료**
2. upsert ON CONFLICT (user_id,broker) → (daemon_id,broker) + fallback 제거: **완료**
실 운영 DB 적용은 런북에 따라 사장님 결재 후 운영자가 수행한다.