CRUD портфелей (/portfolios), ручные события (POST /events, DELETE только для manual), PATCH /instruments/{id}, GET /analytics/overview — карточка на каждый скоуп одним запросом. Холдинги отдают logo_url и logo_color. openapi.json обновлён.
114 lines
4.3 KiB
Python
114 lines
4.3 KiB
Python
"""Portfolio CRUD and the account set over HTTP."""
|
||
|
||
from factories import make_account
|
||
|
||
URL = "/api/v1/portfolios"
|
||
|
||
|
||
async def test_create_list_and_replace_accounts(client, auth_headers):
|
||
a = await make_account(name="Брокер 1")
|
||
b = await make_account(name="Брокер 2")
|
||
|
||
r = await client.post(
|
||
URL, json={"name": " Основной ", "account_ids": [b, a]}, headers=auth_headers
|
||
)
|
||
assert r.status_code == 201, r.text
|
||
created = r.json()
|
||
assert created["name"] == "Основной"
|
||
assert created["base_currency"] == "RUB"
|
||
assert created["account_ids"] == sorted([a, b])
|
||
|
||
r = await client.get(URL, headers=auth_headers)
|
||
assert [p["id"] for p in r.json()] == [created["id"]]
|
||
|
||
r = await client.put(
|
||
f"{URL}/{created['id']}/accounts", json={"account_ids": [b]}, headers=auth_headers
|
||
)
|
||
assert r.status_code == 200, r.text
|
||
assert r.json()["account_ids"] == [b]
|
||
|
||
r = await client.put(
|
||
f"{URL}/{created['id']}/accounts", json={"account_ids": []}, headers=auth_headers
|
||
)
|
||
assert r.json()["account_ids"] == []
|
||
|
||
|
||
async def test_create_without_accounts_is_allowed(client, auth_headers):
|
||
r = await client.post(URL, json={"name": "Пустой"}, headers=auth_headers)
|
||
assert r.status_code == 201
|
||
assert r.json()["account_ids"] == []
|
||
|
||
|
||
async def test_duplicate_name_is_a_conflict(client, auth_headers):
|
||
await client.post(URL, json={"name": "Основной"}, headers=auth_headers)
|
||
r = await client.post(URL, json={"name": "Основной"}, headers=auth_headers)
|
||
assert r.status_code == 409
|
||
assert r.headers["content-type"].startswith("application/problem+json")
|
||
|
||
|
||
async def test_rename_and_rename_conflict(client, auth_headers):
|
||
first = (await client.post(URL, json={"name": "Один"}, headers=auth_headers)).json()
|
||
await client.post(URL, json={"name": "Два"}, headers=auth_headers)
|
||
|
||
r = await client.patch(
|
||
f"{URL}/{first['id']}", json={"name": "Один и один"}, headers=auth_headers
|
||
)
|
||
assert r.status_code == 200
|
||
assert r.json()["name"] == "Один и один"
|
||
|
||
r = await client.patch(f"{URL}/{first['id']}", json={"name": "Два"}, headers=auth_headers)
|
||
assert r.status_code == 409
|
||
|
||
r = await client.patch(
|
||
f"{URL}/{first['id']}", json={"name": "Один и один"}, headers=auth_headers
|
||
)
|
||
assert r.status_code == 200 # keeping its own name is not a conflict
|
||
|
||
|
||
async def test_blank_name_is_rejected(client, auth_headers):
|
||
r = await client.post(URL, json={"name": " "}, headers=auth_headers)
|
||
assert r.status_code == 400
|
||
|
||
|
||
async def test_unknown_account_is_rejected_and_nothing_is_saved(client, auth_headers):
|
||
real = await make_account(name="Брокер")
|
||
r = await client.post(
|
||
URL, json={"name": "Х", "account_ids": [real, 99999]}, headers=auth_headers
|
||
)
|
||
assert r.status_code == 400
|
||
assert "99999" in r.json()["detail"]
|
||
assert (await client.get(URL, headers=auth_headers)).json() == []
|
||
|
||
created = (
|
||
await client.post(URL, json={"name": "Х", "account_ids": [real]}, headers=auth_headers)
|
||
).json()
|
||
r = await client.put(
|
||
f"{URL}/{created['id']}/accounts", json={"account_ids": [99999]}, headers=auth_headers
|
||
)
|
||
assert r.status_code == 400
|
||
listed = (await client.get(URL, headers=auth_headers)).json()
|
||
assert listed[0]["account_ids"] == [real]
|
||
|
||
|
||
async def test_delete_removes_the_portfolio_but_not_its_accounts(client, auth_headers):
|
||
a = await make_account(name="Брокер")
|
||
created = (
|
||
await client.post(URL, json={"name": "Х", "account_ids": [a]}, headers=auth_headers)
|
||
).json()
|
||
|
||
r = await client.delete(f"{URL}/{created['id']}", headers=auth_headers)
|
||
assert r.status_code == 204
|
||
assert (await client.get(URL, headers=auth_headers)).json() == []
|
||
accounts = (await client.get("/api/v1/accounts", headers=auth_headers)).json()
|
||
assert [x["id"] for x in accounts] == [a]
|
||
|
||
|
||
async def test_missing_portfolio_is_404(client, auth_headers):
|
||
assert (
|
||
await client.patch(f"{URL}/999", json={"name": "Х"}, headers=auth_headers)
|
||
).status_code == 404
|
||
assert (
|
||
await client.put(f"{URL}/999/accounts", json={"account_ids": []}, headers=auth_headers)
|
||
).status_code == 404
|
||
assert (await client.delete(f"{URL}/999", headers=auth_headers)).status_code == 404
|