fix(e5): deep-review survivors — TMDB key log leak, person-enrich rollback + timeout
Three verified deep-review findings (the 4th, the filter combobox a11y gap, is deferred to a dedicated session on the combobox architecture): - SEC (medium): TmdbClient sent the api_key as a URL query param, so an HTTP error's httpx exception string embedded the full URL incl. the key, which enrich_batch logged. _get now raises status+path only (never str(e)/the URL) and severs the secret-bearing cause with `from None`. Regression test asserts neither the status nor the network error path contains the key. - STATE (low): person_detail's lazy-enrich except now db.rollback()s, so a failed enrich_person commit can't poison the session and 500 the subsequent SELECTs (SQLAlchemy 2.x deactivates the tx on a commit-time flush failure and does not auto-reset it). Test locks that the page renders despite a poisoned enrich. - ABUSE (low): the request-path lazy person enrich now caps its TMDB client at an 8s timeout (was the 30s default) so a slow TMDB can't tie up a worker; TmdbClient gained a `timeout` param (batch keeps 30s). The fuller fix (move person enrichment off the request path) is logged for later.
This commit is contained in:
@@ -66,7 +66,7 @@ def test_enrich_person_persists_and_stamps(db, monkeypatch):
|
||||
db.add(person)
|
||||
db.commit()
|
||||
monkeypatch.setattr(plex_tmdb.sysconfig, "get_str", lambda d, k: "key" if k == "tmdb_api_key" else "")
|
||||
monkeypatch.setattr(plex_tmdb, "TmdbClient", lambda d: _FakeTmdb(PAYLOAD))
|
||||
monkeypatch.setattr(plex_tmdb, "TmdbClient", lambda d, timeout=None: _FakeTmdb(PAYLOAD))
|
||||
assert plex_tmdb.enrich_person(db, person) is True
|
||||
assert person.biography == "A celebrated actor."
|
||||
assert person.tmdb_enriched_at is not None
|
||||
@@ -100,6 +100,36 @@ def _get(db, user, monkeypatch, path):
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
|
||||
def test_person_detail_survives_poisoned_enrich(db, monkeypatch):
|
||||
# If the lazy enrich_person leaves the session in a failed-transaction state (e.g. a commit-time
|
||||
# flush error), person_detail's except must roll it back so the following SELECTs still run — the
|
||||
# page renders despite the enrich failure ("never fail the page"). Without the db.rollback() fix the
|
||||
# poisoned session would raise PendingRollbackError on the next query and 500.
|
||||
from sqlalchemy import text
|
||||
|
||||
lib = PlexLibrary(plex_key="9", title="Movies", kind="movie", enabled=True)
|
||||
db.add(lib)
|
||||
db.flush()
|
||||
# Un-enriched person (tmdb_person_id set + tmdb_enriched_at None) → triggers the lazy enrich path.
|
||||
db.add(PlexPerson(name="Poison P", tmdb_person_id=777))
|
||||
user = User(email="poison@example.com")
|
||||
db.add(user)
|
||||
db.commit()
|
||||
|
||||
def _poison_then_raise(db_, person_):
|
||||
try:
|
||||
db_.execute(text("SELECT 1 / 0")) # aborts the transaction (like a failed enrich commit)
|
||||
except Exception: # noqa: BLE001 — swallow so the tx stays poisoned, then raise like the real path
|
||||
pass
|
||||
raise RuntimeError("enrich commit blew up")
|
||||
|
||||
monkeypatch.setattr(plex_tmdb, "enrich_person", _poison_then_raise)
|
||||
|
||||
resp = _get(db, user, monkeypatch, "/api/plex/person/Poison P")
|
||||
assert resp.status_code == 200, resp.text # page still renders (session was rolled back)
|
||||
assert resp.json()["biography"] is None # enrich failed, so no bio
|
||||
|
||||
|
||||
def test_person_detail_serves_bio_and_resolves_known_for(db, monkeypatch):
|
||||
lib = PlexLibrary(plex_key="1", title="Movies", kind="movie", enabled=True)
|
||||
db.add(lib)
|
||||
|
||||
Reference in New Issue
Block a user