"""Feed search + watch-state views (E2 S3), DB-lane. Regression nets for two query rewrites that must stay behaviour-equivalent: - PERF-12: the search channel-name branch moved from an ORed `unaccent(channels.title) ILIKE` (which defeated the search_vector GIN) to a pre-resolved `channel_id IN (...)`. A search must STILL return both videos whose text matches AND videos whose CHANNEL name matches. - PERF-16: the watched/hidden/in_progress views moved from an outer join + `coalesce(state.status,'new')` to an inner join on video_states + the raw column. Each view must return exactly the same set. """ from datetime import datetime, timezone from app.models import Channel, Subscription, Video, VideoState def _dt(day: int) -> datetime: return datetime(2024, 1, day, tzinfo=timezone.utc) def _video(vid: str, ch: str, title: str) -> Video: return Video( id=vid, channel_id=ch, published_at=_dt(1), title=title, is_short=False, live_status="none", via_search=False, via_explore=False, ) def _feed_ids(client, query: str) -> set[str]: resp = client.get(f"/api/feed?{query}&limit=50") assert resp.status_code == 200, resp.text return {it["id"] for it in resp.json()["items"]} def test_search_matches_both_video_text_and_channel_name(db, as_user, make_user): # Two subscribed channels: one whose NAME matches the query (its video's text does not), and one # whose video TEXT matches (its name does not). The pre-resolved channel_id branch must still OR in # the name match, so both videos come back — and an unrelated video on a matching-name-less channel # stays out. user = make_user(email="search@example.com") db.add_all([ Channel(id="UCkube", title="Kubernetes Weekly", subscriber_count=10), Channel(id="UCother", title="Some Other Channel", subscriber_count=10), ]) db.add_all([ Subscription(user_id=user.id, channel_id="UCkube", priority=0, hidden=False), Subscription(user_id=user.id, channel_id="UCother", priority=0, hidden=False), ]) db.add_all([ _video("vidChan", "UCkube", "Totally unrelated content"), # channel NAME matches _video("vidText", "UCother", "kubernetes deep dive"), # video TEXT matches _video("vidNone", "UCother", "cooking pasta at home"), # neither matches ]) db.commit() ids = _feed_ids(as_user(user), "q=kubernetes&show=all") assert ids == {"vidChan", "vidText"} def test_watch_state_views_return_the_right_sets(db, as_user, make_user): user = make_user(email="views@example.com") db.add(Channel(id="UCv", title="Channel V", subscriber_count=10)) db.add(Subscription(user_id=user.id, channel_id="UCv", priority=0, hidden=False)) db.add_all([ _video("vWatch", "UCv", "watched one"), _video("vHide", "UCv", "hidden one"), _video("vProg", "UCv", "in progress one"), _video("vNew", "UCv", "brand new one"), ]) db.flush() db.add_all([ VideoState(user_id=user.id, video_id="vWatch", status="watched"), VideoState(user_id=user.id, video_id="vHide", status="hidden"), # in progress = a stored resume position while still "new" (not watched/hidden) VideoState(user_id=user.id, video_id="vProg", status="new", position_seconds=30), # vNew has NO state row -> "new"/unwatched ]) db.commit() client = as_user(user) assert _feed_ids(client, "show=watched") == {"vWatch"} assert _feed_ids(client, "show=hidden") == {"vHide"} assert _feed_ids(client, "show=in_progress") == {"vProg"} # unwatched excludes watched + hidden, keeps new AND in-progress assert _feed_ids(client, "show=unwatched") == {"vProg", "vNew"} # "all" shows everything except hidden assert _feed_ids(client, "show=all") == {"vWatch", "vProg", "vNew"}