Add ProofLine criteria documentation and Strategy Catalog v1 implementation
- Add acceptance criteria documentation (AC-0013 through AC-0023) - Add Line-0003 discovery documentation - Implement Strategy Catalog v1 with new database migration - Update portfolio API endpoints and schemas - Refactor strategy and portfolio services - Add strategy management CLI commands - Update domain models with strategy catalog support - Add comprehensive tests for v1 strategies - Update design documentation (data model, interfaces, runtime flows)
This commit is contained in:
@@ -201,7 +201,7 @@ class FakeRepo:
|
||||
def get_strategy_set(self, id: str) -> StrategySet | None:
|
||||
return self._strategy_sets.get(id)
|
||||
|
||||
def list_strategy_sets(self) -> list[StrategySet]:
|
||||
def list_strategy_sets(self, *, include_inactive: bool = True) -> list[StrategySet]:
|
||||
return list(self._strategy_sets.values())
|
||||
|
||||
def delete_strategy_set(self, id: str) -> None:
|
||||
@@ -350,7 +350,7 @@ def test_create_portfolio_assigns_uuid_id() -> None:
|
||||
|
||||
result = runner.invoke(
|
||||
runner.app,
|
||||
["portfolio", "create", "SAMSUNG", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--force"],
|
||||
["portfolio", "create", "SAMSUNG", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--strategy-id", "set-1", "--force"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
@@ -371,7 +371,7 @@ def test_create_returns_uuid_in_response() -> None:
|
||||
|
||||
result = runner.invoke(
|
||||
runner.app,
|
||||
["--format", "json", "portfolio", "create", "MY_PORTFOLIO", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--force"],
|
||||
["--format", "json", "portfolio", "create", "MY_PORTFOLIO", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--strategy-id", "set-1", "--force"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
@@ -389,7 +389,7 @@ def test_status_resolves_by_name_and_user() -> None:
|
||||
|
||||
create_result = runner.invoke(
|
||||
runner.app,
|
||||
["--format", "json", "portfolio", "create", "MY_PORT", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--force"],
|
||||
["--format", "json", "portfolio", "create", "MY_PORT", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--strategy-id", "set-1", "--force"],
|
||||
)
|
||||
assert create_result.exit_code == 0, create_result.output
|
||||
|
||||
@@ -437,7 +437,7 @@ def test_create_without_user_fails() -> None:
|
||||
|
||||
result = runner.invoke(
|
||||
runner.app,
|
||||
["portfolio", "create", "MY_PORT", "--asset-id", _TEST_ASSET_ID, "--force"],
|
||||
["portfolio", "create", "MY_PORT", "--asset-id", _TEST_ASSET_ID, "--strategy-id", "set-1", "--force"],
|
||||
)
|
||||
|
||||
assert result.exit_code != 0
|
||||
@@ -450,7 +450,7 @@ def test_create_with_unknown_user_fails() -> None:
|
||||
|
||||
result = runner.invoke(
|
||||
runner.app,
|
||||
["portfolio", "create", "MY_PORT", "--asset-id", _TEST_ASSET_ID, "--user", "ghost", "--force"],
|
||||
["portfolio", "create", "MY_PORT", "--asset-id", _TEST_ASSET_ID, "--user", "ghost", "--strategy-id", "set-1", "--force"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 1
|
||||
@@ -464,7 +464,7 @@ def test_name_validation_rejects_invalid_characters() -> None:
|
||||
|
||||
result = runner.invoke(
|
||||
runner.app,
|
||||
["portfolio", "create", "invalid name!", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--force"],
|
||||
["portfolio", "create", "invalid name!", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--strategy-id", "set-1", "--force"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 1
|
||||
@@ -478,7 +478,7 @@ def test_name_validation_rejects_empty_name() -> None:
|
||||
|
||||
result = runner.invoke(
|
||||
runner.app,
|
||||
["portfolio", "create", "", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--force"],
|
||||
["portfolio", "create", "", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--strategy-id", "set-1", "--force"],
|
||||
)
|
||||
|
||||
assert result.exit_code != 0
|
||||
@@ -492,11 +492,11 @@ def test_alice_and_bob_can_have_same_portfolio_name() -> None:
|
||||
|
||||
alice_result = runner.invoke(
|
||||
runner.app,
|
||||
["portfolio", "create", "SAMSUNG", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--force"],
|
||||
["portfolio", "create", "SAMSUNG", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--strategy-id", "set-1", "--force"],
|
||||
)
|
||||
bob_result = runner.invoke(
|
||||
runner.app,
|
||||
["portfolio", "create", "SAMSUNG", "--asset-id", _TEST_ASSET_ID, "--user", "bob", "--force"],
|
||||
["portfolio", "create", "SAMSUNG", "--asset-id", _TEST_ASSET_ID, "--user", "bob", "--strategy-id", "set-1", "--force"],
|
||||
)
|
||||
|
||||
assert alice_result.exit_code == 0, alice_result.output
|
||||
@@ -513,7 +513,7 @@ def test_buy_resolves_portfolio_by_name_and_user() -> None:
|
||||
|
||||
runner.invoke(
|
||||
runner.app,
|
||||
["portfolio", "create", "MY_PORT", "--asset-id", _TEST_ASSET_ID, "--cash-balance", "1000000", "--user", "alice", "--force"],
|
||||
["portfolio", "create", "MY_PORT", "--asset-id", _TEST_ASSET_ID, "--cash-balance", "1000000", "--user", "alice", "--strategy-id", "set-1", "--force"],
|
||||
)
|
||||
buy_result = runner.invoke(
|
||||
runner.app,
|
||||
@@ -532,7 +532,7 @@ def test_delete_resolves_portfolio_by_name_and_user() -> None:
|
||||
|
||||
runner.invoke(
|
||||
runner.app,
|
||||
["portfolio", "create", "MY_PORT", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--force"],
|
||||
["portfolio", "create", "MY_PORT", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--strategy-id", "set-1", "--force"],
|
||||
)
|
||||
delete_result = runner.invoke(
|
||||
runner.app,
|
||||
@@ -552,7 +552,7 @@ def test_alice_cannot_delete_bobs_portfolio() -> None:
|
||||
|
||||
runner.invoke(
|
||||
runner.app,
|
||||
["portfolio", "create", "SAMSUNG", "--asset-id", _TEST_ASSET_ID, "--user", "bob", "--force"],
|
||||
["portfolio", "create", "SAMSUNG", "--asset-id", _TEST_ASSET_ID, "--user", "bob", "--strategy-id", "set-1", "--force"],
|
||||
)
|
||||
delete_result = runner.invoke(
|
||||
runner.app,
|
||||
@@ -575,7 +575,7 @@ def test_name_validation_accepts_korean() -> None:
|
||||
|
||||
result = runner.invoke(
|
||||
runner.app,
|
||||
["portfolio", "create", "삼성전자 포트폴리오", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--force"],
|
||||
["portfolio", "create", "삼성전자 포트폴리오", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--strategy-id", "set-1", "--force"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
@@ -589,7 +589,7 @@ def test_name_validation_accepts_ascii_unchanged() -> None:
|
||||
|
||||
result = runner.invoke(
|
||||
runner.app,
|
||||
["portfolio", "create", "my-portfolio_v1:test.01", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--force"],
|
||||
["portfolio", "create", "my-portfolio_v1:test.01", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--strategy-id", "set-1", "--force"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
@@ -602,7 +602,7 @@ def test_name_validation_rejects_over_100_chars() -> None:
|
||||
|
||||
long_name = "가" * 101
|
||||
with pytest.raises(Exception):
|
||||
CreatePortfolioRequest(name=long_name, asset_id="some-uuid")
|
||||
CreatePortfolioRequest(name=long_name, asset_id="some-uuid", strategy_id="set-1")
|
||||
|
||||
|
||||
def test_update_new_name_renames_portfolio() -> None:
|
||||
@@ -612,7 +612,7 @@ def test_update_new_name_renames_portfolio() -> None:
|
||||
|
||||
runner.invoke(
|
||||
runner.app,
|
||||
["portfolio", "create", "OLD_NAME", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--force"],
|
||||
["portfolio", "create", "OLD_NAME", "--asset-id", _TEST_ASSET_ID, "--user", "alice", "--strategy-id", "set-1", "--force"],
|
||||
)
|
||||
rename_result = runner.invoke(
|
||||
runner.app,
|
||||
|
||||
Reference in New Issue
Block a user