test(be): every dependency override goes through override_dep

22 test files set `app.dependency_overrides[...]` by hand and tore down with a blanket `.clear()`
in a `finally`, which wipes every override rather than the test's own and stops fixtures composing.
They now take the `override_dep` fixture (helpers get it passed in), whose teardown restores each
key's prior value; no clear() carried meaning between two phases of a test. A convention test fails
any test file that writes `dependency_overrides` directly.
This commit is contained in:
2026-09-19 17:26:07 +02:00
parent dc902d2c0e
commit 8759b5860d
23 changed files with 537 additions and 529 deletions
+43 -36
View File
@@ -120,24 +120,21 @@ def _seed(db):
return user
def _get(db, user, monkeypatch, path):
def _get(db, user, monkeypatch, override_dep, path):
monkeypatch.setattr(state, "_configured_cache", True)
app.dependency_overrides[get_db] = lambda: db
app.dependency_overrides[current_user] = lambda: user
try:
client = TestClient(app) # no context manager: skip lifespan
return client.get(path)
finally:
app.dependency_overrides.clear()
override_dep(get_db, lambda: db)
override_dep(current_user, lambda: user)
client = TestClient(app) # no context manager: skip lifespan
return client.get(path)
def _ids(cards):
return [c["id"] for c in cards]
def test_discover_shape_and_recently_added_order(db, monkeypatch):
def test_discover_shape_and_recently_added_order(db, monkeypatch, override_dep):
user = _seed(db)
resp = _get(db, user, monkeypatch, "/api/plex/discover?scope=both")
resp = _get(db, user, monkeypatch, override_dep, "/api/plex/discover?scope=both")
assert resp.status_code == 200, resp.text
d = resp.json()
assert set(d) >= {"continue_watching", "recently_added", "genres", "because_you_watched"}
@@ -148,19 +145,19 @@ def test_discover_shape_and_recently_added_order(db, monkeypatch):
assert genres.get("Drama") == 2 and genres.get("Comedy") == 1
def test_genre_tiles_carry_poster_art(db, monkeypatch):
def test_genre_tiles_carry_poster_art(db, monkeypatch, override_dep):
# Each genre tile carries up to 4 poster rating_keys for the 2×2 collage (top_art_keys): the top
# movies in that genre, best-rated then newest. No ratings here → newest-first; shows are excluded.
user = _seed(db)
d = _get(db, user, monkeypatch, "/api/plex/discover?scope=both").json()
d = _get(db, user, monkeypatch, override_dep, "/api/plex/discover?scope=both").json()
art = {g["value"]: g["art"] for g in d["genres"]}
assert art["Drama"] == ["m1", "m3"] # both Drama movies, m1 (newer) first
assert art["Comedy"] == ["m2"]
def test_continue_watching_movies_and_shows(db, monkeypatch):
def test_continue_watching_movies_and_shows(db, monkeypatch, override_dep):
user = _seed(db)
d = _get(db, user, monkeypatch, "/api/plex/discover?scope=both").json()
d = _get(db, user, monkeypatch, override_dep, "/api/plex/discover?scope=both").json()
ids = _ids(d["continue_watching"])
# The in-progress movie and the show with an in-progress episode are both present; the watched
# anchor movie (m1) is not. Order is most-recently-progressed first (the show's ep beats m3).
@@ -168,7 +165,7 @@ def test_continue_watching_movies_and_shows(db, monkeypatch):
assert "m1" not in ids
def test_continue_watching_null_status_in_hidden_out(db, monkeypatch):
def test_continue_watching_null_status_in_hidden_out(db, monkeypatch, override_dep):
# A started movie whose PlexState.status is NULL is still "in progress": `NULL NOT IN (...)` is NULL
# (not TRUE), so a bare notin_ would wrongly drop it — the or_(is_(None), notin_) branch keeps it.
# A hidden started movie must be excluded.
@@ -207,13 +204,15 @@ def test_continue_watching_null_status_in_hidden_out(db, monkeypatch):
db.commit()
ids = _ids(
_get(db, user, monkeypatch, "/api/plex/discover?scope=movie").json()["continue_watching"]
_get(db, user, monkeypatch, override_dep, "/api/plex/discover?scope=movie").json()[
"continue_watching"
]
)
assert "mn" in ids # NULL-status started movie IS in progress
assert "mh" not in ids # hidden is excluded
def test_continue_watching_excludes_fully_watched_show(db, monkeypatch):
def test_continue_watching_excludes_fully_watched_show(db, monkeypatch, override_dep):
# A show whose every episode is watched is DONE, not in-progress: the `watched < total` guard must
# keep it off the Continue-watching rail (a bare `watched > 0` would wrongly resurface it).
lib_s = PlexLibrary(plex_key="2", title="Shows", kind="show", enabled=True)
@@ -248,14 +247,16 @@ def test_continue_watching_excludes_fully_watched_show(db, monkeypatch):
db.commit()
ids = _ids(
_get(db, user, monkeypatch, "/api/plex/discover?scope=show").json()["continue_watching"]
_get(db, user, monkeypatch, override_dep, "/api/plex/discover?scope=show").json()[
"continue_watching"
]
)
assert "shw" not in ids # fully watched → excluded from continue-watching
def test_because_you_watched_resolves_in_library_recs(db, monkeypatch):
def test_because_you_watched_resolves_in_library_recs(db, monkeypatch, override_dep):
user = _seed(db)
d = _get(db, user, monkeypatch, "/api/plex/discover?scope=both").json()
d = _get(db, user, monkeypatch, override_dep, "/api/plex/discover?scope=both").json()
bw = d["because_you_watched"]
assert bw is not None
assert bw["anchor"]["id"] == "m1" and bw["anchor"]["title"] == "Anchor Movie"
@@ -263,34 +264,36 @@ def test_because_you_watched_resolves_in_library_recs(db, monkeypatch):
assert _ids(bw["items"]) == ["m2"]
def test_because_you_watched_null_without_history(db, monkeypatch):
def test_because_you_watched_null_without_history(db, monkeypatch, override_dep):
_seed(db)
fresh = User(email="fresh-discover@example.com")
db.add(fresh)
db.commit()
d = _get(db, fresh, monkeypatch, "/api/plex/discover?scope=both").json()
d = _get(db, fresh, monkeypatch, override_dep, "/api/plex/discover?scope=both").json()
# A user with no watched-and-enriched movie gets no recommendation rail.
assert d["because_you_watched"] is None
assert d["continue_watching"] == []
def test_surprise_scope_and_bound(db, monkeypatch):
def test_surprise_scope_and_bound(db, monkeypatch, override_dep):
user = _seed(db)
resp = _get(db, user, monkeypatch, "/api/plex/surprise?scope=movie&limit=2")
resp = _get(db, user, monkeypatch, override_dep, "/api/plex/surprise?scope=movie&limit=2")
assert resp.status_code == 200, resp.text
items = resp.json()["items"]
assert len(items) <= 2
assert all(c["type"] == "movie" for c in items) # scope=movie excludes shows
def test_recently_watched_movies_and_episodes(db, monkeypatch):
def test_recently_watched_movies_and_episodes(db, monkeypatch, override_dep):
# The rail lists last-fully-watched titles newest-first: a watched MOVIE as its own card, a show as
# the specific watched EPISODE (with its show title). _seed watches m1 (movie); add a watched episode.
user = _seed(db)
ep2 = db.execute(select(PlexItem).where(PlexItem.rating_key == "sh1e2")).scalars().one()
db.add(PlexState(user_id=user.id, item_id=ep2.id, status="watched", watched_at=NOW))
db.commit()
rw = _get(db, user, monkeypatch, "/api/plex/discover?scope=both").json()["recently_watched"]
rw = _get(db, user, monkeypatch, override_dep, "/api/plex/discover?scope=both").json()[
"recently_watched"
]
by_id = {c["id"]: c for c in rw}
assert "m1" in by_id and by_id["m1"]["type"] == "movie"
assert "sh1e2" in by_id and by_id["sh1e2"]["type"] == "episode"
@@ -299,11 +302,13 @@ def test_recently_watched_movies_and_episodes(db, monkeypatch):
assert set(_ids(rw)) == {"m1", "sh1e2"}
def test_surprise_excludes_watched_and_caps_per_kind(db, monkeypatch):
def test_surprise_excludes_watched_and_caps_per_kind(db, monkeypatch, override_dep):
# Surprise surfaces only UNSEEN titles: the watched movie (m1) is out; the unwatched (m2), the
# in-progress movie (m3) and the partially-watched show (sh1) are eligible. Capped at 3 per kind.
user = _seed(db)
items = _get(db, user, monkeypatch, "/api/plex/surprise?scope=both").json()["items"]
items = _get(db, user, monkeypatch, override_dep, "/api/plex/surprise?scope=both").json()[
"items"
]
ids = _ids(items)
assert "m1" not in ids # watched → excluded
assert set(ids) <= {"m2", "m3", "sh1"} # only unseen titles
@@ -311,19 +316,21 @@ def test_surprise_excludes_watched_and_caps_per_kind(db, monkeypatch):
assert len([c for c in items if c["type"] == "show"]) <= 3
def test_discover_scope_movie_excludes_shows(db, monkeypatch):
def test_discover_scope_movie_excludes_shows(db, monkeypatch, override_dep):
user = _seed(db)
d = _get(db, user, monkeypatch, "/api/plex/discover?scope=movie").json()
d = _get(db, user, monkeypatch, override_dep, "/api/plex/discover?scope=movie").json()
assert all(c["type"] == "movie" for c in d["recently_added"])
# The show drops out of continue watching under the movie scope.
assert _ids(d["continue_watching"]) == ["m3"]
def test_filters_narrow_every_discover_rail(db, monkeypatch):
def test_filters_narrow_every_discover_rail(db, monkeypatch, override_dep):
# The sidebar filters constrain the Discover rails just like the Browse grid (ask 3). A genre=Drama
# filter keeps only the Drama titles across every rail; the genre-less show and the Comedy rec drop.
user = _seed(db)
d = _get(db, user, monkeypatch, "/api/plex/discover?scope=both&genres=Drama").json()
d = _get(
db, user, monkeypatch, override_dep, "/api/plex/discover?scope=both&genres=Drama"
).json()
# Recently-added: m1 + m3 (Drama) survive; Comedy m2 and the genre-less show sh1 are filtered out.
assert set(_ids(d["recently_added"])) == {"m1", "m3"}
# Continue-watching: only the in-progress Drama movie remains (the show has no genres → excluded).
@@ -334,10 +341,10 @@ def test_filters_narrow_every_discover_rail(db, monkeypatch):
assert d["because_you_watched"] is None
def test_filters_narrow_surprise(db, monkeypatch):
def test_filters_narrow_surprise(db, monkeypatch, override_dep):
user = _seed(db)
items = _get(db, user, monkeypatch, "/api/plex/surprise?scope=movie&genres=Comedy").json()[
"items"
]
items = _get(
db, user, monkeypatch, override_dep, "/api/plex/surprise?scope=movie&genres=Comedy"
).json()["items"]
# Only the one Comedy movie qualifies under the filter.
assert _ids(items) == ["m2"]