# 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 적용은 런북에 따라 사장님 결재 후 운영자가 수행한다.