Files
siftlode/backend/tests/test_override_dep_convention.py
T
peter e27656ee7e test: the request commit check sees Core DML and row locks
A db.execute(update/delete) fires no flush and stages nothing, so a handler whose
only write was Core DML could forget its commit and stay green. The check now
asks Postgres whether the request opened a writing transaction. It found one
real case: a repeated issue send returned with the session row still locked.
2026-10-20 00:45:20 +02:00

116 lines
5.0 KiB
Python

"""Dependency overrides go through `conftest.override_dep`, never `app.dependency_overrides` directly.
The hand-rolled shape — assign, then `app.dependency_overrides.clear()` in a `finally` — wipes EVERY
override, not the ones the test set, so it stops fixtures composing: one test's teardown silently
clobbers an override another fixture installed. `override_dep` restores each key's prior value
instead. 22 files had drifted to the hand-rolled shape (it was the house norm at ~20 files to 3), and
the sweep that converted them is only as good as the check that keeps it converted — this one.
"""
from pathlib import Path
TESTS = Path(__file__).parent
def test_no_test_writes_dependency_overrides_directly():
offenders = [
f"{p.name}:{n}"
for p in sorted(TESTS.rglob("*.py"))
if p.name not in {"conftest.py", Path(__file__).name}
for n, line in enumerate(p.read_text(encoding="utf-8").splitlines(), 1)
if "dependency_overrides" in line
]
assert offenders == [], "use the override_dep fixture: " + ", ".join(offenders)
def test_a_request_gets_the_session_that_checks_its_commit():
"""`get_db` is overridden with `db.info["request"]` — the test session plus the check that the
handler committed what it flushed (conftest `_request_dep`). A bare `lambda: db` hands the route
the same session without the check, and a handler that forgot its commit passes again."""
offenders = [
f"{p.name}:{n}"
for p in sorted(TESTS.rglob("*.py"))
if p.name != Path(__file__).name
for n, line in enumerate(p.read_text(encoding="utf-8").splitlines(), 1)
if "override_dep(get_db," in line and 'override_dep(get_db, db.info["request"])' not in line
]
assert offenders == [], 'override get_db with db.info["request"]: ' + ", ".join(offenders)
def test_the_commit_check_counts_only_the_root_transaction(db):
"""A savepoint released inside a handler commits nothing to the database; ending it must not
clear the check. Driven directly, as a request would drive the dependency."""
import pytest
from starlette.requests import HTTPConnection
from app.models import User
conn = HTTPConnection(
{"type": "http", "method": "POST", "path": "/probe", "headers": [], "query_string": b""}
)
request = db.info["request"](conn)
session = next(request)
session.add(User(email="savepoint-probe@example.com"))
session.flush()
with session.begin_nested():
pass
with pytest.raises(StopIteration):
next(request)
assert db.info["unfinished"] == ["POST /probe"]
db.info["unfinished"].clear() # recorded as it should be — not this test's failure
def test_a_request_that_raises_leaves_nothing_it_did_not_commit(db):
"""Production closes a failing request's session, which rolls back whatever it flushed and did
not commit; the shared test session must do the same, or a later read in the test sees a write
production would have lost (the tracker's issue link, kept only by an explicit commit in
the handler's failure path). Driven directly, as a request would drive the dependency."""
import pytest
from sqlalchemy import select
from starlette.requests import HTTPConnection
from app.models import User
conn = HTTPConnection(
{"type": "http", "method": "POST", "path": "/probe", "headers": [], "query_string": b""}
)
request = db.info["request"](conn)
session = next(request)
session.add(User(email="rolled-back-probe@example.com"))
session.flush()
with pytest.raises(RuntimeError):
request.throw(RuntimeError("the handler failed"))
assert db.scalar(select(User).where(User.email == "rolled-back-probe@example.com")) is None
assert db.info["unfinished"] == [] # a failing request is rolled back, not reported
def test_the_commit_check_sees_a_core_dml_write(db, make_user):
"""`db.execute(update(...))` flushes nothing and stages nothing, so the session's own bookkeeping
never sees it: a handler whose only write is Core DML and that forgot its commit passed. The
database's transaction id is what tells. A committed one must still pass."""
import pytest
from sqlalchemy import update
from starlette.requests import HTTPConnection
from app.models import User
user = make_user()
def drive(commit: bool) -> None:
conn = HTTPConnection(
{"type": "http", "method": "POST", "path": "/probe", "headers": [], "query_string": b""}
)
request = db.info["request"](conn)
session = next(request)
session.execute(update(User).where(User.id == user.id).values(display_name="dml"))
if commit:
session.commit()
with pytest.raises(StopIteration):
next(request)
drive(commit=True)
assert db.info["unfinished"] == []
drive(commit=False)
assert db.info["unfinished"] == ["POST /probe"]
db.info["unfinished"].clear() # recorded as it should be — not this test's failure