fix(test): avoid autobegin-vs-begin collision in the log toggle/exclude test [M79 step 5]
CI / lint (push) Waiting to run
CI / unit (push) Waiting to run
CI / integration (push) Waiting to run
CI / publish (push) Blocked by required conditions

test_persist_logs_respects_toggle_and_exclude read via s.execute(SELECT) between
two `async with s.begin()` blocks; that SELECT autobegins a transaction, so the
next begin() raised "A transaction is already begun". Move both reads after the
final begin block (the toggle-off persist stores nothing, so end-state still
reflects the exclude phase), and pass a SimpleNamespace stand-in host to sidestep
ORM attribute-expiry across the commit boundaries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAGR73DUowdVFVvYzLXC5C
This commit is contained in:
2026-07-19 19:04:25 -04:00
parent 8f1c8c5cf7
commit 414c8efa98
+22 -13
View File
@@ -214,6 +214,7 @@ def test_persist_logs_stores_lines(app):
def test_persist_logs_respects_toggle_and_exclude(app):
"""Server-side controls (Settings): an excluded container's lines are dropped
while others persist; the global toggle off stores nothing new."""
from types import SimpleNamespace
from sqlalchemy import text
from steward.models.hosts import Host
from steward.core.settings import set_setting
@@ -225,33 +226,41 @@ def test_persist_logs_respects_toggle_and_exclude(app):
{"container": "noisy", "stream": "stdout", "ts": None, "line": "drop-me"},
])]
hid = str(uuid.uuid4())
# Stand-in host: the logs-only persist path reads only host.id, so a plain
# object sidesteps ORM attribute-expiry across the commit boundaries below.
host = SimpleNamespace(id=hid, name="loghost3")
async def _go():
async with app.db_sessionmaker() as s:
# Back-to-back begin blocks with NO read in between — a SELECT between
# them would autobegin a txn and make the next begin() collide.
async with s.begin():
await s.execute(text("DELETE FROM docker_logs"))
await set_setting(s, "docker.logs.enabled", True)
await set_setting(s, "docker.logs.exclude", ["noisy"])
h = Host(id=str(uuid.uuid4()), name="loghost3", address="10.7.7.10")
s.add(h)
await s.flush()
hid = h.id
await persist(s, h, [], None, None, batch)
kept = {r[0] for r in (await s.execute(text(
"SELECT DISTINCT container_name FROM docker_logs WHERE host_id=:h"),
{"h": hid})).all()}
async with s.begin(): # flip the global kill-switch off
s.add(Host(id=hid, name="loghost3", address="10.7.7.10"))
await s.flush() # host row before its log rows (FK order)
await persist(s, host, [], None, None, batch) # web kept, noisy excluded
async with s.begin(): # global kill-switch off → nothing stored
await set_setting(s, "docker.logs.enabled", False)
await persist(s, h, [], None, None, batch)
after_off = (await s.execute(text(
"SELECT COUNT(*) FROM docker_logs WHERE host_id=:h"), {"h": hid})).scalar()
await persist(s, host, [], None, None, batch)
async with s.begin(): # restore defaults for other tests
await set_setting(s, "docker.logs.enabled", True)
await set_setting(s, "docker.logs.exclude", [])
# Reads only after the last begin block (they autobegin, but nothing
# opens a begin() after them). Toggle-off added nothing, so the state
# here still reflects the exclude phase.
kept = {r[0] for r in (await s.execute(text(
"SELECT DISTINCT container_name FROM docker_logs WHERE host_id=:h"),
{"h": hid})).all()}
after_off = (await s.execute(text(
"SELECT COUNT(*) FROM docker_logs WHERE host_id=:h"), {"h": hid})).scalar()
return kept, after_off
kept, after_off = asyncio.run(_go())
assert kept == {"web"} # excluded 'noisy' dropped on ingest
assert after_off == 1 # toggle off → no new rows (still just the one 'web')
assert after_off == 1 # toggle off added nothing → still just the one 'web' line
@_NEEDS_DB