104 lines
4.1 KiB
Python
104 lines
4.1 KiB
Python
|
|
"""Submitted PnL backfill covers the local library and preserves existing caches."""
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from sqlalchemy import func, select
|
||
|
|
|
||
|
|
from app.alphas import upsert_alpha
|
||
|
|
from app.models import Pnl
|
||
|
|
from app.worldquant import WqError
|
||
|
|
from tests.conftest import alpha
|
||
|
|
from tests.test_jobs import ready_runner, result
|
||
|
|
|
||
|
|
URL = "/api/v1/sync-jobs"
|
||
|
|
|
||
|
|
|
||
|
|
async def start(client):
|
||
|
|
response = await client.post(URL, json={"kind": "pnl_backfill"})
|
||
|
|
assert response.status_code == 202
|
||
|
|
return response.json()
|
||
|
|
|
||
|
|
|
||
|
|
async def test_backfill_all_submitted_beyond_page_limit_and_deduplicates(app, logged_in):
|
||
|
|
runner = await ready_runner(app)
|
||
|
|
async with runner.sessions() as db:
|
||
|
|
for i in range(105):
|
||
|
|
await upsert_alpha(db, alpha(
|
||
|
|
f"ref{i:03}", status="DECOMMISSIONED" if i % 2 else "ACTIVE",
|
||
|
|
hidden=bool(i % 2), settings={"region": "EUR" if i % 2 else "USA"},
|
||
|
|
))
|
||
|
|
for raw in [alpha("pending"), alpha("unknown", status=None), alpha("cached", status="ACTIVE")]:
|
||
|
|
await upsert_alpha(db, raw)
|
||
|
|
db.add(Pnl(alpha_id="cached", raw={"original": True}, points=[]))
|
||
|
|
await db.commit()
|
||
|
|
first = await start(logged_in)
|
||
|
|
assert first["total"] == 105
|
||
|
|
assert first["payload"]["alpha_ids"] == [f"ref{i:03}" for i in range(105)]
|
||
|
|
# Another task can populate an item after this snapshot was fixed.
|
||
|
|
async with runner.sessions() as db:
|
||
|
|
db.add(Pnl(alpha_id="ref000", raw={"original": True}, points=[]))
|
||
|
|
await db.commit()
|
||
|
|
assert (await start(logged_in))["id"] == first["id"]
|
||
|
|
calls = []
|
||
|
|
original_pnl = runner.client.pnl
|
||
|
|
|
||
|
|
async def pnl(alpha_id):
|
||
|
|
calls.append(alpha_id)
|
||
|
|
return await original_pnl(alpha_id)
|
||
|
|
|
||
|
|
runner.client.pnl = pnl
|
||
|
|
await runner.execute(first["id"])
|
||
|
|
finished = await result(runner, first["id"])
|
||
|
|
assert (finished.status, finished.processed, finished.failed) == ("completed", 105, 0)
|
||
|
|
assert calls == [f"ref{i:03}" for i in range(1, 105)]
|
||
|
|
async with runner.sessions() as db:
|
||
|
|
assert await db.scalar(select(func.count()).select_from(Pnl)) == 106
|
||
|
|
assert (await db.get(Pnl, "cached")).raw == {"original": True}
|
||
|
|
assert (await db.get(Pnl, "ref000")).raw == {"original": True}
|
||
|
|
empty = await start(logged_in)
|
||
|
|
assert empty["status"] == "completed" and empty["total"] == 0
|
||
|
|
assert empty["payload"]["alpha_ids"] == []
|
||
|
|
|
||
|
|
|
||
|
|
async def test_backfill_keeps_progress_and_retries_only_unfinished_items(app, logged_in):
|
||
|
|
runner = await ready_runner(app)
|
||
|
|
async with runner.sessions() as db:
|
||
|
|
for name in ("a", "b", "c"):
|
||
|
|
await upsert_alpha(db, alpha(name, status="ACTIVE"))
|
||
|
|
await db.commit()
|
||
|
|
calls = []
|
||
|
|
blocked = True
|
||
|
|
original_pnl = runner.client.pnl
|
||
|
|
|
||
|
|
async def pnl(alpha_id):
|
||
|
|
calls.append(alpha_id)
|
||
|
|
if blocked and alpha_id == "b":
|
||
|
|
raise WqError("无权访问", "access_denied")
|
||
|
|
if blocked and alpha_id == "c":
|
||
|
|
raise WqError("平台数据仍在准备,请稍后重试", "pending")
|
||
|
|
return await original_pnl(alpha_id)
|
||
|
|
|
||
|
|
runner.client.pnl = pnl
|
||
|
|
job = await start(logged_in)
|
||
|
|
await runner.execute(job["id"])
|
||
|
|
failed = await result(runner, job["id"])
|
||
|
|
assert (failed.status, failed.processed, failed.failed) == ("failed", 1, 1)
|
||
|
|
assert failed.checkpoint["alpha_id"] == "c"
|
||
|
|
async with runner.sessions() as db:
|
||
|
|
assert await db.get(Pnl, "a") is not None
|
||
|
|
assert await db.get(Pnl, "b") is None
|
||
|
|
assert await db.get(Pnl, "c") is None
|
||
|
|
blocked = False
|
||
|
|
assert (await logged_in.post(f"{URL}/{job['id']}/retry")).status_code == 200
|
||
|
|
await runner.execute(job["id"])
|
||
|
|
finished = await result(runner, job["id"])
|
||
|
|
assert (finished.status, finished.processed, finished.failed) == ("completed", 3, 0)
|
||
|
|
assert calls == ["a", "b", "c", "b", "c"]
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize("extra", [
|
||
|
|
{"alpha_ids": ["a"]}, {"submission": "UNSUBMITTED"}, {"date_from": "2025-01-01"},
|
||
|
|
])
|
||
|
|
async def test_backfill_rejects_client_scope(logged_in, extra):
|
||
|
|
response = await logged_in.post(URL, json={"kind": "pnl_backfill", **extra})
|
||
|
|
assert response.status_code == 422
|