feat(api): портфели, ручные события, правка инструментов и обзор по скоупам
CRUD портфелей (/portfolios), ручные события (POST /events, DELETE только для manual), PATCH /instruments/{id}, GET /analytics/overview — карточка на каждый скоуп одним запросом. Холдинги отдают logo_url и logo_color. openapi.json обновлён.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
"""One card per scope for the home screen."""
|
||||
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
from factories import make_account, make_event, make_instrument, make_price, refresh
|
||||
from fintracker.analytics import today_local
|
||||
from fintracker.models import AccountKind, AccountRole, EventKind
|
||||
|
||||
URL = "/api/v1/analytics/overview"
|
||||
|
||||
|
||||
async def _broker(name: str) -> int:
|
||||
return await make_account(
|
||||
name=name, kind=AccountKind.broker, role=AccountRole.investment, balance=None
|
||||
)
|
||||
|
||||
|
||||
async def _invest(account: int, *, today_price: str) -> None:
|
||||
"""10 000 ₽ in, 100 shares at 100; the price is flat until today, when it is `today_price`."""
|
||||
t = today_local()
|
||||
bought = t - timedelta(days=5)
|
||||
sber = await make_instrument(ticker=f"T{account}")
|
||||
await make_event(bought, account_id=account, kind=EventKind.deposit, amount=10000)
|
||||
await make_event(
|
||||
bought, account_id=account, kind=EventKind.buy, instrument_id=sber,
|
||||
quantity=100, price=100, amount=-10000,
|
||||
) # fmt: skip
|
||||
d = bought
|
||||
while d < t:
|
||||
await make_price(d, instrument_id=sber, close="100")
|
||||
d += timedelta(days=1)
|
||||
await make_price(t, instrument_id=sber, close=today_price)
|
||||
|
||||
|
||||
async def test_cards_carry_value_result_and_the_change_over_the_last_day(client, auth_headers):
|
||||
account = await _broker("Брокер")
|
||||
await _invest(account, today_price="105")
|
||||
await refresh()
|
||||
|
||||
r = await client.get(URL, headers=auth_headers)
|
||||
assert r.status_code == 200, r.text
|
||||
cards = {c["scope"]: c for c in r.json()}
|
||||
card = cards[f"account:{account}"]
|
||||
assert card["name"] == "Брокер"
|
||||
assert card["kind"] == "account"
|
||||
assert Decimal(card["total_rub"]) == 10500
|
||||
assert Decimal(card["invested_rub"]) == 10000
|
||||
assert Decimal(card["pnl_rub"]) == 500
|
||||
assert Decimal(card["pnl_pct"]) == Decimal("0.05")
|
||||
assert Decimal(card["day_change_rub"]) == 500
|
||||
assert Decimal(card["day_change_pct"]) == Decimal("500") / Decimal("10000")
|
||||
assert Decimal(card["income_year_rub"]) == 0
|
||||
|
||||
|
||||
async def test_all_comes_first_then_portfolios_then_accounts_by_value(client, auth_headers):
|
||||
small, big = await _broker("Малый"), await _broker("Большой")
|
||||
await _invest(small, today_price="100")
|
||||
await _invest(big, today_price="130")
|
||||
created = await client.post(
|
||||
"/api/v1/portfolios", json={"name": "Мой", "account_ids": [small]}, headers=auth_headers
|
||||
)
|
||||
assert created.status_code == 201
|
||||
await refresh()
|
||||
|
||||
cards = (await client.get(URL, headers=auth_headers)).json()
|
||||
assert [c["kind"] for c in cards] == ["all", "portfolio", "account", "account"]
|
||||
assert [c["name"] for c in cards[2:]] == ["Большой", "Малый"]
|
||||
assert Decimal(cards[0]["total_rub"]) == Decimal(cards[2]["total_rub"]) + Decimal(
|
||||
cards[3]["total_rub"]
|
||||
)
|
||||
|
||||
|
||||
async def test_an_account_switched_off_has_no_card(client, auth_headers):
|
||||
kept, off = await _broker("Оставить"), await _broker("Отключить")
|
||||
await _invest(kept, today_price="100")
|
||||
await _invest(off, today_price="100")
|
||||
await client.patch(f"/api/v1/accounts/{off}", json={"disabled": True}, headers=auth_headers)
|
||||
await refresh()
|
||||
|
||||
names = [c["name"] for c in (await client.get(URL, headers=auth_headers)).json()]
|
||||
assert "Оставить" in names
|
||||
assert "Отключить" not in names
|
||||
|
||||
|
||||
async def test_nothing_built_yet_is_an_empty_list(client, auth_headers):
|
||||
assert (await client.get(URL, headers=auth_headers)).json() == []
|
||||
Reference in New Issue
Block a user