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:
2026-05-02 13:22:41 +00:00
parent b4d964253b
commit 2f686f79ce
12 changed files with 699 additions and 13 deletions
+68 -4
View File
@@ -458,10 +458,14 @@ def _portfolio() -> Portfolio:
def test_cli_list_renders_table() -> None:
repo = FakeRepo(_portfolio())
repo = FakeRepo()
user = repo.create_user("alice", permission="read")
p = _portfolio()
p.owner_user_id = user.id
repo._portfolio = p
runner = build_runner(repo)
result = runner.invoke(runner.app, ["portfolio", "list"])
result = runner.invoke(runner.app, ["portfolio", "list", "--user", "alice"])
assert result.exit_code == 0
assert "005930:tv" in result.stdout
@@ -820,11 +824,15 @@ def test_cli_tag_add_and_list() -> None:
def test_cli_list_supports_tag_filters() -> None:
repo = FakeRepo(_portfolio())
repo = FakeRepo()
user = repo.create_user("alice", permission="read")
p = _portfolio()
p.owner_user_id = user.id
repo._portfolio = p
repo.add_tag("005930:tv", tag="grade:A")
runner = build_runner(repo)
result = runner.invoke(runner.app, ["portfolio", "list", "--include-tag", "grade:A", "--match-mode", "AND"])
result = runner.invoke(runner.app, ["portfolio", "list", "--include-tag", "grade:A", "--match-mode", "AND", "--user", "alice"])
assert result.exit_code == 0
assert "005930:tv" in result.stdout
@@ -1166,3 +1174,59 @@ def test_cli_service_token_rotate_can_abort() -> None:
token = repo.get_service_token_by_name("permanent")
assert token is not None
assert token.token_prefix == "pref"
# ---------------------------------------------------------------------------
# portfolio list --user (REQ-013)
# ---------------------------------------------------------------------------
def test_cli_portfolio_list_without_user_exits_error() -> None:
repo = FakeRepo()
runner = build_runner(repo)
result = runner.invoke(runner.app, ["portfolio", "list"])
assert result.exit_code == 1
assert "--user" in result.stdout
def test_cli_portfolio_list_with_valid_user_filters_by_owner() -> None:
repo = FakeRepo()
alice = repo.create_user("alice", permission="read")
bob = repo.create_user("bob", permission="read")
alice_portfolio = _portfolio()
alice_portfolio.owner_user_id = alice.id
repo._portfolio = alice_portfolio
runner = build_runner(repo)
result_alice = runner.invoke(runner.app, ["portfolio", "list", "--user", "alice"])
result_bob = runner.invoke(runner.app, ["portfolio", "list", "--user", "bob"])
assert result_alice.exit_code == 0
assert "005930:tv" in result_alice.stdout
assert result_bob.exit_code == 0
assert "005930:tv" not in result_bob.stdout
def test_cli_portfolio_list_with_unknown_user_exits_error() -> None:
repo = FakeRepo()
runner = build_runner(repo)
result = runner.invoke(runner.app, ["portfolio", "list", "--user", "nobody"])
assert result.exit_code == 1
assert "nobody" in result.stdout
def test_cli_portfolio_list_in_memory_mode_filters_owner() -> None:
repo = FakeRepo()
user = repo.create_user("alice", permission="read")
p = _portfolio()
p.owner_user_id = user.id
repo._portfolio = p
runner = build_runner(repo)
result = runner.invoke(runner.app, ["portfolio", "list", "--user", "alice"])
assert result.exit_code == 0
assert "005930:tv" in result.stdout