"""ask 4: saved views are per-module (`source`). Feed and Plex views are separate lists, may share a name, and each has its own single default. DB lane (`db` fixture) — skipped without Postgres. """ from fastapi.testclient import TestClient import app.state as state from app.auth import current_user from app.db import get_db from app.main import app from app.models import User def _client(db, user, monkeypatch, override_dep): monkeypatch.setattr( state, "_configured_cache", True ) # the request gate 503s an unconfigured app override_dep(get_db, db.info["request"]) override_dep(current_user, lambda: user) # require_human resolves through this return TestClient(app) def _user(db): u = User(email="views@example.com") db.add(u) db.commit() return u def test_views_are_scoped_by_source(db, monkeypatch, override_dep): user = _user(db) c = _client(db, user, monkeypatch, override_dep) # Same NAME under two sources is allowed (unique is per user+source now). f = c.post("/api/saved-views", json={"name": "Fresh", "filters": {"a": 1}, "source": "feed"}) p = c.post("/api/saved-views", json={"name": "Fresh", "filters": {"b": 2}, "source": "plex"}) assert f.status_code == 200 and p.status_code == 200, (f.text, p.text) feed = c.get("/api/saved-views?source=feed").json() plex = c.get("/api/saved-views?source=plex").json() assert [v["name"] for v in feed] == ["Fresh"] assert [v["name"] for v in plex] == ["Fresh"] assert feed[0]["filters"] == {"a": 1} and plex[0]["filters"] == {"b": 2} # A source-less GET keeps the old feed-only behaviour. assert [v["id"] for v in c.get("/api/saved-views").json()] == [v["id"] for v in feed] def test_default_is_per_source(db, monkeypatch, override_dep): user = _user(db) c = _client(db, user, monkeypatch, override_dep) fid = c.post("/api/saved-views", json={"name": "F", "filters": {}, "source": "feed"}).json()[ "id" ] pid = c.post("/api/saved-views", json={"name": "P", "filters": {}, "source": "plex"}).json()[ "id" ] assert c.patch(f"/api/saved-views/{fid}", json={"is_default": True}).status_code == 200 assert c.patch(f"/api/saved-views/{pid}", json={"is_default": True}).status_code == 200 # Making the Plex view default must NOT clear the feed view's default (different source). assert c.get("/api/saved-views?source=feed").json()[0]["is_default"] is True assert c.get("/api/saved-views?source=plex").json()[0]["is_default"] is True def test_duplicate_name_within_a_source_conflicts(db, monkeypatch, override_dep): user = _user(db) c = _client(db, user, monkeypatch, override_dep) a = c.post("/api/saved-views", json={"name": "Dup", "filters": {}, "source": "plex"}) b = c.post("/api/saved-views", json={"name": "Dup", "filters": {}, "source": "plex"}) assert a.status_code == 200 assert b.status_code == 409 # same (user, source, name) def test_a_feed_views_tag_pick_must_be_a_short_list_of_ids(db, monkeypatch, override_dep): """The autotag job reads every view's tag pick (E32 review): the route holds it to the shape the feed sends, on create and on update.""" user = _user(db) c = _client(db, user, monkeypatch, override_dep) for bad in (5, "1", [1, "2"], [True], list(range(501))): r = c.post("/api/saved-views", json={"name": "Bad", "filters": {"tags": bad}}) assert r.status_code == 422, (bad, r.text) ok = c.post("/api/saved-views", json={"name": "Ok", "filters": {"tags": [1, 2]}}) assert ok.status_code == 200, ok.text vid = ok.json()["id"] assert c.patch(f"/api/saved-views/{vid}", json={"filters": {"tags": 5}}).status_code == 422 assert c.patch(f"/api/saved-views/{vid}", json={"filters": {"tags": []}}).status_code == 200