mirror of
https://github.com/Zproger/Aurum.git
synced 2026-10-13 02:56:39 +07:00
fix: график крипты открывать за всё время, лучший/худший рост — по выбранному периоду
График во вкладке «Криптовалюта» теперь по умолчанию показывает всю историю, а не 30 дней — короткое окно у портфеля, растущего годами, часто попадало на локальную просадку и выглядело демотивирующе. Плитки «Лучший рост» и «Худшее падение» раньше всегда считались за 24 часа. Теперь они следуют за диапазоном, выбранным у графика (7д/30д/90д/всё время): для 7д/30д используются закэшированные данные CoinGecko, для «всё время» — метрика за 1 год (дальше бесплатный тариф CoinGecko историю не отдаёт), а для 90д — отдельный запрос по каждой монете, который выполняется только когда пользователь реально выбрал этот период.
This commit is contained in:
@@ -407,6 +407,108 @@ async def test_update_transaction_rejects_editing_a_sell_to_exceed_holdings(clie
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
async def test_refresh_updates_30d_and_1y_change(client: AsyncClient, monkeypatch):
|
||||
monkeypatch.setattr(crypto_service, "_fetch_market_data", _fake_fetch({"bitcoin": _point("50000")}))
|
||||
await _add_bitcoin(client, "0.5", "40000")
|
||||
|
||||
async def counting_fetch(coingecko_ids, vs_currency):
|
||||
return {
|
||||
"bitcoin": crypto_service._MarketPoint(
|
||||
price=Decimal("60000"),
|
||||
change_1h=None,
|
||||
change_24h=None,
|
||||
change_7d=None,
|
||||
change_30d=Decimal("15.5"),
|
||||
change_1y=Decimal("120.25"),
|
||||
)
|
||||
}
|
||||
|
||||
monkeypatch.setattr(crypto_service, "_fetch_market_data", counting_fetch)
|
||||
|
||||
resp = await client.post("/crypto/refresh")
|
||||
|
||||
holding = resp.json()["holdings"][0]
|
||||
assert money(holding["price_change_30d"]) == money("15.5")
|
||||
assert money(holding["price_change_1y"]) == money("120.25")
|
||||
|
||||
|
||||
async def test_90d_performance_reflects_a_real_market_chart_fetch(client: AsyncClient, monkeypatch):
|
||||
"""The 90d Best/Worst Performer window can't ride along with the regular
|
||||
sync (CoinGecko's /coins/markets has no 90d bucket) — it's a separate
|
||||
on-demand fetch, monkeypatched here at the same seam _fetch_market_data
|
||||
is patched at elsewhere in this file."""
|
||||
monkeypatch.setattr(crypto_service, "_fetch_market_data", _fake_fetch({"bitcoin": _point("50000")}))
|
||||
holding = await _add_bitcoin(client)
|
||||
|
||||
async def fake_90d_change(client_, api_key, coingecko_id, vs_currency):
|
||||
return 12.5 if coingecko_id == "bitcoin" else None
|
||||
|
||||
monkeypatch.setattr(crypto_service, "_fetch_90d_change", fake_90d_change)
|
||||
|
||||
resp = await client.get("/crypto/performance/90d")
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["items"] == [{"asset_id": holding["asset_id"], "price_change_percent": 12.5}]
|
||||
|
||||
|
||||
async def test_90d_performance_skips_fully_sold_holdings(client: AsyncClient, monkeypatch):
|
||||
monkeypatch.setattr(crypto_service, "_fetch_market_data", _fake_fetch({"bitcoin": _point("50000")}))
|
||||
holding = await _add_bitcoin(client, "1", "40000")
|
||||
resp = await client.post(
|
||||
f"/crypto/holdings/{holding['asset_id']}/transactions",
|
||||
json={"type": "sell", "quantity": "1", "price_per_unit": "45000", "date": "2026-02-01"},
|
||||
)
|
||||
assert resp.status_code == 201, resp.text
|
||||
|
||||
calls: list[str] = []
|
||||
|
||||
async def fake_90d_change(client_, api_key, coingecko_id, vs_currency):
|
||||
calls.append(coingecko_id)
|
||||
return 5.0
|
||||
|
||||
monkeypatch.setattr(crypto_service, "_fetch_90d_change", fake_90d_change)
|
||||
|
||||
resp = await client.get("/crypto/performance/90d")
|
||||
|
||||
assert resp.json()["items"] == []
|
||||
assert calls == [] # a coin with zero quantity has nothing to fetch a price for
|
||||
|
||||
|
||||
async def test_90d_performance_filters_by_portfolio(client: AsyncClient, monkeypatch):
|
||||
monkeypatch.setattr(crypto_service, "_fetch_market_data", _fake_fetch({"bitcoin": _point("50000")}))
|
||||
portfolio_a = (await client.post("/crypto/portfolios", json={"name": "A"})).json()
|
||||
portfolio_b = (await client.post("/crypto/portfolios", json={"name": "B"})).json()
|
||||
|
||||
async def add(portfolio_id):
|
||||
resp = await client.post(
|
||||
"/crypto/holdings",
|
||||
json={
|
||||
"portfolio_id": portfolio_id,
|
||||
"coingecko_id": "bitcoin",
|
||||
"symbol": "btc",
|
||||
"name": "Bitcoin",
|
||||
"quantity": "1",
|
||||
"price_per_unit": "40000",
|
||||
"date": "2026-01-01",
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 201, resp.text
|
||||
return resp.json()
|
||||
|
||||
await add(portfolio_a["id"])
|
||||
holding_b = await add(portfolio_b["id"])
|
||||
|
||||
async def fake_90d_change(client_, api_key, coingecko_id, vs_currency):
|
||||
return 3.0
|
||||
|
||||
monkeypatch.setattr(crypto_service, "_fetch_90d_change", fake_90d_change)
|
||||
|
||||
resp = await client.get(f"/crypto/performance/90d?portfolio_id={portfolio_b['id']}")
|
||||
|
||||
items = resp.json()["items"]
|
||||
assert [item["asset_id"] for item in items] == [holding_b["asset_id"]]
|
||||
|
||||
|
||||
async def test_update_transaction_404_for_unknown_id(client: AsyncClient):
|
||||
resp = await client.patch("/crypto/transactions/999999", json={"quantity": "1"})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user