Files
fin-tracker/backend/tests/api/test_instruments_api.py
Dmitry f1f336f94f feat(api): портфели, ручные события, правка инструментов и обзор по скоупам
CRUD портфелей (/portfolios), ручные события (POST /events, DELETE только для manual), PATCH /instruments/{id}, GET /analytics/overview — карточка на каждый скоуп одним запросом. Холдинги отдают logo_url и logo_color. openapi.json обновлён.
2026-09-19 22:13:55 +03:00

110 lines
4.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Hand corrections to an instrument over HTTP."""
from factories import make_instrument
def url(instrument_id: int) -> str:
return f"/api/v1/instruments/{instrument_id}"
async def test_patch_lot_board_name_class_and_sector(client, auth_headers):
iid = await make_instrument(ticker="MSNG", board="TQBR")
r = await client.patch(
url(iid),
json={
"lot": 1000,
"name": " Мосэнерго ",
"asset_class": "bond",
"sector": "Энергетика",
"board": "TQTF",
},
headers=auth_headers,
)
assert r.status_code == 200, r.text
row = r.json()
assert (row["lot"], row["name"], row["asset_class"]) == (1000, "Мосэнерго", "bond")
assert (row["sector"], row["board"]) == ("Энергетика", "TQTF")
detail = (await client.get(url(iid), headers=auth_headers)).json()["instrument"]
assert detail["lot"] == 1000
async def test_unset_fields_are_left_alone_and_null_clears_board(client, auth_headers):
iid = await make_instrument(ticker="AFLT", board="TQBR", name="Аэрофлот")
r = await client.patch(url(iid), json={"lot": 10}, headers=auth_headers)
assert (r.json()["name"], r.json()["board"], r.json()["lot"]) == ("Аэрофлот", "TQBR", 10)
r = await client.patch(url(iid), json={"board": None}, headers=auth_headers)
assert r.status_code == 200
assert r.json()["board"] is None
assert r.json()["lot"] == 10
async def test_invalid_values_are_refused(client, auth_headers):
iid = await make_instrument(ticker="PHOR")
for body in ({"lot": 0}, {"lot": -5}, {"name": ""}, {"unknown_field": 1}):
r = await client.patch(url(iid), json=body, headers=auth_headers)
assert r.status_code == 422, body
for body in ({"lot": None}, {"name": None}, {"asset_class": None}, {"name": " "}):
r = await client.patch(url(iid), json=body, headers=auth_headers)
assert r.status_code == 400, body
r = await client.patch(url(iid), json={"asset_class": "stock"}, headers=auth_headers)
assert r.status_code == 422
assert "share" in r.json()["detail"]
async def test_moving_to_a_taken_ticker_and_board_is_a_conflict(client, auth_headers):
await make_instrument(ticker="SBER", board="TQBR")
other = await make_instrument(ticker="SBER", board="SMAL")
r = await client.patch(url(other), json={"board": "TQBR"}, headers=auth_headers)
assert r.status_code == 409
assert r.headers["content-type"].startswith("application/problem+json")
r = await client.patch(url(other), json={"board": "TQBR", "lot": 10}, headers=auth_headers)
assert r.status_code == 409 # nothing was half-applied
assert (await client.get(url(other), headers=auth_headers)).json()["instrument"]["lot"] == 1
async def test_missing_instrument_is_404(client, auth_headers):
r = await client.patch(url(999), json={"lot": 2}, headers=auth_headers)
assert r.status_code == 404
async def test_instrument_and_holding_expose_a_public_logo_url(client, auth_headers):
from datetime import date
from factories import make_account, make_event, refresh
from fintracker.db import get_sessionmaker
from fintracker.models import AccountKind, AccountRole, EventKind, Instrument
iid = await make_instrument(ticker="SBER")
async with get_sessionmaker()() as session:
row = await session.get(Instrument, iid)
assert row is not None
row.logo_name, row.logo_color = "sber.png", "#21A038"
await session.commit()
account = await make_account(
name="Т", kind=AccountKind.broker, role=AccountRole.investment, balance=None
)
await make_event(
date(2026, 3, 1), account_id=account, kind=EventKind.buy, instrument_id=iid,
quantity=1, price=100, amount=-100,
) # fmt: skip
await refresh()
card = (await client.get(url(iid), headers=auth_headers)).json()
assert card["instrument"]["logo_url"] == "https://invest-brands.cdn-tinkoff.ru/sberx160.png"
assert card["instrument"]["logo_color"] == "#21A038"
assert card["holding"]["logo_url"] == card["instrument"]["logo_url"]
plain = await make_instrument(ticker="NOLOGO")
assert (await client.get(url(plain), headers=auth_headers)).json()["instrument"][
"logo_url"
] is None