feat(REQ-014): add portfolio-create-user-required requirement and implementation

- Add docs/srs/Interface/req-014_portfolio-create-user-required.md with acceptance criteria
- Create docs/batches/bat-004_20260502_portfolio-create-user-required/ delivery batch
- Enhance app/cli/commands/portfolio.py: validate user_id parameter before portfolio creation
- Add comprehensive CLI tests in tests/test_cli.py for portfolio create validation flows
- Update docs/batches/index.md and docs/srs/index.md with new requirement/batch references
- Update README.md documentation
This commit is contained in:
2026-05-02 13:45:46 +00:00
parent 2f686f79ce
commit a111fdd83f
10 changed files with 368 additions and 16 deletions
+51
View File
@@ -485,6 +485,7 @@ def test_cli_status_supports_json_output() -> None:
def test_cli_create_buy_and_delete_flow() -> None:
repo = FakeRepo()
user = repo.create_user("alice", permission="read")
runner = build_runner(repo)
create_result = runner.invoke(
@@ -495,6 +496,8 @@ def test_cli_create_buy_and_delete_flow() -> None:
"005930:tv",
"--symbol",
"005930",
"--user",
"alice",
"--cash-balance",
"1000000",
"--strategy-params",
@@ -518,6 +521,7 @@ def test_cli_create_buy_and_delete_flow() -> None:
],
)
assert repo.entries[0].action == "BUY"
assert repo._portfolio.owner_user_id == user.id
delete_result = runner.invoke(runner.app, ["portfolio", "delete", "005930:tv", "--keep-ledger", "--force"])
assert create_result.exit_code == 0
@@ -528,6 +532,7 @@ def test_cli_create_buy_and_delete_flow() -> None:
def test_cli_write_command_prompts_before_create() -> None:
repo = FakeRepo()
repo.create_user("alice", permission="read")
runner = build_runner(repo)
result = runner.invoke(
@@ -538,6 +543,8 @@ def test_cli_write_command_prompts_before_create() -> None:
"005930:tv",
"--symbol",
"005930",
"--user",
"alice",
"--cash-balance",
"1000000",
],
@@ -550,6 +557,7 @@ def test_cli_write_command_prompts_before_create() -> None:
def test_cli_write_command_can_abort() -> None:
repo = FakeRepo()
repo.create_user("alice", permission="read")
runner = build_runner(repo)
result = runner.invoke(
@@ -560,6 +568,8 @@ def test_cli_write_command_can_abort() -> None:
"005930:tv",
"--symbol",
"005930",
"--user",
"alice",
"--cash-balance",
"1000000",
],
@@ -570,6 +580,47 @@ def test_cli_write_command_can_abort() -> None:
assert repo._portfolio is None
def test_cli_portfolio_create_without_user_exits_error() -> None:
repo = FakeRepo()
runner = build_runner(repo)
result = runner.invoke(
runner.app,
["portfolio", "create", "005930:tv", "--symbol", "005930", "--force"],
)
assert result.exit_code == 1
assert "--user" in result.stdout
def test_cli_portfolio_create_with_valid_user_sets_owner() -> None:
repo = FakeRepo()
user = repo.create_user("alice", permission="read")
runner = build_runner(repo)
result = runner.invoke(
runner.app,
["portfolio", "create", "005930:tv", "--symbol", "005930", "--user", "alice", "--force"],
)
assert result.exit_code == 0
assert repo._portfolio is not None
assert repo._portfolio.owner_user_id == user.id
def test_cli_portfolio_create_with_unknown_user_exits_error() -> None:
repo = FakeRepo()
runner = build_runner(repo)
result = runner.invoke(
runner.app,
["portfolio", "create", "005930:tv", "--symbol", "005930", "--user", "nobody", "--force"],
)
assert result.exit_code == 1
assert "nobody" in result.stdout
def test_cli_health_check_reports_repository() -> None:
repo = FakeRepo(_portfolio())
runner = build_runner(repo)