feat(dashboards): scheduler health strip, failing-source rollup, 24h activity sparkline, credential staleness nudge
D1 scheduler visibility: AppSetting last-tick stamp on every Beat tick + GET /api/sources/schedule-status (last_tick_at/next_due_at/due_now/auto_sources) + SchedulerStatusBar on the Subscriptions tab (re-polled every 30s). D2 failing-source rollup: ?failing=true on the sources list + FailingSourcesCard on Downloads with per-source and bulk "retry" (re-runs the feed via /check). D3 activity sparkline: GET /api/downloads/activity hourly buckets + CSS bar chart by the stat chips (failures stacked in error color); refreshes on live poll. D4 credential staleness: surface last_verified age + "re-verify recommended" warning past 30d; also fixes the dead last_verified_at field-name mismatch so the verification row renders at all. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from backend.app import create_app
|
||||
from backend.app.models import Artist
|
||||
from backend.app.models import Artist, Source
|
||||
|
||||
pytestmark = pytest.mark.integration
|
||||
|
||||
@@ -25,6 +25,37 @@ async def artist(db):
|
||||
return a
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_failing_filter_returns_only_failing(client, artist, db):
|
||||
healthy = Source(
|
||||
artist_id=artist.id, platform="patreon", url="https://p/ok",
|
||||
enabled=True, consecutive_failures=0,
|
||||
)
|
||||
broken = Source(
|
||||
artist_id=artist.id, platform="pixiv", url="https://p/bad",
|
||||
enabled=True, consecutive_failures=3, last_error="boom",
|
||||
)
|
||||
db.add_all([healthy, broken])
|
||||
await db.commit()
|
||||
|
||||
resp = await client.get("/api/sources?failing=true")
|
||||
assert resp.status_code == 200
|
||||
body = await resp.get_json()
|
||||
assert len(body) == 1
|
||||
assert body[0]["consecutive_failures"] == 3
|
||||
assert body[0]["last_error"] == "boom"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_schedule_status_shape(client):
|
||||
resp = await client.get("/api/sources/schedule-status")
|
||||
assert resp.status_code == 200
|
||||
body = await resp.get_json()
|
||||
assert set(body) == {"last_tick_at", "next_due_at", "due_now", "auto_sources"}
|
||||
assert isinstance(body["due_now"], int)
|
||||
assert isinstance(body["auto_sources"], int)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_list_get_delete(client, artist):
|
||||
create = await client.post("/api/sources", json={
|
||||
|
||||
Reference in New Issue
Block a user