Files
siftlode/backend/tests/test_saved_views_source.py
T
peter 4aaaaa7860 chore(comments): drop epic, sprint and review-round ids from the code
The code cited its plan instead of saying why: `E28 review J4`, `(R6 S3b)`, `S1 justified…`,
`deep-review round 5`. That plan lives in the private ops repo, so for every reader of this one the
citation was noise, and a sentence whose subject was the plan step ("round 2 showed…") hid its
lesson behind an id. Each one is gone and the "why" kept: a bare citation is deleted, a sentence
carrying a lesson is reworded around it ("a later attempt showed…").

Measured (`scripts/audit_metrics.py`, product code): 652 lines in 270 files carried an epic code by
the definition the next commit holds (507 / 241 by the old, two-digit-only one) -> 0. Across all
code, tests, scripts and configs: ~1100 epic-code lines plus ~320 sprint/round ids.

Behaviour unchanged, checked mechanically: every changed .py file has the same AST once docstrings
are blanked; css/sh/toml/yml/json files are identical once comments are stripped; the only
non-comment changes in TS/JS are test titles, one description string in a contract table, and five
ESLint messages, whose text is all the gates snapshot diff holds. Not touched: Markdown (docs and
the changelog keep their history), Alembic migrations, TV episode notation (`S2 · E5`), DESIGN.md
ids (`C1`, `B4`, `S3h`), tracker ids (`SEC-16`, `DX-17`).

One stale claim fixed on the way: test_hls_reaper's docstring said the player checkpoints "every
10 s whether or not it is playing", which plex/stream.py calls wrong; it now says what the code does.
2026-10-05 00:19:39 +02:00

85 lines
3.7 KiB
Python

"""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)
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: 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