feat: REQ-028 add display_name to GET /v1/portfolios response
- Portfolio domain model + PortfolioItem schema에 display_name 필드 추가 - _SELECT_PORTFOLIO에 a.display_name JOIN 보강, _portfolio_to_item 매퍼 전달 - REQ-028 CHG-20260601-01/02: Implemented → Approved → Implemented - AC-13 신설 (display_name == joined assets.display_name) - BAT-009 verification.md: AC-13 매핑 + augmentation history 누적 - REL-001 verification checklist 재실행 evidence 갱신 (368 PASSED, 로컬 smoke 5건 통과) - tests/test_v1.py: dead 중복 GET /v1/assets 섹션 제거 (두 번째 동명 정의가 첫 번째를 override하던 잔재) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -209,135 +209,6 @@ def test_auth_issue_service_token_generates_distinct_runtime_ids_per_token(monke
|
||||
assert first.json()["runtime_id"] != second.json()["runtime_id"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /v1/assets
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _make_portfolio(portfolio_id: str = "p1") -> Portfolio:
|
||||
return Portfolio(
|
||||
portfolio_id=portfolio_id,
|
||||
broker="kis",
|
||||
status="enabled",
|
||||
name="Test",
|
||||
symbol="005930",
|
||||
tags=["tag1"],
|
||||
cash_balance=1000.0,
|
||||
asset_quantity=10.0,
|
||||
price=50000.0,
|
||||
profit_buffer=0.0,
|
||||
active_strategy_id=None,
|
||||
avabile_strategies=None,
|
||||
owner_user_id="user-1",
|
||||
managed_by_daemon_id="d-1",
|
||||
)
|
||||
|
||||
|
||||
def test_v1_assets_requires_service_token(monkeypatch) -> None:
|
||||
monkeypatch.setenv("PORTFOLIOHUB_DB_ENABLED", "false")
|
||||
settings = _make_settings(db_enabled=False)
|
||||
client = _make_app(settings)
|
||||
resp = client.get("/v1/assets")
|
||||
assert resp.status_code == 401
|
||||
|
||||
|
||||
def test_v1_assets_rejects_env_token() -> None:
|
||||
from app.core.config import get_settings
|
||||
settings = _make_settings(db_enabled=False, read_tokens_raw="env-token")
|
||||
app = create_app(settings_factory=lambda: settings)
|
||||
app.dependency_overrides[get_settings] = lambda: settings
|
||||
client = TestClient(app)
|
||||
resp = client.get("/v1/assets", headers=_service_token_header("env-token"))
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
def test_v1_assets_returns_asset_list(monkeypatch) -> None:
|
||||
settings = _make_app_db(monkeypatch)
|
||||
|
||||
portfolio = _make_portfolio()
|
||||
principal = TokenPrincipal(
|
||||
token_id="t1", user_id="user-1", permission="read",
|
||||
token_type="service", runtime_id="rt-1", daemon_id="d-1"
|
||||
)
|
||||
|
||||
mock_repo = MagicMock()
|
||||
mock_repo.get_token_principal.return_value = principal
|
||||
mock_repo.list_portfolios_with_strategies.return_value = [portfolio]
|
||||
mock_repo.get_strategy_set.return_value = None
|
||||
|
||||
with patch("app.core.auth.get_portfolio_repository_from_settings", return_value=mock_repo), \
|
||||
patch("app.api.v1_assets.get_portfolio_repository_from_settings", return_value=mock_repo):
|
||||
client = _make_app(_make_settings(credential_encryption_key=_TEST_ENC_KEY))
|
||||
resp = client.get("/v1/assets", headers=_service_token_header())
|
||||
|
||||
assert resp.status_code == 200
|
||||
body = resp.json()
|
||||
assert body["total"] == 1
|
||||
item = body["assets"][0]
|
||||
assert item["asset_id"] == "p1"
|
||||
assert "market" not in item
|
||||
assert item["broker_type"] == "kis"
|
||||
assert item["cash"] == 1000.0
|
||||
assert item["quantity"] == 10.0
|
||||
assert item["strategy_params"] == {}
|
||||
assert item["strategy_type"] is None
|
||||
mock_repo.list_portfolios_with_strategies.assert_called_once_with(
|
||||
owner_user_id="user-1",
|
||||
include_disabled=False,
|
||||
managed_by_daemon_id="d-1",
|
||||
market=None,
|
||||
)
|
||||
|
||||
|
||||
def test_v1_assets_status_filter(monkeypatch) -> None:
|
||||
settings = _make_app_db(monkeypatch)
|
||||
principal = TokenPrincipal(
|
||||
token_id="t1", user_id="user-1", permission="read",
|
||||
token_type="service", runtime_id="rt-1", daemon_id="d-1"
|
||||
)
|
||||
mock_repo = MagicMock()
|
||||
mock_repo.get_token_principal.return_value = principal
|
||||
|
||||
with patch("app.core.auth.get_portfolio_repository_from_settings", return_value=mock_repo):
|
||||
client = _make_app(_make_settings(credential_encryption_key=_TEST_ENC_KEY))
|
||||
resp = client.get("/v1/assets?status=active", headers=_service_token_header())
|
||||
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
def test_v1_assets_strategy_params(monkeypatch) -> None:
|
||||
"""GET /v1/assets is admin catalog only — service token is rejected (strategy data is on GET /v1/portfolios)."""
|
||||
settings = _make_app_db(monkeypatch)
|
||||
principal = TokenPrincipal(
|
||||
token_id="t1", user_id="user-1", permission="read",
|
||||
token_type="service", runtime_id="rt-1", daemon_id="d-1"
|
||||
)
|
||||
mock_repo = MagicMock()
|
||||
mock_repo.get_token_principal.return_value = principal
|
||||
|
||||
with patch("app.core.auth.get_portfolio_repository_from_settings", return_value=mock_repo):
|
||||
client = _make_app(_make_settings(credential_encryption_key=_TEST_ENC_KEY))
|
||||
resp = client.get("/v1/assets", headers=_service_token_header())
|
||||
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
def test_v1_assets_strategy_params_fallback_on_error(monkeypatch) -> None:
|
||||
"""GET /v1/assets is admin catalog only — service token is rejected."""
|
||||
settings = _make_app_db(monkeypatch)
|
||||
principal = TokenPrincipal(
|
||||
token_id="t1", user_id="user-1", permission="read",
|
||||
token_type="service", runtime_id="rt-1", daemon_id="d-1"
|
||||
)
|
||||
mock_repo = MagicMock()
|
||||
mock_repo.get_token_principal.return_value = principal
|
||||
|
||||
with patch("app.core.auth.get_portfolio_repository_from_settings", return_value=mock_repo):
|
||||
client = _make_app(_make_settings(credential_encryption_key=_TEST_ENC_KEY))
|
||||
resp = client.get("/v1/assets", headers=_service_token_header())
|
||||
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /v1/user_info
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user