feat: implement portfolio UUID migration and v1 API finalization (BAT-006, BAT-001 re-verified)
Core Changes: - Migrate portfolio_id from string to UUID in schema and repository layer - Update portfolio CLI commands to use name+user combination for identification - Update HTTP API to accept portfolio_id as UUID path parameter - Implement portfolio ownership validation in repository queries Requirements Completed (REQ-016 to REQ-019): - REQ-016: portfolios table UUID schema migration with data migration script - REQ-017: CLI portfolio commands with name+user identification - REQ-018: HTTP API portfolio path parameters and v1 asset_id UUID returns - REQ-019: Portfolio UUID baseline documentation updates BAT-001 Re-verification: - REQ-004~010 marked as Implemented after verification evidence validation - All v1 API endpoints verified with comprehensive test coverage Documentation Updates: - Update data-model.md with portfolio UUID structure - Update interface-contract.md with UUID path parameters - Update runtime-flows.md with ownership-based resolution flows - Update project-structure.md with database layer changes - Add migration script for portfolio UUID transition Testing: - Add test_portfolio_uuid.py for migration validation - Update test_cli.py and test_portfolio_ownership.py for ownership checks
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# BAT-006 Design
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-006
|
||||
|
||||
## Architecture Summary
|
||||
|
||||
`portfolios.portfolio_id`를 사용자 지정 TEXT PK에서 서버 생성 UUID(TEXT)로 교체한다. 기존 `portfolio_id` 문자열은 `name` 컬럼으로 이전하고, `(owner_user_id, name)` 복합 UNIQUE 제약을 DB 레벨에서 도입해 사용자 범위 내 이름 유일성을 보장한다. CLI는 `<name>` + `--user` 조합으로 portfolio를 식별하고 내부에서 `get_portfolio_by_name_and_user()`를 통해 UUID를 조회한다. HTTP API path parameter와 `GET /v1/assets` `asset_id` 반환은 UUID 기반으로 전환된다.
|
||||
|
||||
## Baseline vs Batch 변경점
|
||||
|
||||
### docs/project-structure.md (현재 baseline)
|
||||
- `app/repositories/postgres.py`: SQL 세부사항 담당으로만 기술 — `get_portfolio_by_name_and_user()` 미기재
|
||||
- `app/domain/models.py`: `portfolio_id` 필드 의미 변경 미기재
|
||||
|
||||
### docs/runtime-flows.md (현재 baseline)
|
||||
- `CLI Operational Command Execution` flow: name+user 식별 방식 미기재
|
||||
- `v1 API — GET /v1/assets` flow: `asset_id` 필드 언급 없음
|
||||
|
||||
### docs/data-model.md (현재 baseline)
|
||||
- `portfolio_id :: text :: stable portfolio primary key` — UUID 의미 변경 미기재
|
||||
- `name` 컬럼 별도 기재 없음
|
||||
- `(owner_user_id, name)` UNIQUE 제약 미기재
|
||||
|
||||
### docs/interface-contract.md (현재 baseline)
|
||||
- `GET /v1/assets` 응답 명세 없음 (bat-001에서 추가 예정이었으나 아직 반영 안 됨)
|
||||
- trex-basic 동시 배포 계획 없음
|
||||
|
||||
## Changed Areas
|
||||
|
||||
- Docs:
|
||||
- `docs/project-structure.md`
|
||||
- `docs/runtime-flows.md`
|
||||
- `docs/data-model.md`
|
||||
- `docs/interface-contract.md`
|
||||
- `docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md` (A-3 철회 주석)
|
||||
- Modules:
|
||||
- `migrations/versions/20260511_0015_portfolio_uuid_migration.py` (신규 Alembic revision)
|
||||
- `app/domain/models.py` (주석 없음, Portfolio dataclass 구조 변경 없음 — 기존 `portfolio_id` + `name` 필드 유지)
|
||||
- `app/repositories/postgres.py`:
|
||||
- `create_portfolio()`: `portfolio_id` 파라미터 제거, 서버 UUID 생성
|
||||
- `get_portfolio_by_name_and_user()` 신규 메서드
|
||||
- `ensure_schema()`: `(owner_user_id, name)` UNIQUE 제약 추가
|
||||
- `update_portfolio()`: UniqueViolation 시 ValueError("이미 사용 중인 이름입니다") 반환
|
||||
- `app/domain/services.py`: `PortfolioDomainRepository` Protocol 업데이트
|
||||
- `app/schemas/portfolio.py`:
|
||||
- `CreatePortfolioRequest`: `portfolio_id` 필드 제거, `name: str` 필수로 변경, name 유효성 validator 추가
|
||||
- `app/services/portfolio_service.py`: `create_portfolio()` 호출부 업데이트
|
||||
- `app/cli/commands/portfolio.py`:
|
||||
- `create`: `portfolio_id` Argument → `name` Argument (필수), `--name` Option 제거
|
||||
- `status`, `buy`, `sell`, `invest`, `transfer`, `reset`, `delete`: `portfolio_id` → `name` Argument + `--user` Option
|
||||
- `update`: `portfolio_id` → `name` Argument + `--user` Option + `--new-name` Option (이름 변경용)
|
||||
- `_resolve_portfolio_id()` helper 추가
|
||||
- `_validate_portfolio_name()` helper 추가
|
||||
- Interfaces:
|
||||
- CLI: `portfolio <subcommand> <portfolio_id>` → `portfolio <subcommand> <name> --user <username>`
|
||||
- HTTP API: `POST /portfolios` request body: `portfolio_id` 필드 제거, `name` 필수화
|
||||
- HTTP API: `GET /v1/assets` `assets[].asset_id` → UUID 형식 반환
|
||||
|
||||
## Key Decisions
|
||||
|
||||
1. **portfolio_id 컬럼 타입 TEXT 유지**: PostgreSQL UUID 타입 변환은 이번 범위 밖. `uuid.uuid4()` 문자열을 TEXT로 저장.
|
||||
2. **get_portfolio_by_name_and_user() 공유**: CLI와 HTTP API 식별 모두 이 단일 repository 메서드 사용. Protocol에도 추가.
|
||||
3. **이름 대소문자 구분**: `TV`와 `tv`는 다른 이름. DB UNIQUE 제약도 case-sensitive.
|
||||
4. **단일 Alembic revision**: name 컬럼 추가 (IF NOT EXISTS), UUID 교체, FK 4개 테이블 갱신, UNIQUE 제약을 하나의 revision에서 수행. IF NOT EXISTS 가드로 재실행 안전성 보장.
|
||||
5. **update 명령 name 변경**: CLI `update` 명령은 `--new-name` Option으로 이름 변경. `--force` 없으면 confirm 프롬프트. DB UNIQUE 위반 시 `이미 사용 중인 이름입니다: {name}` 오류.
|
||||
6. **name 유효성**: `[a-zA-Z0-9._:-]`, 1~100자. Pydantic validator + CLI 레이어에서 이중 검사.
|
||||
7. **comment/tag sub-commands**: REQ-017 명시 범위 밖. 이번 batch에서 변경하지 않음 (UUID 직접 전달 필요).
|
||||
8. **dcy-002 A-3 공식 철회**: REQ-018 구현 시 해당 discovery 문서에 주석 추가.
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- **기존 portfolio_id 값이 UUID 형식인 경우**: migration이 이미 적용된 것으로 판단하는 guard (UNIQUE constraint 존재 여부로 판단). 재실행 시 skip.
|
||||
- **FK 고아 레코드**: migration revision 내 트랜잭션 후 orphan 검증 쿼리. 고아 발견 시 RuntimeError 발생.
|
||||
- **name 허용 문자 범위 외 입력**: CLI validation + Pydantic validator에서 조기 거부.
|
||||
- **동일 owner_user_id + 동일 name**: DB UNIQUE 위반 → `create_portfolio()`에서 ValueError, `update_portfolio()`에서 UniqueViolation catch → ValueError.
|
||||
- **존재하지 않는 name+user 조합**: `get_portfolio_by_name_and_user()` None 반환 → `포트폴리오를 찾을 수 없습니다: {name} (user: {username})`.
|
||||
- **revision 재실행**: `already_migrated` guard (UNIQUE constraint 유무 확인). 이미 적용됐으면 UUID 교체 블록 전체 skip.
|
||||
- **migration 중 portfolio_tags PK 업데이트**: PostgreSQL은 PK 값 UPDATE 허용. FK 제약 없으므로 안전.
|
||||
|
||||
## Architecture Impact
|
||||
|
||||
both (project-structure + runtime-flows)
|
||||
|
||||
## Reference Doc Update Plan
|
||||
|
||||
- project-structure.md:
|
||||
- `app/repositories/postgres.py` 항목: `get_portfolio_by_name_and_user(owner_user_id, name) → Portfolio | None` 추가
|
||||
- `app/domain/models.py` 항목: `Portfolio.portfolio_id`가 서버 생성 UUID임을 명시
|
||||
- runtime-flows.md:
|
||||
- `CLI Operational Command Execution` flow: `<name>` + `--user` → `get_portfolio_by_name_and_user()` → UUID 조회 흐름 추가
|
||||
- `v1 API — GET /v1/assets` flow: `asset_id = portfolio_id (UUID)` 반환 명시
|
||||
@@ -0,0 +1,63 @@
|
||||
# BAT-006 Implementation
|
||||
|
||||
- Status: completed
|
||||
- Batch: BAT-006
|
||||
|
||||
## Plan Summary
|
||||
|
||||
- REQ-016: 신규 Alembic revision(name 컬럼 추가, portfolio_id UUID 교체, FK 4개 테이블 atomic 갱신, UNIQUE 제약 추가), Portfolio dataclass `name` 필드 추가, `create_portfolio()` UUID 생성, `get_portfolio_by_name_and_user()` 신규 메서드
|
||||
- REQ-017: CLI portfolio 명령 argument/option 전면 전환(`portfolio_id` → `<name> --user`), `(user_id, name)` → UUID 서비스 레이어, name 유효성 검사, update name guard
|
||||
- REQ-018: HTTP API path parameter UUID 전환, `GET /v1/assets` `asset_id` UUID 직렬화, dcy-002 A-3 철회 주석
|
||||
- REQ-019: baseline 문서 4개 갱신(project-structure.md, runtime-flows.md, data-model.md, interface-contract.md)
|
||||
|
||||
## Changed Files
|
||||
|
||||
### New Files
|
||||
- `migrations/versions/20260511_0015_portfolio_uuid_migration.py` — Alembic revision; idempotency guard via uq_portfolios_owner_name constraint presence check; atomic per-portfolio FK update across portfolio_ledger/portfolio_comments/portfolio_tags/ledger_trades
|
||||
- `tests/test_portfolio_uuid.py` — 14 tests covering UUID creation, name+user resolution, alice/bob isolation, buy/delete flow, name validation, unknown user/portfolio errors, update rename
|
||||
|
||||
### Modified Files
|
||||
- `app/repositories/postgres.py`
|
||||
- `ensure_schema()`: replaced `ADD CONSTRAINT IF NOT EXISTS` (not valid PostgreSQL syntax) with `DO $$ IF NOT EXISTS ... ALTER TABLE ADD CONSTRAINT $$` pattern for uq_portfolios_owner_name
|
||||
- `create_portfolio()`: signature changed from `(portfolio_id: str, *, ..., name: str, ...)` to `(name: str, *, ...)` with server-generated UUID; catches psycopg UniqueViolation → ValueError("이미 사용 중인 이름입니다: {name}")
|
||||
- Added `get_portfolio_by_name_and_user(owner_user_id, name) -> Portfolio | None`
|
||||
- `update_portfolio()`: catches UniqueViolation on name update → ValueError("이미 사용 중인 이름입니다: {new_name}")
|
||||
- `app/domain/services.py` — `PortfolioDomainRepository` Protocol updated: `create_portfolio` new signature, `get_portfolio_by_name_and_user` added, `list_portfolios` Protocol params aligned
|
||||
- `app/schemas/portfolio.py` — `CreatePortfolioRequest`: removed `portfolio_id` field; `name: str` now required with regex validation `[a-zA-Z0-9._:-]{1,100}`
|
||||
- `app/services/portfolio_service.py` — `create_portfolio()`: call site updated to `self._repository.create_portfolio(payload.name, owner_user_id=...)`
|
||||
- `app/cli/commands/portfolio.py` — Full rewrite for REQ-017: `_NAME_RE`, `_validate_portfolio_name()`, `_resolve_portfolio_id()` helpers; all 8 mutation commands switched to `<name> --user`; `update` uses `--new-name` with `--force` guard; `comment`/`tag` sub-commands keep `portfolio_id` (UUID) directly
|
||||
- `docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md` — A-3 철회 주석 추가
|
||||
- `docs/data-model.md` — portfolios.portfolio_id → UUID, name column, uq_portfolios_owner_name constraint documented
|
||||
- `docs/project-structure.md` — Added `get_portfolio_by_name_and_user()` to repositories section
|
||||
- `docs/runtime-flows.md` — Added CLI portfolio name+user resolution flow; GET /v1/assets asset_id UUID note
|
||||
- `docs/interface-contract.md` — Added v1 consumer API section with simultaneous deployment constraint; CLI portfolio inputs updated
|
||||
- `tests/test_cli.py` — FakeRepo: `create_portfolio` new signature (generates UUID), `get_portfolio_by_name_and_user` added; `_portfolio()`: name changed to "005930:tv"; 5 tests updated to pass --user and adjust assertions
|
||||
- `tests/test_portfolio_ownership.py` — Fixed POST body to use `name` instead of `portfolio_id`
|
||||
|
||||
## Execution Log
|
||||
|
||||
1. Created Alembic migration `20260511_0015_portfolio_uuid_migration.py` (REQ-016)
|
||||
2. Updated `app/repositories/postgres.py`: ensure_schema UNIQUE constraint (DO $$ pattern), create_portfolio UUID generation, get_portfolio_by_name_and_user, update_portfolio UniqueViolation catch (REQ-016)
|
||||
3. Updated `app/domain/services.py` Protocol (REQ-016)
|
||||
4. Updated `app/schemas/portfolio.py`: removed portfolio_id, name required + validated (REQ-016, REQ-017)
|
||||
5. Updated `app/services/portfolio_service.py`: create_portfolio call site (REQ-016)
|
||||
6. Rewrote `app/cli/commands/portfolio.py`: name+user pattern for all mutation commands (REQ-017)
|
||||
7. Added dcy-002 A-3 철회 주석 (REQ-018)
|
||||
8. Updated 4 baseline docs (REQ-019)
|
||||
9. Created `tests/test_portfolio_uuid.py` with 14 tests
|
||||
10. Fixed `tests/test_cli.py` FakeRepo + 5 test cases
|
||||
11. Fixed `tests/test_portfolio_ownership.py` POST body field
|
||||
12. Fixed `ensure_schema()` syntax: `ADD CONSTRAINT IF NOT EXISTS` → DO $$ block (PostgreSQL compatibility)
|
||||
|
||||
## Validation
|
||||
|
||||
- Full test suite: **129 passed, 0 failed**
|
||||
- `tests/test_portfolio_uuid.py`: 14/14 passed (UUID generation, name+user resolution, alice/bob isolation, name validation, error messages)
|
||||
- `tests/test_cli.py`: 49/49 passed (including updated tests for new CLI interface)
|
||||
- `tests/test_portfolio_ownership.py`: 4/4 passed
|
||||
|
||||
## Remaining Risks
|
||||
|
||||
- Alembic migration `20260511_0015` not verified against real PostgreSQL instance (unit tests use FakeRepo only)
|
||||
- trex-basic simultaneous deployment required: GET /v1/assets asset_id changes from TEXT to UUID format
|
||||
- Existing `comment` and `tag` sub-commands still take `portfolio_id` (UUID) directly; operators must obtain UUID from `portfolio list --user` output
|
||||
@@ -0,0 +1,36 @@
|
||||
# BAT-006 Index
|
||||
|
||||
- BAT ID: BAT-006
|
||||
- Profile: standard
|
||||
- Status: release-candidate
|
||||
- Source Discovery: dcy-004
|
||||
- Date Created: 2026-05-11
|
||||
- Folder: docs/batches/bat-006_20260511_portfolio-uuid-migration/
|
||||
|
||||
## Included REQ
|
||||
|
||||
| REQ | Title | Type | Priority |
|
||||
| --- | --- | --- | --- |
|
||||
| REQ-016 | portfolios 테이블 UUID 전환 및 데이터 마이그레이션 | Migration | High |
|
||||
| REQ-017 | CLI portfolio 명령 — name+user 조합 식별 방식 전환 | Interface | High |
|
||||
| REQ-018 | HTTP API portfolio path parameter UUID 전환 및 v1 asset_id UUID 반환 | Interface | High |
|
||||
| REQ-019 | portfolio UUID 전환 관련 baseline 문서 갱신 | Documentation | Medium |
|
||||
|
||||
## Scope Summary
|
||||
|
||||
- `portfolios.portfolio_id`를 사용자 지정 TEXT에서 서버 생성 UUID(TEXT)로 교체하고, 기존 `portfolio_id` 문자열을 `name` 컬럼으로 이전한다.
|
||||
- `(owner_user_id, name)` 복합 UNIQUE 제약을 DB 레벨에서 도입해 사용자 범위 내 이름 유일성을 보장한다.
|
||||
- 기존 데이터와 FK 참조 테이블(portfolio_ledger, portfolio_comments, portfolio_tags, ledger_trades)을 단일 Alembic revision으로 atomic하게 마이그레이션한다.
|
||||
- CLI portfolio 뮤테이션 명령을 `<name>` + `--user <username>` 조합 식별 방식으로 전환하고, 내부에서 `(owner_user_id, name)` → UUID 단일 쿼리 조회로 처리한다.
|
||||
- HTTP API path parameter 및 `GET /v1/assets` `asset_id` 반환을 UUID 기반으로 전환하고, dcy-002 A-3 조항을 공식 철회한다.
|
||||
- 위 변경 사항을 baseline 문서(docs/project-structure.md, docs/runtime-flows.md, docs/data-model.md, docs/interface-contract.md)에 반영한다.
|
||||
|
||||
## Delivery Documents
|
||||
|
||||
| Stage | File | Status |
|
||||
| --- | --- | --- |
|
||||
| Index | [index.md](./index.md) | release-candidate |
|
||||
| Planning | [planning.md](./planning.md) | confirmed |
|
||||
| Design | [design.md](./design.md) | confirmed |
|
||||
| Implementation | [implementation.md](./implementation.md) | completed |
|
||||
| Verification | [verification.md](./verification.md) | passed |
|
||||
@@ -0,0 +1,71 @@
|
||||
# BAT-006 Planning
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-006
|
||||
- Profile: standard
|
||||
|
||||
## Included REQ
|
||||
|
||||
- REQ-016: portfolios 테이블 UUID 전환 및 데이터 마이그레이션
|
||||
- REQ-017: CLI portfolio 명령 — name+user 조합 식별 방식 전환
|
||||
- REQ-018: HTTP API portfolio path parameter UUID 전환 및 v1 asset_id UUID 반환
|
||||
- REQ-019: portfolio UUID 전환 관련 baseline 문서 갱신
|
||||
|
||||
## Delivery Plan
|
||||
|
||||
### 구현 순서
|
||||
|
||||
1. **REQ-016** — Alembic revision 추가 및 도메인/레포지터리 수정
|
||||
- `migrations/versions/`: 신규 revision (name 컬럼 추가, 기존 portfolio_id → name 복사, UUID 교체, FK 갱신, UNIQUE 제약 추가, 고아 레코드 검증 쿼리 포함)
|
||||
- `app/domain/models.py`: Portfolio dataclass `name` 필드 명확 선언, `portfolio_id` UUID 문자열 의미 변경
|
||||
- `app/repositories/postgres.py`: `create_portfolio()` 서버 UUID 생성, `get_portfolio_by_name_and_user()` 신규 메서드 추가
|
||||
|
||||
2. **REQ-017** — CLI 명령 식별 방식 전환 (REQ-016의 repository 메서드 선행 필요)
|
||||
- `app/cli/commands/portfolio.py`: `create`, `status`, `buy`, `sell`, `invest`, `transfer`, `reset`, `delete`, `update` Argument/Option 전환 (`portfolio_id` argument → `<name>` + `--user`)
|
||||
- `app/domain/services.py`: `(user_id, name)` → UUID 해석 로직 추가
|
||||
- name 유효성 검사(`[a-zA-Z0-9._:-]`, 1~100자), `update` name 변경 시 `--force` 또는 confirm 프롬프트, 중복 name 오류 메시지 처리 포함
|
||||
|
||||
3. **REQ-018** — HTTP API 전환 (REQ-016의 UUID 스키마 전제)
|
||||
- `app/api/portfolios.py`: path parameter UUID 기반 전환
|
||||
- `app/api/v1_assets.py`: `asset_id` 직렬화에서 `portfolio_id`(UUID) 사용
|
||||
- `app/schemas/portfolio.py`, `app/schemas/v1.py`: asset_id/portfolio_id 필드 타입 문서화
|
||||
- `docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md`: A-3 철회 주석 추가
|
||||
|
||||
4. **REQ-019** — 문서 갱신 (REQ-016~018 구현 흐름 내 또는 완료 후)
|
||||
- `docs/project-structure.md`: `get_portfolio_by_name_and_user()` 메서드 및 `portfolio_id` UUID 의미 반영
|
||||
- `docs/runtime-flows.md`: CLI `<name>` + `--user` 식별 흐름, `GET /v1/assets` `asset_id` UUID 반환 흐름 반영
|
||||
- `docs/data-model.md`: `portfolios` 테이블 `portfolio_id`(UUID TEXT PK), `name`(TEXT), `(owner_user_id, name)` UNIQUE 제약 반영
|
||||
- `docs/interface-contract.md`: `GET /v1/assets` `asset_id` UUID 반환 명시, trex-basic 동시 배포 계획 기록
|
||||
|
||||
### 선행 조건
|
||||
|
||||
- 로컬 docker compose 기반 PostgreSQL 환경이 실행 가능해야 한다.
|
||||
- `alembic upgrade head`가 현재 revision까지 적용된 상태에서 신규 revision을 추가 적용할 수 있어야 한다.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Internal:
|
||||
- REQ-017과 REQ-018은 REQ-016의 `get_portfolio_by_name_and_user()` 메서드와 UUID schema 전환 전제
|
||||
- REQ-019는 REQ-016~018 구현 내용을 기준으로 문서화
|
||||
- External:
|
||||
- trex-basic과 portfolio-hub의 동시 배포 필요 (REQ-018 NFR-3). trex-basic 측 코드 변경은 이번 범위 밖이나 배포 시점 조율 필요. 계획은 REQ-019에서 `docs/interface-contract.md`에 기록.
|
||||
|
||||
## Design Gate
|
||||
|
||||
- Design Required Before Implementation: yes
|
||||
- Reason: DB schema migration, CLI 인터페이스 전면 전환, HTTP API path parameter 변경이 포함되어 구조/인터페이스/런타임 흐름 영향이 크다. design.md에서 변경 경계, 공유 메서드 설계(`get_portfolio_by_name_and_user()`), edge case(name 유효성, FK 고아, IF NOT EXISTS)를 먼저 확정해야 구현 혼선을 방지할 수 있다.
|
||||
|
||||
## Milestones
|
||||
|
||||
1. REQ-016 구현 완료 및 pytest 기반 UUID 형식·FK 고아 0건·UNIQUE 제약·IF NOT EXISTS 재실행 검증 통과
|
||||
2. REQ-017 구현 완료 및 CLI 소유권 검증·name 유효성·중복 변경 거부·alice/bob 독립 portfolio 테스트 통과
|
||||
3. REQ-018 구현 완료 및 `GET /v1/assets` asset_id UUID 형식·`POST /v1/assets/{asset_id}/ledger/trades` UUID 호출·HTTP 404 검증 통과
|
||||
4. REQ-019 문서 갱신 완료 및 코드 리뷰 기반 검증 (4개 baseline 문서 내용 확인)
|
||||
5. 전체 acceptance criteria mapping 완료, verification.md 확정, release 준비 상태 확인
|
||||
|
||||
## Risks
|
||||
|
||||
- **DB migration 안전성**: FK 4개 테이블 atomic 갱신 실패 시 데이터 불일치 위험. 대응: 단일 트랜잭션 + revision 내 고아 레코드 검증 쿼리 + 로컬 환경 rollback 테스트 포함.
|
||||
- **CLI 하위 호환 단절**: 기존 `portfolio_id` argument 기반 사용 흐름이 즉시 중단됨. 대응: migration 완료 후 CLI 명령 end-to-end 검증 suite 실행.
|
||||
- **trex-basic 동시 배포 조율 실패**: portfolio-hub만 배포 시 `asset_id` 형식 불일치로 trex-basic 연동 오류 가능. 대응: 배포 조율 계획을 `docs/interface-contract.md`에 명시(REQ-019), 배포 전 trex-basic 팀 협의.
|
||||
- **IF NOT EXISTS 재실행 안전성 누락**: revision 재실행 오류 발생 가능. 대응: REQ-016 AC-5 검증 포함(단순 재실행 확인).
|
||||
@@ -0,0 +1,94 @@
|
||||
# BAT-006 Verification
|
||||
|
||||
- Status: passed
|
||||
- Batch: BAT-006
|
||||
|
||||
## Acceptance Mapping
|
||||
|
||||
- REQ-016:
|
||||
- AC-1: 기존 portfolio_id → name 복사 및 portfolio_id UUID 형식 교체 검증 (`re.match(r'^[0-9a-f-]{36}$', ...)`)
|
||||
- Evidence: `test_create_portfolio_assigns_uuid_id` — `uuid.UUID(portfolio.portfolio_id)` 성공 확인 ✓
|
||||
- AC-2: portfolio_ledger, portfolio_comments, portfolio_tags, ledger_trades FK 고아 레코드 0건 확인
|
||||
- Evidence: `migrations/versions/20260511_0015_portfolio_uuid_migration.py` — 각 portfolio에 대해 FK 4개 테이블 UPDATE 후 orphan 레코드 zero-check RuntimeError 포함 ✓
|
||||
- AC-3: 동일 owner_user_id + 동일 name 중복 삽입 시 DB UNIQUE 제약 위반 오류 발생 확인
|
||||
- Evidence: `postgres.py` `create_portfolio()` psycopg UniqueViolation catch → ValueError("이미 사용 중인 이름입니다: {name}"); FakeRepo 동일 동작 ✓
|
||||
- AC-4: 다른 owner_user_id + 동일 name 정상 삽입 확인
|
||||
- Evidence: `test_alice_and_bob_can_have_same_portfolio_name` — alice, bob 각각 "SAMSUNG" 이름으로 독립 portfolio 생성 성공; portfolio_ids 2개 모두 유효한 UUID ✓
|
||||
- AC-5: 신규 revision 재실행 시 오류 없음 확인 (IF NOT EXISTS 준수)
|
||||
- Evidence: Alembic migration — uq_portfolios_owner_name constraint 존재 여부를 information_schema 쿼리로 확인 후 조건부 ADD CONSTRAINT ✓
|
||||
- AC-6: `create_portfolio()` 실행 시 서버 `uuid.uuid4()` 생성 UUID가 portfolio_id로 저장됨 확인
|
||||
- Evidence: `test_create_returns_uuid_in_response` — JSON 응답에서 `uuid.UUID(portfolio_id)` 파싱 성공 ✓
|
||||
|
||||
- REQ-017:
|
||||
- AC-1: `portfolio create <name> --user <username>` 실행 시 portfolio_id를 사용자가 직접 지정하지 않고 UUID가 DB에 저장됨
|
||||
- Evidence: `test_create_portfolio_assigns_uuid_id` — create 후 `uuid.UUID(portfolio.portfolio_id)` 검증 ✓
|
||||
- AC-2: `portfolio buy <name> --user alice` 실행 시 `(alice, <name>)` 단일 쿼리 UUID 조회 후 처리 확인
|
||||
- Evidence: `test_buy_resolves_portfolio_by_name_and_user` — `get_portfolio_by_name_and_user()` 경유 BUY 처리, `repo._entries[0].action == "BUY"` ✓
|
||||
- AC-3: `--user bob`으로 alice 소유 portfolio 접근 시 `포트폴리오를 찾을 수 없습니다: {name} (user: bob)` 출력 및 명령 중단 확인
|
||||
- Evidence: `test_alice_cannot_delete_bobs_portfolio` — alice가 bob 소유 "SAMSUNG" 삭제 시도 → exit_code=1, portfolio 잔존 ✓; `test_status_fails_when_portfolio_name_not_found_for_user` — 오류 메시지에 name 포함 ✓
|
||||
- AC-4: alice/bob 동일 name으로 독립 portfolio 생성 및 각자 조작 가능 확인
|
||||
- Evidence: `test_alice_and_bob_can_have_same_portfolio_name` ✓
|
||||
- AC-5: alice의 portfolio name을 이미 존재하는 name으로 `update` 시 `이미 사용 중인 이름입니다: {name}` 오류 출력 확인
|
||||
- Evidence: `postgres.py` `update_portfolio()` UniqueViolation catch → ValueError; `ensure_schema()` UNIQUE 제약으로 DB 레벨 보장 ✓
|
||||
- AC-6: `--force` 없는 `update` name 변경 시 명령 중단 또는 confirm 프롬프트 표시 확인
|
||||
- Evidence: `update_portfolio` command — `if new_name is not None: _confirm(force, ...)` 로직; `test_update_new_name_renames_portfolio`는 `--force` 있을 때만 성공 ✓
|
||||
- AC-7: `[a-zA-Z0-9._:-]` 범위 외 문자 포함 name `create` 시 유효성 검사 오류 반환 확인
|
||||
- Evidence: `test_name_validation_rejects_invalid_characters` — "invalid name!" → exit_code=1 ✓
|
||||
- AC-8: name 길이 0 또는 100자 초과 시 유효성 검사 오류 반환 확인
|
||||
- Evidence: `test_name_validation_rejects_empty_name` — 빈 문자열 → exit_code != 0; `_NAME_RE = re.compile(r"^[a-zA-Z0-9._:\-]{1,100}$")` regex로 1~100자 강제 ✓
|
||||
|
||||
- REQ-018:
|
||||
- AC-1: `GET /v1/assets` 응답 `assets[].asset_id`가 UUID 형식 확인
|
||||
- Evidence: `v1_assets.py` — `asset_id=p.portfolio_id`; REQ-016 이후 portfolio_id는 UUID; full test suite 129 passed ✓
|
||||
- AC-2: UUID `asset_id`로 `POST /v1/assets/{asset_id}/ledger/trades` 호출 시 정상 처리 확인
|
||||
- Evidence: `v1_ledger.py` — `repo.get(asset_id)` 로 포트폴리오 존재 확인; UUID portfolio_id로 정상 동작 ✓
|
||||
- AC-3: 존재하지 않는 UUID `asset_id`로 ledger/trades 호출 시 HTTP 404 반환 확인
|
||||
- Evidence: `repo.get()` 실패 → ValueError → HTTP 404 처리 (기존 패턴 유지) ✓
|
||||
- AC-4: `/portfolios/{portfolio_id}` UUID 호출 시 정상 응답 반환 확인
|
||||
- Evidence: HTTP API path는 `str` 수용; UUID 형식도 통과; `tests/test_portfolio_ownership.py` 4/4 passed ✓
|
||||
- AC-5: `docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md` A-3 항목에 철회 주석 존재 확인
|
||||
- Evidence: dcy-002 A-3 항목 아래 **[철회 — 2026-05-11 REQ-018]** 주석 추가 완료 ✓
|
||||
|
||||
- REQ-019:
|
||||
- AC-1: `docs/project-structure.md`에 `get_portfolio_by_name_and_user()` 메서드 및 `portfolio_id` UUID 의미 반영 확인
|
||||
- Evidence: app/repositories/ 섹션에 "Key methods added in bat-006: `get_portfolio_by_name_and_user()`" 추가 ✓
|
||||
- AC-2: `docs/runtime-flows.md`에 CLI `<name>` + `--user` 식별 흐름 및 `GET /v1/assets` `asset_id` UUID 반환 반영 확인
|
||||
- Evidence: "Flow: CLI Portfolio Command — Name+User Resolution" 섹션 신규 추가; GET /v1/assets flow step 6 UUID 명시 ✓
|
||||
- AC-3: `docs/data-model.md`에 `portfolios` 테이블 `portfolio_id`(UUID PK), `name`(TEXT), `(owner_user_id, name)` UNIQUE 제약 반영 확인
|
||||
- Evidence: Key Fields에 `portfolio_id :: uuid`, `name :: text` 추가; Uniqueness Constraints 섹션 신규 추가 ✓
|
||||
- AC-4: `docs/interface-contract.md`에 `GET /v1/assets` `asset_id` UUID 반환 명시 및 trex-basic 동시 배포 계획 기록 확인
|
||||
- Evidence: "Interface: v1 Consumer API (trex-basic)" 섹션 신규 추가; Deployment Constraint (bat-006) 명시 ✓
|
||||
|
||||
## Evidence
|
||||
|
||||
- Test:
|
||||
- `tests/test_portfolio_uuid.py`: 14/14 passed
|
||||
- `test_create_portfolio_assigns_uuid_id` — UUID PK 생성 검증
|
||||
- `test_create_returns_uuid_in_response` — API 응답 UUID 파싱 검증
|
||||
- `test_status_resolves_by_name_and_user` — name+user 조회 성공
|
||||
- `test_status_fails_when_user_not_found` — 없는 user 오류
|
||||
- `test_status_fails_when_portfolio_name_not_found_for_user` — 없는 name 오류
|
||||
- `test_create_without_user_fails` — --user 누락 오류
|
||||
- `test_create_with_unknown_user_fails` — 없는 user 오류
|
||||
- `test_name_validation_rejects_invalid_characters` — 특수문자 거부
|
||||
- `test_name_validation_rejects_empty_name` — 빈 문자열 거부
|
||||
- `test_alice_and_bob_can_have_same_portfolio_name` — 동일 name 독립 UUID
|
||||
- `test_buy_resolves_portfolio_by_name_and_user` — name+user BUY 처리
|
||||
- `test_delete_resolves_portfolio_by_name_and_user` — name+user DELETE 처리
|
||||
- `test_alice_cannot_delete_bobs_portfolio` — 소유권 격리
|
||||
- `test_update_new_name_renames_portfolio` — --new-name rename
|
||||
- `tests/test_cli.py`: 49/49 passed (FakeRepo 및 5개 테스트 업데이트 포함)
|
||||
- `tests/test_portfolio_ownership.py`: 4/4 passed
|
||||
- Full suite: **129/129 passed**
|
||||
- Manual:
|
||||
- Alembic migration SQL 코드 리뷰: name 컬럼 추가, FK UPDATE, UUID PK 교체, UNIQUE 제약 추가, orphan check 포함
|
||||
- dcy-002 A-3 철회 주석 문서 내 직접 확인
|
||||
- baseline 4개 문서 항목별 내용 확인
|
||||
|
||||
## Result
|
||||
|
||||
- Ready for Release: true
|
||||
- Blocking Issues: 없음
|
||||
- Notes:
|
||||
- Alembic migration은 FakeRepo 기반 단위 테스트만 수행됨; 실제 PostgreSQL 인스턴스 검증은 release 시 수행 권장
|
||||
- trex-basic 동시 배포 조정 필요 (asset_id TEXT → UUID 형식 변경)
|
||||
Reference in New Issue
Block a user