fix(sync): a failed feed-fallback read waits its window out, and spent quota ends the round
A channel whose uploads playlist also failed was never stamped, so a terminated channel was charged again on every 20-minute poll instead of once per rss_fallback_hours. An upstream quotaExceeded now stops the round instead of charging every remaining channel the same refusal.
This commit is contained in:
@@ -8,12 +8,16 @@ import pytest
|
||||
from app import sysconfig
|
||||
from app.models import Channel, Video
|
||||
from app.sync import runner
|
||||
from app.youtube.client import YouTubeError
|
||||
|
||||
NOW = datetime.now(timezone.utc)
|
||||
_PUB = datetime(2020, 1, 1, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
class _FakeYt:
|
||||
# playlist id -> the reason its read fails with (the tests put entries here)
|
||||
failing: dict[str, str] = {}
|
||||
|
||||
def __init__(self, calls):
|
||||
self.calls = calls
|
||||
|
||||
@@ -25,6 +29,8 @@ class _FakeYt:
|
||||
|
||||
def get_playlist_items(self, playlist_id, page_token=None):
|
||||
self.calls.append(playlist_id)
|
||||
if playlist_id in self.failing:
|
||||
raise YouTubeError("refused", reason=self.failing[playlist_id])
|
||||
cid = "UC" + playlist_id[2:]
|
||||
return {
|
||||
"items": [
|
||||
@@ -42,6 +48,7 @@ def calls(monkeypatch):
|
||||
monkeypatch.setattr(runner, "get_service_user", lambda db: object())
|
||||
monkeypatch.setattr(runner, "YouTubeClient", lambda db, user: _FakeYt(made))
|
||||
monkeypatch.setattr(runner.quota, "backfill_reserve_reached", lambda: False)
|
||||
monkeypatch.setattr(_FakeYt, "failing", {})
|
||||
return made
|
||||
|
||||
|
||||
@@ -113,3 +120,24 @@ def test_it_yields_to_the_backfill_reserve(db, calls, monkeypatch):
|
||||
|
||||
assert runner.run_feed_fallback(db, ["UCdark"])["read"] == 0
|
||||
assert calls == []
|
||||
|
||||
|
||||
def test_a_read_that_fails_waits_the_window_out_too(db, calls):
|
||||
# A terminated channel: its feed 404s and so does its uploads playlist, for good. Without a
|
||||
# stamp it was charged on every 20-minute poll instead of once per window.
|
||||
_channel(db, "UCgone", rss=NOW - timedelta(hours=3))
|
||||
_FakeYt.failing["UUgone"] = "playlistNotFound"
|
||||
|
||||
assert runner.run_feed_fallback(db, ["UCgone"]) == {"read": 0, "new": 0, "failed": 1}
|
||||
assert db.get(Channel, "UCgone").feed_fallback_at is not None
|
||||
assert runner.run_feed_fallback(db, ["UCgone"])["failed"] == 0
|
||||
assert calls == ["UUgone"]
|
||||
|
||||
|
||||
def test_out_of_quota_upstream_stops_the_round(db, calls):
|
||||
_channel(db, "UCa")
|
||||
_channel(db, "UCb")
|
||||
_FakeYt.failing["UUa"] = "quotaExceeded"
|
||||
|
||||
assert runner.run_feed_fallback(db, ["UCa", "UCb"])["failed"] == 1
|
||||
assert calls == ["UUa"]
|
||||
|
||||
Reference in New Issue
Block a user