"""Feed keyset paging (TEST-2), DB-lane. The feed's cursor paging (feedquery.sort_keys / order_by / keyset_predicate / encode_cursor / decode_cursor) is the NULLS-LAST + tiebreaker algebra where a page boundary bug hides as a duplicated or skipped row — on the busiest endpoint, while the scheduler ingests mid-scroll. It had zero tests. The invariant here is strong and order-agnostic: for EVERY sort, walking the feed in pages of 2 via the cursor must reproduce EXACTLY the order a single unpaged query returns — same rows, same order, no dup, no skip. The seed is built to stress it: duplicate sort values (so the id tiebreaker is load-bearing) and NULLs in published_at / view_count / duration_seconds / title / subscriber_count (so nulls_last is exercised on both a first and a second key). (The malformed-cursor -> 400 path is covered by test_feed_cursor.py.) """ from datetime import datetime, timezone import pytest from app.models import Channel, Subscription, User, Video # Every sort feedquery.sort_keys defines except "relevance" (which needs a search query to rank). _SORTS = [ "newest", "oldest", "views", "views_asc", "duration_desc", "duration_asc", "title", "title_desc", "subscribers", "subscribers_asc", "priority", "priority_asc", "shuffle", ] def _dt(day: int) -> datetime: return datetime(2024, 1, day, tzinfo=timezone.utc) def _seed(db): # Two channels; one has a NULL subscriber_count (exercises the subscribers sort's nulls_last). db.add_all( [ Channel(id="UCa", title="Channel A", subscriber_count=1000), Channel(id="UCb", title="Channel B", subscriber_count=None), ] ) user = User(email="keyset@example.com", email_verified=True) db.add(user) db.flush() db.add_all( [ Subscription(user_id=user.id, channel_id="UCa", priority=5, hidden=False), Subscription(user_id=user.id, channel_id="UCb", priority=0, hidden=False), ] ) # 7 normal/organic videos. Deliberate duplicates (published_at day 1 x2, day 2 x2 and day 3 x2, # view_count 100 x2, duration 60 x2 / 30 x2) and NULLs spread across the still-nullable sort columns # (view_count / duration_seconds / title / subscriber_count) to exercise nulls_last. published_at is # NOT NULL (migration 0068), so no NULL there — the duplicates keep the id tiebreaker load-bearing. rows = [ # id ch published views dur title ("vid00", "UCa", _dt(1), 100, 60, "alpha"), ("vid01", "UCa", _dt(1), 100, 60, "beta"), ("vid02", "UCa", _dt(2), None, None, "gamma"), ("vid03", "UCb", _dt(2), 50, 120, None), ("vid04", "UCb", _dt(3), 200, 30, "delta"), ("vid05", "UCb", _dt(3), None, 30, "epsilon"), ("vid06", "UCa", _dt(28), None, None, "zeta"), ] db.add_all( [ Video( id=vid, channel_id=ch, published_at=pub, view_count=views, duration_seconds=dur, title=title, is_short=False, live_status="none", via_search=False, via_explore=False, ) for vid, ch, pub, views, dur, title in rows ] ) db.commit() return user, [r[0] for r in rows] def _page(client, sort, cursor): url = f"/api/feed?sort={sort}&limit=2" if cursor: url += f"&cursor={cursor}" resp = client.get(url) assert resp.status_code == 200, resp.text return resp.json() def test_feed_surfaces_the_whole_seed(db, as_user): user, ids = _seed(db) body = as_user(user).get("/api/feed?limit=50").json() assert {it["id"] for it in body["items"]} == set(ids) assert body["has_more"] is False and body["next_cursor"] is None @pytest.mark.parametrize("sort", _SORTS) def test_cursor_walk_matches_the_unpaged_order(db, as_user, sort): user, ids = _seed(db) client = as_user(user) # Ground truth: the full order in one query (no cursor). full = [it["id"] for it in client.get(f"/api/feed?sort={sort}&limit=50").json()["items"]] # Guard against a vacuous pass: if seeding ever stops surfacing the videos, `walked == full` would # be [] == [] for every sort and never exercise the cursor. All 7 rows must be present. assert len(full) == len(ids), ( f"{sort}: seed did not surface all rows — the walk would be vacuous" ) # Walk it two at a time via the cursor and reassemble. walked: list[str] = [] cursor = None for _ in range(len(full) + 2): # generous upper bound; the loop breaks on the last page body = _page(client, sort, cursor) walked.extend(it["id"] for it in body["items"]) cursor = body["next_cursor"] if not body["has_more"]: assert cursor is None break assert walked == full, f"{sort}: paged order diverged from the unpaged query" assert len(walked) == len(set(walked)), f"{sort}: cursor paging duplicated a row"