diff --git a/goodnews/queries.py b/goodnews/queries.py index fb76234..cb412b7 100644 --- a/goodnews/queries.py +++ b/goodnews/queries.py @@ -327,6 +327,10 @@ def source_health(conn: sqlite3.Connection) -> list[dict]: # Curation quality: of what this source got ACCEPTED, how much was a # duplicate of content already served (accepted_total − served = accepted dupes). d["accepted_dup_rate"] = round(100 * (accepted - d["served"]) / accepted) if accepted else None + # Match the REAL scheduler gate: due = the later of the streak-backoff time + # and any retry_after_at rest (UTC strings sort chronologically). + due_times = [t for t in (d["next_due_at"], d["retry_after_at"]) if t] + d["next_due_at"] = max(due_times) if due_times else None out.append(d) return out diff --git a/tests/test_retry_after.py b/tests/test_retry_after.py index 957fdd6..593fcc7 100644 --- a/tests/test_retry_after.py +++ b/tests/test_retry_after.py @@ -63,3 +63,14 @@ def test_non_429_failure_still_increments_streak(monkeypatch): res = feeds.poll_source(c, src) assert res["status"] == "failed" assert c.execute("SELECT consecutive_failures FROM sources WHERE id=1").fetchone()[0] == 1 + + +def test_source_health_next_due_uses_later_of_backoff_and_retry_after(): + from goodnews import queries + c = connect(":memory:"); init_db(c); _src(c) + # a recent attempt (streak due ~soon) but a far-future retry_after_at + c.execute("INSERT INTO ingest_runs (source_id, finished_at, status) VALUES (1, datetime('now'), 'rate_limited')") + c.execute("UPDATE sources SET retry_after_at = '2099-01-01 00:00:00' WHERE id = 1") + c.commit() + s = next(x for x in queries.source_health(c) if x["id"] == 1) + assert s["next_due_at"] == "2099-01-01 00:00:00" # agrees with the real gate, not the streak time