- 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)
4.0 KiB
4.0 KiB
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 보존
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 컬럼 유지
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() 정합
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)
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 컬럼 순서)
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건 모두 해소됨:
- ensure_schema() broker_credentials 갱신: 완료
- upsert ON CONFLICT (user_id,broker) → (daemon_id,broker) + fallback 제거: 완료
실 운영 DB 적용은 런북에 따라 사장님 결재 후 운영자가 수행한다.