test: a request that raises is rolled back, as production rolls it back
The shared test session kept whatever a failing handler flushed and never committed, so a later read in the test saw a write production loses when get_db closes the session — and a handler whose failure path must commit (E16: the tracker's issue link) looked right without the commit.
This commit is contained in:
@@ -111,8 +111,11 @@ def _request_dep(db, unfinished: list[str]):
|
|||||||
request that ends normally with a write it did not commit — flushed, or still only staged — is
|
request that ends normally with a write it did not commit — flushed, or still only staged — is
|
||||||
recorded, and the test fails on it at teardown. Only the ROOT transaction's end clears it: a
|
recorded, and the test fails on it at teardown. Only the ROOT transaction's end clears it: a
|
||||||
savepoint released inside the handler commits nothing. The test's own staged rows are flushed before the request starts — they are the
|
savepoint released inside the handler commits nothing. The test's own staged rows are flushed before the request starts — they are the
|
||||||
test's business, not the handler's. A request that raises is exempt: its writes are meant to
|
test's business, not the handler's. A request that raises is rolled back, as production's
|
||||||
roll back."""
|
`close()` rolls it back: without that, what a failing handler flushed but never committed stayed
|
||||||
|
readable to the rest of the test — the one state production cannot be in (E16: an issue created
|
||||||
|
on the tracker before a failed upload was kept only by an explicit commit, and nothing saw it
|
||||||
|
missing)."""
|
||||||
|
|
||||||
def dep(conn: HTTPConnection):
|
def dep(conn: HTTPConnection):
|
||||||
db.flush()
|
db.flush()
|
||||||
@@ -133,6 +136,9 @@ def _request_dep(db, unfinished: list[str]):
|
|||||||
staged = db.new or db.deleted or any(db.is_modified(o) for o in db.dirty)
|
staged = db.new or db.deleted or any(db.is_modified(o) for o in db.dirty)
|
||||||
if state["dirty"] or staged:
|
if state["dirty"] or staged:
|
||||||
unfinished.append(f"{conn.scope.get('method', 'WS')} {conn.url.path}")
|
unfinished.append(f"{conn.scope.get('method', 'WS')} {conn.url.path}")
|
||||||
|
except Exception:
|
||||||
|
db.rollback()
|
||||||
|
raise
|
||||||
finally:
|
finally:
|
||||||
for name, fn in hooks:
|
for name, fn in hooks:
|
||||||
event.remove(db, name, fn)
|
event.remove(db, name, fn)
|
||||||
|
|||||||
@@ -58,3 +58,27 @@ def test_the_commit_check_counts_only_the_root_transaction(db):
|
|||||||
next(request)
|
next(request)
|
||||||
assert db.info["unfinished"] == ["POST /probe"]
|
assert db.info["unfinished"] == ["POST /probe"]
|
||||||
db.info["unfinished"].clear() # recorded as it should be — not this test's failure
|
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 (E16: 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
|
||||||
|
|||||||
Reference in New Issue
Block a user