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:
2026-05-11 09:27:57 +00:00
parent 9ed307555a
commit 829629fa8f
46 changed files with 3260 additions and 220 deletions
@@ -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