docs(bat-020): verification, implementation, index 문서 추가
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# BAT-020 Implementation
|
||||
|
||||
## 변경 파일 요약
|
||||
|
||||
| 파일 | 변경 | REQ |
|
||||
| --- | --- | --- |
|
||||
| `app/domain/models.py` | BrokerCredential.daemon_id: str | None = None 추가 | REQ-046 |
|
||||
| `app/repositories/postgres.py` | _row_to_broker_credential 7컬럼 지원, upsert/list/delete daemon_id 파라미터, update_broker_credential_daemon_id 메서드 신규 | REQ-046, REQ-048 |
|
||||
| `app/schemas/v1.py` | UserInfoResponse: brokers list 제거, broker: BrokerItem | None = None 추가 | REQ-049 |
|
||||
| `app/api/v1_user_info.py` | daemon_id=None → 403, daemon_id 기반 단일 broker 조회, 로그 개선(app_secret 미노출) | REQ-049, REQ-051 |
|
||||
| `app/cli/commands/credential.py` | set/list/remove --daemon-id 옵션, backfill-daemon-id subcommand | REQ-048, REQ-050 |
|
||||
| `migrations/versions/20260628_0026_*.py` | broker_credentials daemon_id TEXT NULL 컬럼 추가 (Step1) | REQ-047 |
|
||||
| `migrations/versions/20260628_0027_*.py` | broker_credentials PK 전환 (Step2, 사장님 결재 후 적용) | REQ-047 |
|
||||
| `tests/test_bat020_broker_credential_domain.py` | REQ-046 AC-1~6 단위 테스트 신규 | REQ-046 |
|
||||
| `tests/test_bat020_user_info_daemon_scoped.py` | REQ-049/051 AC 전체 테스트 신규 | REQ-049, REQ-051 |
|
||||
| `tests/test_bat020_credential_cli.py` | REQ-048/050 AC 전체 테스트 신규 | REQ-048, REQ-050 |
|
||||
| `tests/test_v1.py` | brokers→broker 반영, daemon_id=None → 403 반영, 기존 테스트 갱신 | REQ-049 |
|
||||
| `tests/test_cli.py` | FakeRepo 시그니처 갱신, credential 플로우 테스트 --daemon-id 포함 | REQ-050 |
|
||||
| `tests/test_daemon_assignment.py` | daemon_id=None → 403 정책 갱신 | REQ-049 |
|
||||
|
||||
## Design 위임 확정 결과
|
||||
|
||||
### D-1: REQ-048 구현방식 → CLI subcommand 선택
|
||||
- `pfhub credential backfill-daemon-id --user <username> --daemon-id <slug> [--dry-run] [--force] [--broker <broker>]`
|
||||
- 근거: 운영자 제공 매핑 필요, --dry-run 자연스러운 구현, 기존 CLI 패턴과 일관성
|
||||
|
||||
### D-2: REQ-050 --daemon-id 입력 타입 + 과도기 정책
|
||||
- daemon_slug 우선 (`get_daemon_by_slug(owner_user_id, slug)`) + UUID 폴백 (`get_daemon_by_id`)
|
||||
- 과도기(Step1 완료, Step2 미완료): `--daemon-id` 미제공 시 경고 출력 + 허용
|
||||
- Step2 PK 전환 후: DB NOT NULL 제약으로 자동 강제
|
||||
@@ -0,0 +1,116 @@
|
||||
# BAT-020 Verification
|
||||
|
||||
## 실행 결과
|
||||
|
||||
```
|
||||
python3 -m pytest tests/ -v
|
||||
509 passed, 0 failed, 11 warnings in 26.39s
|
||||
```
|
||||
|
||||
신규 테스트:
|
||||
- tests/test_bat020_broker_credential_domain.py: 6 passed
|
||||
- tests/test_bat020_user_info_daemon_scoped.py: 13 passed
|
||||
- tests/test_bat020_credential_cli.py: 11 passed
|
||||
|
||||
기존 테스트 회귀: 0건
|
||||
|
||||
## AC 커버리지
|
||||
|
||||
### REQ-046: BrokerCredential daemon_id 필드 추가
|
||||
|
||||
| AC | 테스트 | 결과 |
|
||||
| --- | --- | --- |
|
||||
| AC-1: daemon_id=None 기본값 | test_broker_credential_daemon_id_defaults_to_none | PASS |
|
||||
| AC-2: daemon_id 값 지정 | test_broker_credential_daemon_id_set | PASS |
|
||||
| AC-3: 7컬럼 행 변환 daemon_id 세팅 | test_row_to_broker_credential_with_daemon_id | PASS |
|
||||
| AC-4: 6컬럼 행 변환 daemon_id=None (하위호환) | test_row_to_broker_credential_without_daemon_id | PASS |
|
||||
| AC-5: daemon_id 필터 시 해당 행만 반환 | test_list_broker_credentials_daemon_id_filter | PASS |
|
||||
| AC-6: daemon_id 미지정 시 전체 반환 | test_list_broker_credentials_no_daemon_id_filter_returns_all | PASS |
|
||||
|
||||
### REQ-047: broker_credentials PK 전환 마이그레이션
|
||||
|
||||
| AC | 검증 방법 | 결과 |
|
||||
| --- | --- | --- |
|
||||
| AC-1: Step1 후 daemon_id TEXT NULL 컬럼 존재 | alembic revision 코드 확인 + ADD COLUMN 명시 | 코드 검증 완료 |
|
||||
| AC-2: Step1 후 기존 데이터 daemon_id=NULL 보존 | 컬럼 추가 시 기존 행 NULL 유지 (DB 동작) | 코드 검증 완료 |
|
||||
| AC-3: Step2 후 PK (daemon_id, broker) 전환 | revision 코드 + 사전 검증 로직 확인 | 코드 검증 완료 |
|
||||
| AC-4: user_id 컬럼 유지 | Step2 revision에서 user_id 삭제 없음 | 코드 검증 완료 |
|
||||
| AC-5: ensure_schema Step2 이후 정합 | ensure_schema broker_credentials TODO 주석 | (Step2 이후 활성화 예정) |
|
||||
| AC-6: Step1 downgrade 동작 | downgrade 함수에서 컬럼/인덱스 제거 명시 | 코드 검증 완료 |
|
||||
| AC-7: Step2 downgrade 안전장치 | 중복 (user_id,broker) 시 RuntimeError 발생 | 코드 검증 완료 |
|
||||
|
||||
NOTE: REQ-047 마이그레이션은 dev/test DB 검증 대상이나 실제 DB 인프라 미연결 상태. 코드 수준 검증.
|
||||
|
||||
### REQ-048: daemon_id 백필 CLI
|
||||
|
||||
| AC | 테스트 | 결과 |
|
||||
| --- | --- | --- |
|
||||
| AC-1: daemon_id IS NULL 행 UPDATE | test_backfill_daemon_id_updates_null_rows | PASS |
|
||||
| AC-2: 백필 후 NULL 0건 | test_backfill_daemon_id_updates_null_rows | PASS |
|
||||
| AC-3: --dry-run DB 변경 없음 | test_backfill_daemon_id_dry_run_no_change | PASS |
|
||||
| AC-4: encrypted_payload 동등 보존 | test_backfill_preserves_encrypted_payload | PASS |
|
||||
| AC-5: NULL 없으면 "이미 완료됨" | test_backfill_daemon_id_already_complete | PASS |
|
||||
| AC-6: 존재하지 않는 user → exit 1 | test_backfill_daemon_id_nonexistent_user_exits_1 | PASS |
|
||||
| AC-7: Step2 적용 가능 상태 검증 | (Step2 revision 사전 검증 코드에서 NULL=0 확인) | 코드 검증 완료 |
|
||||
|
||||
### REQ-049: GET /v1/user_info daemon-scoped 단일 broker 전달
|
||||
|
||||
| AC | 테스트 | 결과 |
|
||||
| --- | --- | --- |
|
||||
| AC-1: daemon_id 있음 + broker 있음 → 단일 BrokerItem | test_user_info_daemon_scoped_broker_returned | PASS |
|
||||
| AC-2: daemon_id 있음 + broker 없음 → broker=null | test_user_info_daemon_no_broker_returns_null | PASS |
|
||||
| AC-3: daemon_id=None → 403 | test_user_info_no_daemon_id_returns_403 | PASS |
|
||||
| AC-4: daemon A → daemon A account_ref | test_user_info_daemon_isolation_two_daemons | PASS |
|
||||
| AC-5: daemon B → daemon B account_ref (격리) | test_user_info_daemon_isolation_two_daemons | PASS |
|
||||
| AC-6: broker 단일 키, brokers 키 없음 | test_user_info_response_has_broker_not_brokers | PASS |
|
||||
| AC-7: 복호화 실패 → broker.credential_payload=null | test_user_info_decryption_failure_returns_null_payload | PASS |
|
||||
| AC-8: service 아닌 token → 403 | test_user_info_web_token_returns_403 | PASS |
|
||||
| AC-9: 기존 필드 회귀 없음 | test_user_info_regression_existing_fields_unchanged | PASS |
|
||||
|
||||
### REQ-050: credential CLI --daemon-id 옵션
|
||||
|
||||
| AC | 테스트 | 결과 |
|
||||
| --- | --- | --- |
|
||||
| AC-1: set --daemon-id → daemon_id 포함 upsert | test_credential_set_with_daemon_id | PASS |
|
||||
| AC-2: list --daemon-id → 해당 daemon만 출력 | test_credential_list_daemon_id_filter | PASS |
|
||||
| AC-3: list daemon-id 미지정 → 전체 | test_credential_list_no_daemon_id_returns_all | PASS |
|
||||
| AC-4: remove --daemon-id → (daemon_id,broker) 삭제 | test_credential_remove_with_daemon_id | PASS |
|
||||
| AC-5: 존재하지 않는 daemon → exit 1 | test_credential_set_nonexistent_daemon_exits_1 | PASS |
|
||||
| AC-6: --daemon-id 미제공 시 경고 출력 + 허용 (과도기 정책) | test_cli_credential_set_list_and_remove_flow 갱신 | PASS |
|
||||
| AC-7: 출력에 daemon_id 포함 | test_credential_set_output_includes_daemon_id | PASS |
|
||||
|
||||
NOTE: AC-6은 design 결정에 따라 Step2 PK 전환 이전 과도기에는 경고만 출력하고 허용. REQ-047 Step2 후 DB 레벨 NOT NULL 제약으로 자동 강제.
|
||||
|
||||
### REQ-051: 보안 격리
|
||||
|
||||
| AC | 테스트 | 결과 |
|
||||
| --- | --- | --- |
|
||||
| AC-1: web 토큰 → 403 | test_user_info_web_token_returns_403 | PASS |
|
||||
| AC-2: 비인증 → 401 | test_user_info_unauthenticated_returns_401 | PASS |
|
||||
| AC-3: daemon_id=None → 403 | test_user_info_no_daemon_id_returns_403 | PASS |
|
||||
| AC-4: 다른 daemon 인증정보 → null | test_user_info_different_daemon_credential_not_returned | PASS |
|
||||
| AC-5: 타 엔드포인트 credential 미노출 | test_other_endpoints_do_not_expose_credential_payload | PASS |
|
||||
| AC-6: DB 오류 → broker=null (500 없음) | test_user_info_db_error_returns_null_broker | PASS |
|
||||
| AC-7: 복호화 실패 → credential_payload=null | test_user_info_decryption_failure_returns_null_payload | PASS |
|
||||
| AC-8: 로그에 app_secret 미노출 | test_user_info_decryption_failure_log_does_not_expose_secret | PASS |
|
||||
|
||||
## 기존 테스트 회귀 분석
|
||||
|
||||
갱신한 기존 테스트:
|
||||
- `tests/test_v1.py`: brokers→broker 응답 변경 반영, daemon_id=None → 403 반영
|
||||
- `tests/test_cli.py`: FakeRepo 시그니처 갱신(daemon_id 추가), credential 플로우 테스트 --daemon-id 포함
|
||||
- `tests/test_daemon_assignment.py`: daemon_id=None → 200 기대를 403으로 갱신 (REQ-049 정책 변경)
|
||||
|
||||
## 비가역 결재 게이트
|
||||
|
||||
REQ-047 Step2(PK 전환) revision 코드가 작성되었으나 실 운영 DB 적용은 사장님 결재 게이트 대상.
|
||||
이 batch에서는 revision 코드만 작성. 적용 순서:
|
||||
1. `alembic upgrade 20260628_0026` (Step1 - nullable daemon_id 추가)
|
||||
2. `pfhub credential backfill-daemon-id --user <username> --daemon-id <slug>` (REQ-048)
|
||||
3. `alembic upgrade 20260628_0027` (Step2 - PK 전환, 사장님 결재 후)
|
||||
|
||||
## 미해결/위험 사항
|
||||
|
||||
1. **ensure_schema() broker_credentials 정의 미갱신**: Step2 PK 전환 이후 `ensure_schema()` 내 `broker_credentials` CREATE TABLE은 여전히 구 스키마(PK=(user_id,broker)). Step2 결재·적용 후 별도 갱신 필요. 현재 코드에 TODO 주석 없음 — 팀장에게 Step2 적용 시 함께 갱신 요청 필요.
|
||||
2. **upsert ON CONFLICT 절**: REQ-047 Step2 이후 PK가 (daemon_id,broker)로 변경되면 `upsert_broker_credential`의 `ON CONFLICT (user_id, broker)` 절이 맞지 않음. Step2 결재·적용 시 해당 쿼리도 함께 갱신 필요.
|
||||
3. **REQ-047 Step2 dev DB 통합 검증**: 실제 PostgreSQL에서 Step1→backfill→Step2 순서 검증을 완료하지 못했음(DB 인프라 미연결). 코드 수준 검증만 완료.
|
||||
@@ -27,3 +27,4 @@
|
||||
| BAT-017 | standard | released | REQ-039, REQ-040, REQ-041, REQ-042 | dcy-007 | [bat-017](./bat-017_20260618_trade-history-and-pnl-api/) |
|
||||
| BAT-018 | batch-lite | release-candidate | REQ-043 | dcy-008 | [bat-018](./bat-018_20260625_holdings-cost-basis-endpoint/) |
|
||||
| BAT-019 | standard | confirmed | REQ-044, REQ-045 | dcy-009 | [bat-019](./bat-019_20260627_assets-read-permission-and-daemon-list/) |
|
||||
| BAT-020 | standard | draft-batch-verification | REQ-046, REQ-047, REQ-048, REQ-049, REQ-050, REQ-051 | dcy-010 | [bat-020](./bat-020_20260628_daemon-scoped-broker-credential/) |
|
||||
|
||||
Reference in New Issue
Block a user