The demo account — one shared user that whitelisted emails entered from the login page, reset on a schedule — goes end to end (owner decision, E34): demo.py and the reset job, users.is_demo and demo_whitelist (migration 0098 deletes the demo user, its audit rows, the reset job's override and notices, then drops the column and table; the downgrade restores the empty schema), the /auth/demo entry, the admin whitelist and reset, the Demo tab, the banner, the login page's "Try the demo", and every is_demo branch in routes, userscope, scheduler and the UI. require_human only ever refused the demo account, so its 87 uses become current_user (owner-approved); require_write_scope keeps its own check. The login page's "or" divider now shows only beside the Google button: without the demo entry under it, an instance without Google would have said "or" to nothing. A contract test (removedFeatures) fails on the word anywhere in the backend, frontend, e2e or scripts — seen red on a planted hit. Size ceilings tightened for the modules that shrank; admin.py is back under the limit.
85 lines
3.7 KiB
Python
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 (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
|