Fix episode position logging/resume: NaN savePos, clobber-on-boot, unvalidated history writes
- app.js: pause/pagehide listeners passed the Event into savePos(floor), making pos NaN -> serialized as null -> every pause/unload clobbered the saved position. Wrap in arrows; pagehide now posts keepalive so the final save survives page unload - app.js: play() re-fetches history on a miss (boot fetch swallows errors) and only bumps an existing row instead of posting pos:0 at start-of-play - app.py: POST /api/history requires slug_id (was stored as "None"), validates/clamps pos; upsert keeps the old payload when the incoming pos is null so stale clients can't corrupt a row - tests: +3 API tests (slug required, pos clamping, null-pos keeps saved), +1 end-to-end browser test (resume ~400s with a failed boot fetch); lockout test now uses a throwaway username so it can't lock out users the browser tests log in with; hardened the flaky autoplay test
This commit is contained in:
1 parent
7444e29592
commit
d90b1af8d6
4 files changed
+156
-23
No files matched your search
+42
-3
@@ -104,6 +104,45 @@ def test_history_requires_ep_id(base=BASE):
|
||||
assert req("POST", "/api/history", {"slug_id": "x"}, jar=jar, base=base) == 422
|
||||
|
||||
|
||||
def test_history_requires_slug(base=BASE):
|
||||
jar = login("admin", "adminpw12345", base)
|
||||
# a missing slug_id used to be stored as a "None" ghost row
|
||||
assert req("POST", "/api/history", {"ep_id": 991}, jar=jar, base=base) == 422
|
||||
|
||||
|
||||
def test_history_clamps_pos(base=BASE):
|
||||
jar = login("admin", "adminpw12345", base)
|
||||
e = {"ep_id": 992, "slug_id": "junk-a", "title": "Junk", "season_id": 1,
|
||||
"ep": "1", "lang": "eng", "pos": -50, "dur": 100}
|
||||
assert req("POST", "/api/history", e, jar=jar, base=base) == 200
|
||||
got = [x for x in _json("GET", "/api/history?all=1", jar=jar, base=base)
|
||||
if x["slug_id"] == "junk-a"]
|
||||
assert got and got[0]["pos"] == 0.0, got
|
||||
e["pos"] = 999999
|
||||
assert req("POST", "/api/history", e, jar=jar, base=base) == 200
|
||||
got = [x for x in _json("GET", "/api/history?all=1", jar=jar, base=base)
|
||||
if x["slug_id"] == "junk-a"]
|
||||
assert got and got[0]["pos"] == 100.0, got
|
||||
# non-numeric pos is rejected, not stored as junk
|
||||
e["pos"] = "somenonsense"
|
||||
assert req("POST", "/api/history", e, jar=jar, base=base) == 422
|
||||
|
||||
|
||||
def test_history_null_pos_keeps_saved(base=BASE):
|
||||
jar = login("dave", "davepw12345", base)
|
||||
good = {"ep_id": 993, "slug_id": "keep-a", "title": "Keep", "season_id": 1,
|
||||
"ep": "1", "lang": "eng", "pos": 400, "dur": 1500}
|
||||
assert req("POST", "/api/history", good, jar=jar, base=base) == 200
|
||||
# a stale client (old NaN-pos bug) re-POSTs pos null: the upsert must
|
||||
# keep the row's saved position instead of clobbering it to 0
|
||||
bad = dict(good)
|
||||
bad["pos"] = None
|
||||
assert req("POST", "/api/history", bad, jar=jar, base=base) == 200
|
||||
got = [x for x in _json("GET", "/api/history?all=1", jar=jar, base=base)
|
||||
if x["slug_id"] == "keep-a"]
|
||||
assert got and got[0]["pos"] == 400, got
|
||||
|
||||
|
||||
def test_history_per_user(base=BASE):
|
||||
a = login("admin", "adminpw12345", base)
|
||||
b = login("alice", "alicepw12345", base)
|
||||
@@ -258,9 +297,9 @@ def test_stream_token_host_allowlist(base=BASE):
|
||||
|
||||
|
||||
def test_lockout_per_user(base=BASE):
|
||||
# must run on a fresh server (in-memory lockout): 5 fails -> 429 for bob,
|
||||
# admin unaffected
|
||||
codes = [req("POST", "/login", {"username": "bob", "password": "bad"}, base=base)
|
||||
# a throwaway name: the lockout is per-username and in-memory, and bob is
|
||||
# also used by the browser tests (a locked-out bob breaks their logins)
|
||||
codes = [req("POST", "/login", {"username": "lockout-ghost", "password": "bad"}, base=base)
|
||||
for _ in range(6)]
|
||||
assert codes[:5] == [401] * 5 and codes[5] == 429, codes
|
||||
assert req("POST", "/login", {"username": "admin", "password": "adminpw12345"}, base=base) == 200
|
||||
|
||||
Reference in new issue
Block a user