feat(req-013): add portfolio-list-user-required requirement and batch
- Add REQ-013: Portfolio list filtered by user requirement with acceptance criteria - Add DCY-003 discovery document for portfolio list user filtering - Create BAT-003 batch with planning, design, implementation, and verification - Enhance app/cli/commands/portfolio.py with user-based list filtering - Add comprehensive tests for portfolio list CLI command with user filtering - Update docs indices (discovery, srs, batches) - Improve README with latest capability status
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
# BAT-003 Implementation
|
||||
|
||||
- Status: completed
|
||||
- Batch: BAT-003
|
||||
- Profile: batch-lite
|
||||
|
||||
## Plan Summary
|
||||
|
||||
- CLI `pfhub portfolio list`에 `--user <username>` 필수 옵션 추가
|
||||
- `_with_service_and_repo` 헬퍼로 repository 직접 접근 패턴 구현
|
||||
- `repository.get_user_by_username(user)` 호출 → None이면 ValueError → exit code 1
|
||||
- `service.list_portfolios(owner_user_id=owner.id, ...)` 소유자 필터 항상 전달
|
||||
- README 예제 라인 갱신, breaking change 반영
|
||||
- 기존 테스트 2개 수정 + 신규 4개 추가
|
||||
|
||||
## Changed Files
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `app/cli/commands/portfolio.py` | `_with_service_and_repo` 헬퍼 추가, `list_portfolios` `--user` 필수화 및 소유자 필터 적용 |
|
||||
| `tests/test_cli.py` | `test_cli_list_renders_table`, `test_cli_list_supports_tag_filters` 수정; AC 1~4 커버 4개 케이스 추가 |
|
||||
| `README.md` | `pfhub portfolio list` → `pfhub portfolio list --user <username>` 갱신 |
|
||||
|
||||
## Execution Log
|
||||
|
||||
### 구현 결정 사항
|
||||
|
||||
- `--user` 옵션: `typer.Option(None, ...)` + 수동 None 체크로 exit code 1 보장 (typer required 옵션은 exit code 2이므로 AC-1 불일치)
|
||||
- username → user_id 조회: `_with_service_and_repo` 콜백 내 `repository.get_user_by_username(user)` 직접 호출
|
||||
- 존재하지 않는 username: `ValueError` raise → `execute_command`가 exit code 1로 처리
|
||||
- `_with_service_and_repo`: 기존 `_with_service`와 동일 패턴, callback 인자에 `repository` 추가
|
||||
|
||||
### 기존 테스트 수정
|
||||
|
||||
- `test_cli_list_renders_table`: `FakeRepo()` → user alice 생성 + `portfolio.owner_user_id` 설정 + `--user alice` 추가
|
||||
- `test_cli_list_supports_tag_filters`: 동일 패턴 적용
|
||||
|
||||
## Validation
|
||||
|
||||
```
|
||||
pytest tests/test_cli.py -k "test_cli_list_renders_table or test_cli_list_supports_tag_filters or test_cli_portfolio_list" -v
|
||||
```
|
||||
|
||||
```
|
||||
6 passed
|
||||
```
|
||||
|
||||
```
|
||||
pytest tests/test_cli.py -v
|
||||
```
|
||||
|
||||
```
|
||||
41 passed in 1.22s (전체 회귀, 기존 테스트 전원 통과)
|
||||
```
|
||||
|
||||
## Remaining Risks
|
||||
|
||||
- 없음
|
||||
@@ -0,0 +1,42 @@
|
||||
# BAT-003 Index
|
||||
|
||||
- Status: release-candidate
|
||||
- Batch: BAT-003
|
||||
- Profile: batch-lite
|
||||
- Created: 2026-05-02
|
||||
|
||||
## Included REQ
|
||||
|
||||
| REQ | 제목 | Type | Priority |
|
||||
|-----|------|------|----------|
|
||||
| REQ-013 | CLI `portfolio list --user` 필수화 및 소유자 기반 조회 | Interface | Medium |
|
||||
|
||||
## Source Discovery
|
||||
|
||||
- [dcy-003_20260502_portfolio-list-user-required](../../discovery/dcy-003_20260502_portfolio-list-user-required.md)
|
||||
|
||||
## Scope Summary
|
||||
|
||||
- CLI `pfhub portfolio list` 명령에 `--user <username>` 필수 옵션 추가
|
||||
- username → owner_user_id(UUID) 변환 로직 추가 (repository.get_user_by_username 직접 호출)
|
||||
- `PortfolioService.list_portfolios(owner_user_id=...)` 호출 시 소유자 필터 항상 전달
|
||||
- `--user` 미입력 시 exit code 1 오류 종료, 존재하지 않는 username 시 오류 종료
|
||||
- README 예제 라인 갱신 (breaking change 안내 포함)
|
||||
- DB 스키마 변경 없음, HTTP API 변경 없음
|
||||
|
||||
## Impacted Area
|
||||
|
||||
- Docs:
|
||||
- `README.md` — `pfhub portfolio list` 예제 라인 `--user <username>` 옵션 추가, breaking change 안내 추가
|
||||
- Modules:
|
||||
- `app/cli/commands/portfolio.py` — `list_portfolios` 명령 시그니처 및 서비스 호출부 수정
|
||||
- Tests:
|
||||
- `tests/test_cli.py` — AC 1~5 커버 테스트 케이스 4개 추가
|
||||
|
||||
## Stage Documents
|
||||
|
||||
| Stage | File | Status |
|
||||
|-------|------|--------|
|
||||
| Planning | [planning.md](./planning.md) | confirmed |
|
||||
| Implementation | [implementation.md](./implementation.md) | completed |
|
||||
| Verification | [verification.md](./verification.md) | verified |
|
||||
@@ -0,0 +1,89 @@
|
||||
# BAT-003 Planning
|
||||
|
||||
- Status: draft
|
||||
- Batch: BAT-003
|
||||
- Profile: batch-lite
|
||||
|
||||
## Included REQ
|
||||
|
||||
- REQ-013: CLI `portfolio list --user` 필수화 및 소유자 기반 조회
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- `--all` 관리자 전체 조회 옵션 (후속 Discovery로 분리)
|
||||
- HTTP API `GET /portfolios` 접근 통제 변경 (현행 유지)
|
||||
- CLI에 `_ownership_required` 설정 플래그 도입
|
||||
|
||||
## 구현 순서
|
||||
|
||||
### 1단계: CLI 명령 수정
|
||||
|
||||
- `app/cli/commands/portfolio.py` — `list_portfolios` 명령 수정
|
||||
- 현재 시그니처:
|
||||
```python
|
||||
@portfolio_app.command("list")
|
||||
def list_portfolios(
|
||||
ctx: typer.Context,
|
||||
broker: str | None = ...,
|
||||
status: str | None = ...,
|
||||
include_tags: ...,
|
||||
exclude_tags: ...,
|
||||
match_mode: ...,
|
||||
)
|
||||
```
|
||||
- 변경 내용:
|
||||
- `user: str = typer.Option(..., "--user", help="Filter by username (required).")` 필수 옵션 추가
|
||||
- `_with_service` callback 안에서 `repository.get_user_by_username(user)` 호출
|
||||
- user가 `None`이면 오류 출력 + `raise typer.Exit(code=1)` (typer의 `required=True` 또는 직접 검증)
|
||||
- 조회 결과가 `None`이면 "사용자를 찾을 수 없습니다" 메시지 + `raise typer.Exit(code=1)`
|
||||
- `service.list_portfolios(owner_user_id=found_user.id, ...)` 로 호출
|
||||
|
||||
- 참고: `_with_service` 헬퍼는 `PortfolioService`만 생성하므로, username → user_id 조회는 callback 내부에서 `repository.get_user_by_username(user)`를 직접 호출한다. callback 인자로 `(service, app_context)` 대신 repository도 사용 가능 — 기존 `_with_service`에서 `repository = app_context.repository_factory(settings)` 반환 객체를 callback에 노출하는 방식으로 처리한다.
|
||||
|
||||
구현 패턴 (예시):
|
||||
```python
|
||||
def _with_service_and_repo(ctx, callback):
|
||||
app_context = ctx.obj
|
||||
repository = app_context.repository_factory(app_context.settings)
|
||||
service = PortfolioService(repository)
|
||||
try:
|
||||
result = execute_command(
|
||||
app_context.console,
|
||||
callback=lambda: callback(service, repository, app_context),
|
||||
)
|
||||
return result
|
||||
finally:
|
||||
if callable(getattr(type(repository), "close", None)):
|
||||
repository.close()
|
||||
```
|
||||
또는 기존 `_with_service` callback 안에서 `service._repository`를 통해 접근하는 방법도 검토한다. 구현 시 결정.
|
||||
|
||||
### 2단계: README 수정
|
||||
|
||||
- `README.md` — CLI 예제 섹션의 `pfhub portfolio list` 라인을 `pfhub portfolio list --user <username>`으로 변경
|
||||
- breaking change 안내 문구 추가 (Service token issue policy 아래 또는 CLI 섹션 내)
|
||||
|
||||
### 3단계: 테스트 추가
|
||||
|
||||
- `tests/test_cli.py` — 4개 테스트 케이스 추가:
|
||||
1. `test_cli_portfolio_list_without_user_exits_error`
|
||||
- FakeRepo 준비, `--user` 없이 호출 → `exit_code == 1`, 오류 메시지 포함 확인
|
||||
2. `test_cli_portfolio_list_with_valid_user_filters_by_owner`
|
||||
- FakeRepo에 복수 사용자 + 포트폴리오 준비, `--user alice` 호출 → alice 소유 항목만 반환
|
||||
3. `test_cli_portfolio_list_with_unknown_user_exits_error`
|
||||
- 존재하지 않는 username으로 호출 → `exit_code == 1`, "찾을 수 없습니다" 포함 확인
|
||||
4. `test_cli_portfolio_list_in_memory_mode_filters_owner`
|
||||
- in-memory 모드에서도 동일하게 소유자 필터 적용 확인 (AC 4)
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Internal:
|
||||
- `app/repositories/portfolio_repository.py` — `get_user_by_username` Protocol stub 존재 확인 완료 (line 16)
|
||||
- `app/repositories/postgres.py` — `get_user_by_username` 구현 존재 확인 완료 (line 419)
|
||||
- `tests/test_cli.py` — `FakeRepo.get_user_by_username` 존재 확인 완료 (line 109)
|
||||
- External: 없음
|
||||
|
||||
## Design Gate
|
||||
|
||||
- Design Required Before Implementation: no
|
||||
- Reason: DB 스키마 변경 없음, HTTP API 변경 없음. CLI 옵션 추가 + repository 메서드 직접 호출 패턴은 기존 service-token issue 구현과 동일 수준. 구조적 결정 사항 없음.
|
||||
@@ -0,0 +1,72 @@
|
||||
# BAT-003 Verification
|
||||
|
||||
- Status: verified
|
||||
- Batch: BAT-003
|
||||
- Profile: batch-lite
|
||||
|
||||
## Architecture Impact Check
|
||||
|
||||
- Design Gate (from planning.md): no — DB 스키마 변경 없음, HTTP API 변경 없음
|
||||
- Actual Architecture Impact: none confirmed — `app/cli/commands/portfolio.py` 내부 헬퍼 추가, Protocol stub/구현체 변경 없음
|
||||
- Baseline Document Sync Required: no
|
||||
|
||||
## Acceptance Mapping
|
||||
|
||||
### AC 1: `--user` 없이 실행 → exit code 1 + 오류 메시지
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| Evidence | `test_cli_portfolio_list_without_user_exits_error` |
|
||||
| Verification | `runner.invoke(app, ["portfolio", "list"])` → `exit_code == 1`, `"--user" in result.stdout` |
|
||||
| Result | ✅ PASSED |
|
||||
|
||||
### AC 2: `--user alice` → alice 소유 포트폴리오만 반환
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| Evidence | `test_cli_portfolio_list_with_valid_user_filters_by_owner` |
|
||||
| Verification | alice 포트폴리오만 등록 → alice 조회 시 `005930:tv` 포함, bob 조회 시 미포함 확인 |
|
||||
| Result | ✅ PASSED |
|
||||
|
||||
### AC 3: 존재하지 않는 username → 오류 메시지 + exit code 1
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| Evidence | `test_cli_portfolio_list_with_unknown_user_exits_error` |
|
||||
| Verification | `--user nobody` → `exit_code == 1`, `"nobody" in result.stdout` |
|
||||
| Result | ✅ PASSED |
|
||||
|
||||
### AC 4: in-memory 모드에서도 동일 동작
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| Evidence | `test_cli_portfolio_list_in_memory_mode_filters_owner` |
|
||||
| Verification | FakeRepo(in-memory) 사용, `--user alice` → alice 소유 포트폴리오 반환 확인 |
|
||||
| Result | ✅ PASSED |
|
||||
|
||||
### AC 5: 오류 형식이 기존 CLI와 일관성 유지
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| Evidence | 코드 검토 — `execute_command`의 `ValueError` 핸들러(`console.print + raise typer.Exit(code=1)`)와 동일 패턴 사용 |
|
||||
| Verification | `--user` 미입력 → `ctx.obj.console.print(...)` + `raise typer.Exit(code=1)`, 미등록 user → `ValueError` → `execute_command` 핸들러 동일 경로 |
|
||||
| Result | ✅ 충족 |
|
||||
|
||||
## Test Execution
|
||||
|
||||
```
|
||||
pytest tests/test_cli.py -v
|
||||
41 passed in 1.22s
|
||||
```
|
||||
|
||||
- 신규 케이스 6개 (`test_cli_portfolio_list_*`, 수정된 `test_cli_list_renders_table`, `test_cli_list_supports_tag_filters`) 포함 전체 통과
|
||||
- 기존 37개 케이스 회귀 없음
|
||||
|
||||
## Blocking Issues
|
||||
|
||||
- 없음
|
||||
|
||||
## Result
|
||||
|
||||
- Release Ready: yes
|
||||
- Notes: batch-lite, design 없음, planning의 Design Gate = no, architecture impact = none confirmed
|
||||
@@ -9,4 +9,5 @@
|
||||
| BAT ID | Profile | Status | Included REQ | Discovery | Folder |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| BAT-001 | standard | release-candidate | REQ-004, REQ-005, REQ-006, REQ-007, REQ-008, REQ-009, REQ-010 | dcy-002 | [bat-001_20260502_trex-basic-v1-api-contract](./bat-001_20260502_trex-basic-v1-api-contract/) |
|
||||
| BAT-002 | batch-lite | release-candidate | REQ-012 | minor-change | [bat-002_20260502_service-token-rotate-cli](./bat-002_20260502_service-token-rotate-cli/) |
|
||||
| BAT-002 | batch-lite | release-candidate | REQ-012 | minor-change | [bat-002_20260502_service-token-rotate-cli](./bat-002_20260502_service-token-rotate-cli/) |
|
||||
| BAT-003 | batch-lite | release-candidate | REQ-013 | dcy-003 | [bat-003_20260502_portfolio-list-user-required](./bat-003_20260502_portfolio-list-user-required/) |
|
||||
Reference in New Issue
Block a user