Files
siftlode/backend/tests/test_channel_details_refresh.py
T
peter 663f30feeb fix(sync): refresh a channel's details once they are a week old
Channel metadata (banner, avatar, description, counts) was fetched only the first time a
channel was seen: the subscription import asked only for channels with no details, and the
channel page only for ones missing a join date. 394 of 397 subscribed channels had July
metadata. Both now also refresh details older than DETAILS_MAX_AGE (7 days), through one
shared staleness rule; channels.list costs 1 unit per 50 channels.
2026-09-26 21:45:46 +02:00

108 lines
3.5 KiB
Python

"""A channel's metadata is fetched again once it is stale, not only the first time it is seen.
Banners, avatars, descriptions and counts change on YouTube, and an old banner URL stops resolving:
a channel that kept the metadata of the day it was first imported showed a dead banner forever. The
subscription import and the channel page both refresh what is older than `DETAILS_MAX_AGE`, and
leave fresh metadata alone (each refresh spends quota). DB lane.
"""
from datetime import datetime, timedelta, timezone
import pytest
from app.models import Channel, Subscription
from app.routes import channels
from app.sync import subscriptions
from app.sync.subscriptions import DETAILS_MAX_AGE, import_subscriptions
NOW = datetime.now(timezone.utc)
FRESH = NOW - timedelta(days=1)
STALE = NOW - DETAILS_MAX_AGE - timedelta(hours=1)
def _item(cid: str) -> dict:
return {
"id": cid,
"snippet": {"title": f"{cid} renamed", "publishedAt": "2010-01-01T00:00:00Z"},
"brandingSettings": {"image": {"bannerExternalUrl": f"https://yt3.example/{cid}-new"}},
}
def _youtube(asked: list[list[str]], subscribed: list[str]):
class _Client:
def __init__(self, db, user):
pass
def __enter__(self):
return self
def __exit__(self, *exc):
return False
def iter_subscriptions(self):
for cid in subscribed:
yield {"channel_id": cid, "title": cid, "yt_subscription_id": f"sub-{cid}"}
def get_channels(self, ids):
asked.append(sorted(ids))
return [_item(cid) for cid in ids]
return _Client
def _channel(db, cid: str, synced: datetime | None) -> None:
db.add(
Channel(
id=cid,
title=cid,
banner_url=f"https://yt3.example/{cid}-old",
published_at=datetime(2010, 1, 1, tzinfo=timezone.utc),
details_synced_at=synced,
)
)
def test_the_import_refreshes_stale_and_missing_details_and_leaves_fresh_ones(
db, make_user, monkeypatch
):
user = make_user()
_channel(db, "UCfresh", FRESH)
_channel(db, "UCstale", STALE)
_channel(db, "UCnever", None)
db.flush()
for cid in ("UCfresh", "UCstale", "UCnever"):
db.add(Subscription(user_id=user.id, channel_id=cid))
db.commit()
asked: list[list[str]] = []
monkeypatch.setattr(
subscriptions, "YouTubeClient", _youtube(asked, ["UCfresh", "UCstale", "UCnever"])
)
result = import_subscriptions(db, user)
assert asked == [["UCnever", "UCstale"]]
assert result["channels_detailed"] == 2
stale = db.get(Channel, "UCstale")
assert stale is not None
assert stale.banner_url == "https://yt3.example/UCstale-new"
assert stale.details_synced_at is not None and stale.details_synced_at > STALE
fresh = db.get(Channel, "UCfresh")
assert fresh is not None and fresh.banner_url == "https://yt3.example/UCfresh-old"
@pytest.mark.parametrize(("synced", "refreshed"), [(STALE, True), (FRESH, False)])
def test_the_channel_page_refreshes_a_stale_channel_only(
db, make_user, monkeypatch, synced, refreshed
):
user = make_user()
_channel(db, "UCpage", synced)
db.commit()
asked: list[list[str]] = []
monkeypatch.setattr(channels, "YouTubeClient", _youtube(asked, []))
body = channels.channel_detail("UCpage", user=user, db=db)
assert asked == ([["UCpage"]] if refreshed else [])
suffix = "new" if refreshed else "old"
assert body["banner_url"] == f"https://yt3.example/UCpage-{suffix}"