test: no test throws a response away; the stream session is narrowed, not baselined
The 7 baselined Optional accesses in plex/stream.py were one fact the type did not carry: the initial spawn (replace_only=None) always supersedes, so only the fallback respawn can get None. An assert at the call site says so; the pyright baseline drops 195 -> 188. test_abuse_limits: the public-URL and YouTube enqueue tests stopped at the profile lookup (400 no_profile) and passed on that unrelated refusal. They now send an inline spec and assert the job is queued. The same shape — a request whose response is discarded — sat at 53 more places across the suite (all returned what they should, measured with a TestClient probe); each now asserts its status, and test_response_checked keeps it that way (seen red).
This commit is contained in:
@@ -548,64 +548,6 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"./app/plex/stream.py": [
|
||||
{
|
||||
"code": "reportOptionalMemberAccess",
|
||||
"range": {
|
||||
"startColumn": 51,
|
||||
"endColumn": 55,
|
||||
"lineCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "reportOptionalMemberAccess",
|
||||
"range": {
|
||||
"startColumn": 23,
|
||||
"endColumn": 25,
|
||||
"lineCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "reportOptionalMemberAccess",
|
||||
"range": {
|
||||
"startColumn": 18,
|
||||
"endColumn": 31,
|
||||
"lineCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "reportOptionalMemberAccess",
|
||||
"range": {
|
||||
"startColumn": 75,
|
||||
"endColumn": 79,
|
||||
"lineCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "reportOptionalMemberAccess",
|
||||
"range": {
|
||||
"startColumn": 10,
|
||||
"endColumn": 23,
|
||||
"lineCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "reportOptionalMemberAccess",
|
||||
"range": {
|
||||
"startColumn": 10,
|
||||
"endColumn": 12,
|
||||
"lineCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "reportOptionalMemberAccess",
|
||||
"range": {
|
||||
"startColumn": 10,
|
||||
"endColumn": 17,
|
||||
"lineCount": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"./app/plex/sync.py": [
|
||||
{
|
||||
"code": "reportArgumentType",
|
||||
|
||||
@@ -745,8 +745,7 @@ def start_session(
|
||||
# check-and-replace happens under ONE _lock, so a newer same-key session (a concurrent
|
||||
# seek-restart) installed between the fallback's decision and here is never clobbered. When the
|
||||
# registered session is no longer `replace_only`, we abort and return None (the caller yields to
|
||||
# whoever is registered now). The INITIAL spawn passes replace_only=None and always supersedes —
|
||||
# that is the seek-restart's intended last-writer-wins.
|
||||
# whoever is registered now). The INITIAL spawn never gets None — see its call site.
|
||||
# Software-fallback encode tuning — read only on the SW transcode path (the VAAPI path and
|
||||
# remux ignore preset/crf/max_height, and remux must not touch the DB for them).
|
||||
preset, crf, max_height = "veryfast", 20, 0
|
||||
@@ -823,6 +822,7 @@ def start_session(
|
||||
return s
|
||||
|
||||
s = _spawn(*_select_encode(db, item, transcode, src))
|
||||
assert s is not None # replace_only=None always supersedes: a seek-restart's last-writer-wins
|
||||
# Wait for the first video segment outside the lock (this blocks on ffmpeg, and we must not hold up
|
||||
# other sessions). Pass the proc so a HW session that dies at VAAPI init fails fast (not after 25s).
|
||||
ready = wait_for(_seg(0), timeout=25.0, proc=s.proc)
|
||||
|
||||
@@ -36,9 +36,14 @@ def test_a_public_url_gets_past_the_guard(db, as_user, make_user, monkeypatch):
|
||||
"getaddrinfo",
|
||||
lambda *a, **kw: [(None, None, None, "", ("93.184.216.34", 0))],
|
||||
)
|
||||
r = as_user(user).post("/api/downloads", json={"source": "https://example.com/v"})
|
||||
detail = r.json().get("detail") if r.status_code >= 400 else None
|
||||
assert not (isinstance(detail, dict) and detail.get("code") == "url_blocked")
|
||||
# An inline spec, so nothing ELSE refuses it: without one the request stopped at the
|
||||
# profile lookup (400 no_profile) and "not url_blocked" held for a request the guard never
|
||||
# had to wave through (E32 S2).
|
||||
r = as_user(user).post(
|
||||
"/api/downloads", json={"source": "https://example.com/v", "spec": {"format": "best"}}
|
||||
)
|
||||
assert r.status_code == 200, r.text
|
||||
assert r.json()["status"] == "queued"
|
||||
|
||||
|
||||
def test_youtube_sources_never_consult_the_guard(db, as_user, make_user, monkeypatch):
|
||||
@@ -50,7 +55,13 @@ def test_youtube_sources_never_consult_the_guard(db, as_user, make_user, monkeyp
|
||||
user = make_user()
|
||||
db.commit()
|
||||
monkeypatch.setattr(urlguard.socket, "getaddrinfo", boom)
|
||||
as_user(user).post("/api/downloads", json={"source": "dQw4w9WgXcQ"})
|
||||
# The job must actually be created: a request refused for any other reason would never reach
|
||||
# the guard either, and the test would pass without proving anything (E32 S2).
|
||||
r = as_user(user).post(
|
||||
"/api/downloads", json={"source": "dQw4w9WgXcQ", "spec": {"format": "best"}}
|
||||
)
|
||||
assert r.status_code == 200, r.text
|
||||
assert r.json()["source_kind"] == "youtube"
|
||||
|
||||
|
||||
def test_manual_subscription_sync_goes_on_cooldown(db, as_user, make_user, monkeypatch):
|
||||
@@ -81,8 +92,12 @@ def test_the_cooldown_is_per_user_not_global(db, as_user, make_user, monkeypatch
|
||||
monkeypatch.setattr(syncroutes, "import_subscriptions", lambda d, u: {"imported": 0})
|
||||
|
||||
ca = as_user(a)
|
||||
for _ in range(4):
|
||||
ca.post("/api/sync/subscriptions")
|
||||
assert [ca.post("/api/sync/subscriptions").status_code for _ in range(4)] == [
|
||||
200,
|
||||
200,
|
||||
200,
|
||||
429,
|
||||
]
|
||||
assert ca.post("/api/sync/subscriptions").status_code == 429
|
||||
assert as_user(b).post("/api/sync/subscriptions").status_code == 200
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ def test_nobody_reaches_another_users_session(db, make_user, as_user):
|
||||
def test_the_list_is_the_users_own_newest_first(db, make_user, as_user):
|
||||
client = as_user(make_user())
|
||||
first = _start(client)["id"]
|
||||
client.post(f"/api/debug-sessions/{first}/stop")
|
||||
assert client.post(f"/api/debug-sessions/{first}/stop").status_code == 200
|
||||
second = _start(client)["id"]
|
||||
assert [s["id"] for s in client.get("/api/debug-sessions").json()["items"]] == [second, first]
|
||||
|
||||
@@ -199,10 +199,13 @@ def test_an_erased_account_takes_its_sessions_with_it(db, make_user, as_user):
|
||||
user = make_user()
|
||||
client = as_user(user)
|
||||
sid = _start(client)["id"]
|
||||
client.post(
|
||||
f"/api/debug-sessions/{sid}/screenshots",
|
||||
content=_PNG,
|
||||
headers={"content-type": "image/png"},
|
||||
assert (
|
||||
client.post(
|
||||
f"/api/debug-sessions/{sid}/screenshots",
|
||||
content=_PNG,
|
||||
headers={"content-type": "image/png"},
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
purge_user(db, user, BackgroundTasks())
|
||||
assert db.get(DebugSession, sid) is None
|
||||
@@ -250,7 +253,7 @@ def test_a_request_naming_the_own_open_session_is_tagged_and_raised(gated, db, c
|
||||
_signed_in(client, make_user)
|
||||
sid = _start(client, "DEBUG")["id"]
|
||||
gated.records.clear()
|
||||
client.get("/api/debug-sessions", headers={"x-debug-session": sid})
|
||||
assert client.get("/api/debug-sessions", headers={"x-debug-session": sid}).status_code == 200
|
||||
(line,) = _request_lines(gated)
|
||||
assert getattr(line, "debug_session_id", None) == sid
|
||||
# Raised to the session's level: the line is kept for storage though the user's own is Alap.
|
||||
@@ -263,7 +266,7 @@ def test_a_header_naming_anything_else_is_ignored_not_refused(gated, db, client,
|
||||
db.commit()
|
||||
_signed_in(client, make_user)
|
||||
own = _start(client)["id"]
|
||||
client.post(f"/api/debug-sessions/{own}/stop")
|
||||
assert client.post(f"/api/debug-sessions/{own}/stop").status_code == 200
|
||||
for value in (foreign, own, "not-a-session", "f" * 16, "A" * 16):
|
||||
gated.records.clear()
|
||||
# Not `/api/me`: a polled route logs at DEBUG, and an un-raised request would drop the very
|
||||
@@ -280,7 +283,7 @@ def test_a_header_naming_the_own_session_past_its_cap_is_ignored(gated, db, clie
|
||||
sid = _start(client)["id"]
|
||||
_expire(db, sid) # past its cap, not yet swept: still open in the table
|
||||
gated.records.clear()
|
||||
client.get("/api/debug-sessions", headers={"x-debug-session": sid})
|
||||
assert client.get("/api/debug-sessions", headers={"x-debug-session": sid}).status_code == 200
|
||||
(line,) = _request_lines(gated)
|
||||
assert getattr(line, "debug_session_id", None) is None
|
||||
assert getattr(line, "elevated", False) is False
|
||||
@@ -616,7 +619,7 @@ def test_a_users_screenshots_are_capped_across_their_sessions(recording, monkeyp
|
||||
client, sid = recording
|
||||
monkeypatch.setattr(debugsession, "SCREENSHOT_USER_MAX_BYTES", len(_PNG) * 2)
|
||||
assert _upload(client, sid, _PNG).status_code == 200
|
||||
client.post(f"/api/debug-sessions/{sid}/stop")
|
||||
assert client.post(f"/api/debug-sessions/{sid}/stop").status_code == 200
|
||||
other = _start(client)["id"]
|
||||
assert _upload(client, other, _PNG).status_code == 200
|
||||
r = _upload(client, other, _PNG)
|
||||
@@ -631,7 +634,7 @@ def test_new_sessions_are_paced_but_joining_one_is_not(db, make_user, as_user, m
|
||||
client = as_user(make_user())
|
||||
sid = _start(client)["id"]
|
||||
assert _start(client)["id"] == sid # another tab joining
|
||||
client.post(f"/api/debug-sessions/{sid}/stop")
|
||||
assert client.post(f"/api/debug-sessions/{sid}/stop").status_code == 200
|
||||
r = client.post("/api/debug-sessions", json={"level": "DEBUG"})
|
||||
assert r.status_code == 429 and r.json()["detail"]["reason"] == "debug_session_rate_limited"
|
||||
|
||||
@@ -745,9 +748,16 @@ def test_the_export_is_the_saved_draft_the_evidence_and_the_screenshots(db, make
|
||||
db.commit()
|
||||
_upload(client, sid, _PNG)
|
||||
_upload(client, sid, _JPEG)
|
||||
client.put(
|
||||
f"/api/debug-sessions/{sid}/draft",
|
||||
json={"title": "Spinner stays", "body": "It spins.", "excluded": [dropped_web.id, 999999]},
|
||||
assert (
|
||||
client.put(
|
||||
f"/api/debug-sessions/{sid}/draft",
|
||||
json={
|
||||
"title": "Spinner stays",
|
||||
"body": "It spins.",
|
||||
"excluded": [dropped_web.id, 999999],
|
||||
},
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
|
||||
r = client.get(f"/api/debug-sessions/{sid}/export")
|
||||
@@ -1144,7 +1154,7 @@ def test_new_issues_are_paced_per_user(gitea_fake, db, make_user, as_user, monke
|
||||
first = _start(client)["id"]
|
||||
_ready(client, first)
|
||||
assert client.post(f"/api/debug-sessions/{first}/issue").status_code == 200
|
||||
client.post(f"/api/debug-sessions/{first}/stop")
|
||||
assert client.post(f"/api/debug-sessions/{first}/stop").status_code == 200
|
||||
second = _start(client)["id"]
|
||||
_ready(client, second)
|
||||
r = client.post(f"/api/debug-sessions/{second}/issue")
|
||||
|
||||
@@ -212,7 +212,12 @@ def test_a_timestamp_outside_the_consent_window_is_clamped(elevated):
|
||||
now = datetime.now(timezone.utc)
|
||||
future = (now + timedelta(days=365)).isoformat()
|
||||
past = (now - timedelta(days=30)).isoformat()
|
||||
client.post("/api/diagnostics/events", json={"events": [_event(ts=future), _event(ts=past)]})
|
||||
assert (
|
||||
client.post(
|
||||
"/api/diagnostics/events", json={"events": [_event(ts=future), _event(ts=past)]}
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
late, early = rows()
|
||||
assert late.ts <= datetime.now(timezone.utc)
|
||||
assert early.ts >= now - timedelta(hours=diagnostics.ELEVATION_HOURS, minutes=1)
|
||||
@@ -342,15 +347,15 @@ def _app() -> TestClient:
|
||||
|
||||
|
||||
def test_the_account_reaches_the_handlers_own_lines(gated):
|
||||
_app().get("/api/thing/41")
|
||||
assert _app().get("/api/thing/41").status_code == 200
|
||||
line = next(r for r in gated.records if r.getMessage() == "an ordinary line")
|
||||
assert getattr(line, "user_id", None) == 41
|
||||
|
||||
|
||||
def test_one_users_elevation_logs_their_requests_in_detail_and_nobody_elses(gated):
|
||||
client = _app()
|
||||
client.get("/api/thing/7", params={"level": "DEBUG"})
|
||||
client.get("/api/thing/8")
|
||||
assert client.get("/api/thing/7", params={"level": "DEBUG"}).status_code == 200
|
||||
assert client.get("/api/thing/8").status_code == 200
|
||||
detail = [r for r in gated.records if r.getMessage().startswith("detail only")]
|
||||
assert [r.user_id for r in detail] == [7]
|
||||
assert getattr(detail[0], "elevated", False) is True
|
||||
@@ -363,9 +368,9 @@ def test_one_users_elevation_logs_their_requests_in_detail_and_nobody_elses(gate
|
||||
|
||||
def test_the_root_level_returns_to_the_base_when_the_elevation_ends(gated):
|
||||
client = _app()
|
||||
client.get("/api/thing/7", params={"level": "TRACE"})
|
||||
assert client.get("/api/thing/7", params={"level": "TRACE"}).status_code == 200
|
||||
assert logging.getLogger().level == logs.TRACE
|
||||
client.get("/api/thing/7") # the same user, back at the base
|
||||
assert client.get("/api/thing/7").status_code == 200 # the same user, back at the base
|
||||
assert logging.getLogger().level == logging.INFO
|
||||
logs._track_elevation(9, logging.DEBUG, datetime.now(timezone.utc) - timedelta(seconds=1))
|
||||
assert logging.getLogger().level == logging.INFO # an already-expired one is pruned at once
|
||||
@@ -373,15 +378,25 @@ def test_the_root_level_returns_to_the_base_when_the_elevation_ends(gated):
|
||||
|
||||
def test_the_query_string_is_logged_only_for_a_traced_request_and_redacted(gated):
|
||||
client = _app()
|
||||
client.get("/api/thing/7", params={"level": "DEBUG", "q": "lofi"})
|
||||
client.get("/api/thing/7", params={"level": "TRACE", "q": "lofi", "token": "SECRET"})
|
||||
assert client.get("/api/thing/7", params={"level": "DEBUG", "q": "lofi"}).status_code == 200
|
||||
assert (
|
||||
client.get(
|
||||
"/api/thing/7", params={"level": "TRACE", "q": "lofi", "token": "SECRET"}
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
lines = [r for r in gated.records if getattr(r, "event", None) == "http.request"]
|
||||
assert "query" not in lines[0].attrs
|
||||
assert "q=lofi" in lines[1].attrs["query"] and "SECRET" not in lines[1].attrs["query"]
|
||||
|
||||
|
||||
def test_an_address_typed_into_a_search_is_redacted_though_the_browser_encodes_it(gated):
|
||||
_app().get("/api/thing/7?level=TRACE&q=someone%40example.com&new_password=hunter2")
|
||||
assert (
|
||||
_app()
|
||||
.get("/api/thing/7?level=TRACE&q=someone%40example.com&new_password=hunter2")
|
||||
.status_code
|
||||
== 200
|
||||
)
|
||||
(line,) = [r for r in gated.records if getattr(r, "event", None) == "http.request"]
|
||||
assert line.attrs["query"] == "level=TRACE&q=[email]&new_password=[redacted]"
|
||||
|
||||
@@ -400,7 +415,7 @@ def test_a_credential_param_goes_whole_however_its_value_decodes(raw):
|
||||
|
||||
def test_an_admin_level_of_trace_does_not_log_other_users_query_strings(gated):
|
||||
logs.set_level("TRACE")
|
||||
_app().get("/api/thing/7", params={"q": "something private"})
|
||||
assert _app().get("/api/thing/7", params={"q": "something private"}).status_code == 200
|
||||
(line,) = [r for r in gated.records if getattr(r, "event", None) == "http.request"]
|
||||
assert "query" not in line.attrs
|
||||
|
||||
@@ -488,7 +503,7 @@ def test_a_real_sign_in_carries_the_consented_level_into_the_process(gated, clie
|
||||
user = _signed_in(client, make_user)
|
||||
assert client.put("/api/me/diagnostics", json={"level": "DEBUG"}).status_code == 200
|
||||
logs._elevations.clear() # only a request resolving the account through auth may put it back
|
||||
client.get("/api/me")
|
||||
assert client.get("/api/me").status_code == 200
|
||||
assert logs._elevations[user.id][0] == logging.DEBUG
|
||||
assert client.put("/api/me/diagnostics", json={"level": "INFO"}).status_code == 200
|
||||
assert user.id not in logs._elevations
|
||||
|
||||
@@ -334,7 +334,7 @@ def test_me_reports_the_google_address_only_when_it_differs(client, db, make_use
|
||||
)
|
||||
assert client.get("/api/me").json()["google_email"] is None
|
||||
|
||||
client.post("/auth/logout")
|
||||
assert client.post("/auth/logout").status_code == 200
|
||||
tagged = make_user(email="me+siftlode@gmail.com", password=_UAT_PW)
|
||||
tagged.google_email = "me@gmail.com"
|
||||
db.commit()
|
||||
|
||||
@@ -100,7 +100,12 @@ def test_the_admin_user_list_carries_last_login(as_user, db, make_user):
|
||||
def test_the_account_sees_its_own_last_sign_in(client, db, make_user):
|
||||
# Settings → Account shows it (prod UAT 2026-09-19: only the admin's user list did).
|
||||
make_user(email="me@example.com", password=_PW)
|
||||
client.post("/auth/password-login", json={"email": "me@example.com", "password": _PW})
|
||||
assert (
|
||||
client.post(
|
||||
"/auth/password-login", json={"email": "me@example.com", "password": _PW}
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
|
||||
stamp = client.get("/api/me").json()["last_login_at"]
|
||||
|
||||
|
||||
@@ -277,7 +277,7 @@ def test_a_signed_in_request_is_never_capped_and_names_its_account(sink):
|
||||
|
||||
client = TestClient(app)
|
||||
for _ in range(logs.ANONYMOUS_LINES_PER_MINUTE + 3):
|
||||
client.get("/api/me")
|
||||
assert client.get("/api/me").status_code == 200
|
||||
records = sink.records[-(logs.ANONYMOUS_LINES_PER_MINUTE + 3) :]
|
||||
assert all(getattr(r, logs.PERSIST, True) for r in records)
|
||||
assert sink.objects()[-1]["user_id"] == 42 and "user_id" not in sink.objects()[-1]["attrs"]
|
||||
@@ -325,9 +325,9 @@ def test_the_log_consoles_own_reads_are_debug_but_its_failures_are_not(sink):
|
||||
http = logging.getLogger("siftlode.http")
|
||||
http.setLevel(logs.TRACE)
|
||||
client = _client()
|
||||
client.get("/api/admin/logs/facets")
|
||||
assert client.get("/api/admin/logs/facets").status_code == 200
|
||||
assert sink.objects()[-1]["level"] == "DEBUG"
|
||||
client.get("/api/admin/logs/facets?fail=1")
|
||||
assert client.get("/api/admin/logs/facets?fail=1").status_code == 400
|
||||
assert sink.objects()[-1]["level"] == "WARNING"
|
||||
|
||||
|
||||
@@ -344,7 +344,7 @@ def test_a_polled_read_is_debug_a_write_to_the_same_route_is_not(sink):
|
||||
r = client.post("/api/messages/unread_count")
|
||||
assert sink.objects()[-1]["level"] == "INFO"
|
||||
assert "X-Log-Quiet" not in r.headers
|
||||
client.get("/api/videos/abc")
|
||||
assert client.get("/api/videos/abc").status_code == 200
|
||||
assert sink.objects()[-1]["level"] == "INFO"
|
||||
|
||||
|
||||
@@ -360,7 +360,7 @@ def test_every_polled_route_is_a_route_the_app_has():
|
||||
|
||||
def test_a_failing_request_is_logged_by_status_and_an_unhandled_error_is_logged_at_all(sink):
|
||||
client = _client()
|
||||
client.get("/api/videos/missing/deeper") # 404 from the router
|
||||
assert client.get("/api/videos/missing/deeper").status_code == 404 # 404 from the router
|
||||
assert sink.objects()[-1]["level"] == "WARNING"
|
||||
client.get("/boom")
|
||||
payload = sink.objects()[-1]
|
||||
|
||||
@@ -154,8 +154,18 @@ def test_an_access_request_records_the_language_it_was_made_in(db, client):
|
||||
|
||||
|
||||
def test_asking_again_from_another_language_updates_the_row(db, client):
|
||||
client.post("/auth/request-access", json={"email": "asking@example.com", "lang": "en"})
|
||||
client.post("/auth/request-access", json={"email": "asking@example.com", "lang": "hu"})
|
||||
assert (
|
||||
client.post(
|
||||
"/auth/request-access", json={"email": "asking@example.com", "lang": "en"}
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
assert (
|
||||
client.post(
|
||||
"/auth/request-access", json={"email": "asking@example.com", "lang": "hu"}
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
|
||||
db.expire_all()
|
||||
inv = db.execute(select(Invite).where(Invite.email == "asking@example.com")).scalar_one()
|
||||
@@ -165,7 +175,12 @@ def test_asking_again_from_another_language_updates_the_row(db, client):
|
||||
|
||||
|
||||
def test_an_unsupported_language_is_ignored_rather_than_stored(db, client):
|
||||
client.post("/auth/request-access", json={"email": "asking@example.com", "lang": "../../etc"})
|
||||
assert (
|
||||
client.post(
|
||||
"/auth/request-access", json={"email": "asking@example.com", "lang": "../../etc"}
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
|
||||
inv = db.execute(select(Invite).where(Invite.email == "asking@example.com")).scalar_one()
|
||||
assert inv.lang is None
|
||||
@@ -194,7 +209,12 @@ def test_the_confirmation_mail_is_written_in_the_accounts_language(db, client, m
|
||||
monkeypatch.setattr(email_mod, "email_enabled", lambda db=None: True)
|
||||
sent = _bodies(monkeypatch)
|
||||
|
||||
client.post("/auth/register", json={"email": "hu@example.com", "password": _PW, "lang": "hu"})
|
||||
assert (
|
||||
client.post(
|
||||
"/auth/register", json={"email": "hu@example.com", "password": _PW, "lang": "hu"}
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
|
||||
(_, subject, body) = sent[0]
|
||||
assert subject == mailtext.MAIL_TEXT["verify_email.subject"]["hu"]
|
||||
@@ -209,7 +229,12 @@ def test_the_approval_mail_answers_in_the_language_the_request_was_made_in(
|
||||
monkeypatch.setattr(email_mod, "email_enabled", lambda db=None: True)
|
||||
sent = _bodies(monkeypatch)
|
||||
admin = make_user(email="admin@example.com", role="admin")
|
||||
client.post("/auth/request-access", json={"email": "asking@example.com", "lang": "hu"})
|
||||
assert (
|
||||
client.post(
|
||||
"/auth/request-access", json={"email": "asking@example.com", "lang": "hu"}
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
inv = db.execute(select(Invite).where(Invite.email == "asking@example.com")).scalar_one()
|
||||
sent.clear() # drop the admin notification; this test is about the reply to the requester
|
||||
|
||||
@@ -227,11 +252,16 @@ def test_a_denial_answers_in_that_language_too(db, client, make_user, as_user, m
|
||||
monkeypatch.setattr(email_mod, "email_enabled", lambda db=None: True)
|
||||
sent = _bodies(monkeypatch)
|
||||
admin = make_user(email="admin@example.com", role="admin")
|
||||
client.post("/auth/request-access", json={"email": "asking@example.com", "lang": "hu"})
|
||||
assert (
|
||||
client.post(
|
||||
"/auth/request-access", json={"email": "asking@example.com", "lang": "hu"}
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
inv = db.execute(select(Invite).where(Invite.email == "asking@example.com")).scalar_one()
|
||||
sent.clear()
|
||||
|
||||
as_user(admin).post(f"/api/admin/invites/{inv.id}/deny")
|
||||
assert as_user(admin).post(f"/api/admin/invites/{inv.id}/deny").status_code == 200
|
||||
|
||||
assert sent[0][1] == mailtext.MAIL_TEXT["access_denied.subject"]["hu"]
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ def test_putting_a_removed_video_back_leaves_a_linked_list_clean(db, make_user,
|
||||
pl.synced_fingerprint = current_fingerprint(db, pl)
|
||||
pl.dirty = False
|
||||
db.commit()
|
||||
client.delete(f"/api/playlists/{pl.id}/items/b")
|
||||
assert client.delete(f"/api/playlists/{pl.id}/items/b").status_code == 200
|
||||
db.expire_all()
|
||||
assert db.get(Playlist, pl.id).dirty is True
|
||||
_add(client, pl.id, {"video_id": "b", "position": 1})
|
||||
|
||||
@@ -67,7 +67,7 @@ def test_each_variant_is_cut_at_its_own_size(db, make_user, monkeypatch, tmp_pat
|
||||
]
|
||||
# Each cut is cached under its own name: asking again reaches Plex no more.
|
||||
for variant in ("thumb", "thumb_sm", "art", "art_sm"):
|
||||
client.get(f"/api/plex/image/pm1?variant={variant}")
|
||||
assert client.get(f"/api/plex/image/pm1?variant={variant}").status_code == 200
|
||||
assert len(_FakePlex.calls) == 4
|
||||
|
||||
|
||||
|
||||
@@ -105,8 +105,11 @@ def test_remove_then_put_back_is_no_change(db, make_user, as_user):
|
||||
user = make_user()
|
||||
pl = _seed(db, user, ["a", "b", "c", "d"], extra=())
|
||||
client = as_user(user)
|
||||
client.post(
|
||||
f"/api/plex/playlists/{pl.id}/items/remove-bulk", json={"item_rating_keys": ["b", "d"]}
|
||||
assert (
|
||||
client.post(
|
||||
f"/api/plex/playlists/{pl.id}/items/remove-bulk", json={"item_rating_keys": ["b", "d"]}
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
assert _order(db, pl.id)[0] == ["a", "c"]
|
||||
_add(client, pl.id, ["b", "d"], [1, 3])
|
||||
|
||||
@@ -208,7 +208,7 @@ def test_an_unshowable_track_says_why_and_maps_to_a_status(client, monkeypatch):
|
||||
raise subtitles.SubtitleUnavailable("bitmap")
|
||||
|
||||
monkeypatch.setattr(subtitles, "_extract_and_publish", _bitmap)
|
||||
client.get("/api/plex/subtitle/98614/0/status")
|
||||
assert client.get("/api/plex/subtitle/98614/0/status").status_code == 200
|
||||
pool.run_all()
|
||||
st = client.get("/api/plex/subtitle/98614/0/status").json()
|
||||
assert st["state"] == "failed" and st["code"] == "bitmap"
|
||||
|
||||
@@ -51,7 +51,12 @@ def test_a_link_whose_account_was_deleted_is_reported_dead(client, db, make_user
|
||||
|
||||
def test_a_spent_link_is_reported_dead(client, db, make_user):
|
||||
raw = _issue(db, make_user(email="spent@example.com", password=_PW))
|
||||
client.post("/auth/password-reset/confirm", json={"token": raw, "password": _NEW})
|
||||
assert (
|
||||
client.post(
|
||||
"/auth/password-reset/confirm", json={"token": raw, "password": _NEW}
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
|
||||
assert client.post("/auth/password-reset/check", json={"token": raw}).json() == {"ok": False}
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
"""No test sends a request and throws the response away (E32 S2).
|
||||
|
||||
A discarded `client.post(...)` passes whatever it got back. As a SETUP step, a failure leaves the
|
||||
test asserting on a state it never reached; as the step UNDER test, the test proves nothing. E32
|
||||
found `test_youtube_sources_never_consult_the_guard` passing on a 400 that stopped the request
|
||||
before the code it meant to exercise, and 53 more discarded responses across the suite. Each one
|
||||
now states the status it expects; this keeps it that way.
|
||||
"""
|
||||
|
||||
import ast
|
||||
from pathlib import Path
|
||||
|
||||
TESTS = Path(__file__).resolve().parent
|
||||
VERBS = {"get", "post", "put", "patch", "delete", "head", "options", "request"}
|
||||
|
||||
|
||||
def discarded_requests(source: str) -> list[int]:
|
||||
"""Line numbers of the statements that are a bare request call on an app path."""
|
||||
hits = []
|
||||
for node in ast.walk(ast.parse(source)):
|
||||
if not (isinstance(node, ast.Expr) and isinstance(node.value, ast.Call)):
|
||||
continue
|
||||
func = node.value.func
|
||||
if not (isinstance(func, ast.Attribute) and func.attr in VERBS):
|
||||
continue
|
||||
path = node.value.args[0] if node.value.args else None
|
||||
literal = path.value if isinstance(path, ast.Constant) else None
|
||||
if isinstance(path, ast.JoinedStr):
|
||||
first = path.values[0] if path.values else None
|
||||
literal = first.value if isinstance(first, ast.Constant) else None
|
||||
if isinstance(literal, str) and literal.startswith(("/api", "/auth")):
|
||||
hits.append(node.lineno)
|
||||
return hits
|
||||
|
||||
|
||||
def test_the_detector_sees_a_discarded_request_and_passes_a_checked_one():
|
||||
src = (
|
||||
"client.post('/api/x')\n"
|
||||
"client.get(f'/auth/{u}')\n"
|
||||
"assert client.post('/api/x').status_code == 200\n"
|
||||
"r = client.get('/api/y')\n"
|
||||
"items.get('/api/not-a-request-but-a-key')\n"
|
||||
)
|
||||
# The last line is a false positive by design: a dict keyed by an app path is not a thing the
|
||||
# suite does, and a narrower match would miss a client held under any other name.
|
||||
assert discarded_requests(src) == [1, 2, 5]
|
||||
|
||||
|
||||
def test_no_test_discards_a_response():
|
||||
offenders = [
|
||||
f"{p.name}:{line}"
|
||||
for p in sorted(TESTS.rglob("test_*.py"))
|
||||
for line in discarded_requests(p.read_text(encoding="utf-8"))
|
||||
]
|
||||
assert offenders == [], (
|
||||
"assert the status these requests expect (`assert client.post(...).status_code == 200`), "
|
||||
f"or keep the response and check it: {offenders}"
|
||||
)
|
||||
@@ -100,15 +100,22 @@ def test_a_later_registration_never_touches_an_existing_password(db, client, smt
|
||||
have installed a password where the whole point is that there is none. Reverted; the owner's
|
||||
recourse is password-reset, which proves the mailbox. Residual logged as SEC-16.
|
||||
"""
|
||||
client.post(
|
||||
"/auth/register", json={"email": "victim@example.com", "password": "the first password"}
|
||||
assert (
|
||||
client.post(
|
||||
"/auth/register", json={"email": "victim@example.com", "password": "the first password"}
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
db.expire_all()
|
||||
user = db.execute(select(User).where(User.email == "victim@example.com")).scalar_one()
|
||||
before = user.password_hash
|
||||
|
||||
client.post(
|
||||
"/auth/register", json={"email": "victim@example.com", "password": "the second password"}
|
||||
assert (
|
||||
client.post(
|
||||
"/auth/register",
|
||||
json={"email": "victim@example.com", "password": "the second password"},
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
|
||||
db.expire_all()
|
||||
@@ -116,8 +123,11 @@ def test_a_later_registration_never_touches_an_existing_password(db, client, smt
|
||||
|
||||
|
||||
def test_a_later_registration_leaves_a_link_already_in_flight_alive(db, client, smtp_on, wire):
|
||||
client.post(
|
||||
"/auth/register", json={"email": "victim@example.com", "password": "the first password"}
|
||||
assert (
|
||||
client.post(
|
||||
"/auth/register", json={"email": "victim@example.com", "password": "the first password"}
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
db.expire_all()
|
||||
user = db.execute(select(User).where(User.email == "victim@example.com")).scalar_one()
|
||||
@@ -126,8 +136,12 @@ def test_a_later_registration_leaves_a_link_already_in_flight_alive(db, client,
|
||||
# would go green while asserting nothing the day registration stops issuing a token.
|
||||
assert len(first) == 1
|
||||
|
||||
client.post(
|
||||
"/auth/register", json={"email": "victim@example.com", "password": "the second password"}
|
||||
assert (
|
||||
client.post(
|
||||
"/auth/register",
|
||||
json={"email": "victim@example.com", "password": "the second password"},
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
|
||||
db.expire_all()
|
||||
@@ -148,8 +162,12 @@ def test_a_stripped_account_keeps_its_recovery_stamp(db, client, smtp_on, make_u
|
||||
password_revoked_at=datetime.now(timezone.utc),
|
||||
)
|
||||
|
||||
client.post(
|
||||
"/auth/register", json={"email": "stripped@example.com", "password": "an attacker password"}
|
||||
assert (
|
||||
client.post(
|
||||
"/auth/register",
|
||||
json={"email": "stripped@example.com", "password": "an attacker password"},
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
|
||||
db.expire_all()
|
||||
@@ -169,7 +187,9 @@ def test_a_denied_sibling_is_not_reopened_while_a_pending_row_exists(db, client)
|
||||
)
|
||||
db.commit()
|
||||
|
||||
client.post("/auth/request-access", json={"email": "pairone@gmail.com"})
|
||||
assert (
|
||||
client.post("/auth/request-access", json={"email": "pairone@gmail.com"}).status_code == 200
|
||||
)
|
||||
|
||||
db.expire_all()
|
||||
rows = {i.email: i.status for i in db.execute(select(Invite)).scalars().all()}
|
||||
@@ -195,7 +215,7 @@ def test_an_env_granted_address_cannot_be_shadowed_by_a_stranger(db, client, mon
|
||||
# env grant. With the env list matched canonically the request never opens a row.
|
||||
monkeypatch.setattr(settings, "allowed_emails", "bob@gmail.com")
|
||||
|
||||
client.post("/auth/request-access", json={"email": "b.ob@gmail.com"})
|
||||
assert client.post("/auth/request-access", json={"email": "b.ob@gmail.com"}).status_code == 200
|
||||
|
||||
assert db.execute(select(Invite)).scalars().all() == []
|
||||
assert auth_core.is_allowed(db, "bob@gmail.com") is True
|
||||
@@ -331,7 +351,12 @@ def test_two_admins_two_languages_two_mails(db, client, make_user, smtp_on, wire
|
||||
make_user(email="hu.admin@example.com", role="admin", preferences={"language": "hu"})
|
||||
make_user(email="en.admin@example.com", role="admin", preferences={"language": "en"})
|
||||
|
||||
client.post("/auth/request-access", json={"email": "asking@example.com", "lang": "en"})
|
||||
assert (
|
||||
client.post(
|
||||
"/auth/request-access", json={"email": "asking@example.com", "lang": "en"}
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
|
||||
notices = [w for w in wire if "asking@example.com" in w[1]]
|
||||
assert len(notices) == 2
|
||||
|
||||
@@ -53,8 +53,8 @@ def test_default_is_per_source(db, monkeypatch, override_dep):
|
||||
pid = c.post("/api/saved-views", json={"name": "P", "filters": {}, "source": "plex"}).json()[
|
||||
"id"
|
||||
]
|
||||
c.patch(f"/api/saved-views/{fid}", json={"is_default": True})
|
||||
c.patch(f"/api/saved-views/{pid}", json={"is_default": True})
|
||||
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
|
||||
|
||||
@@ -68,8 +68,12 @@ def test_using_the_link_evicts_whoever_held_the_old_password(db, as_user, make_u
|
||||
admin = make_user(role="admin")
|
||||
db.commit()
|
||||
token = _link(as_user(admin), target.id)
|
||||
client.post(
|
||||
"/auth/password-reset/confirm", json={"token": token, "password": "the owner's own choice"}
|
||||
assert (
|
||||
client.post(
|
||||
"/auth/password-reset/confirm",
|
||||
json={"token": token, "password": "the owner's own choice"},
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
|
||||
db.refresh(target)
|
||||
|
||||
@@ -229,7 +229,12 @@ def test_registering_again_does_not_create_a_second_account(db, client, monkeypa
|
||||
_smtp_on(monkeypatch)
|
||||
_capture(monkeypatch, "send_verify_email")
|
||||
for _ in range(2):
|
||||
client.post("/auth/register", json={"email": "twice@example.com", "password": _PW})
|
||||
assert (
|
||||
client.post(
|
||||
"/auth/register", json={"email": "twice@example.com", "password": _PW}
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
|
||||
db.expire_all()
|
||||
rows = db.execute(select(User).where(User.email == "twice@example.com")).scalars().all()
|
||||
|
||||
Reference in New Issue
Block a user