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): def test_persist_logs_respects_toggle_and_exclude(app):
"""Server-side controls (Settings): an excluded container's lines are dropped """Server-side controls (Settings): an excluded container's lines are dropped
while others persist; the global toggle off stores nothing new.""" while others persist; the global toggle off stores nothing new."""
from types import SimpleNamespace
from sqlalchemy import text from sqlalchemy import text
from steward.models.hosts import Host from steward.models.hosts import Host
from steward.core.settings import set_setting 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"}, {"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 def _go():
async with app.db_sessionmaker() as s: 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(): async with s.begin():
await s.execute(text("DELETE FROM docker_logs")) await s.execute(text("DELETE FROM docker_logs"))
await set_setting(s, "docker.logs.enabled", True) await set_setting(s, "docker.logs.enabled", True)
await set_setting(s, "docker.logs.exclude", ["noisy"]) await set_setting(s, "docker.logs.exclude", ["noisy"])
h = Host(id=str(uuid.uuid4()), name="loghost3", address="10.7.7.10") s.add(Host(id=hid, name="loghost3", address="10.7.7.10"))
s.add(h) await s.flush() # host row before its log rows (FK order)
await s.flush() await persist(s, host, [], None, None, batch) # web kept, noisy excluded
hid = h.id async with s.begin(): # global kill-switch off → nothing stored
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
await set_setting(s, "docker.logs.enabled", False) await set_setting(s, "docker.logs.enabled", False)
await persist(s, h, [], None, None, batch) await persist(s, host, [], None, None, batch)
after_off = (await s.execute(text(
"SELECT COUNT(*) FROM docker_logs WHERE host_id=:h"), {"h": hid})).scalar()
async with s.begin(): # restore defaults for other tests async with s.begin(): # restore defaults for other tests
await set_setting(s, "docker.logs.enabled", True) await set_setting(s, "docker.logs.enabled", True)
await set_setting(s, "docker.logs.exclude", []) 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 return kept, after_off
kept, after_off = asyncio.run(_go()) kept, after_off = asyncio.run(_go())
assert kept == {"web"} # excluded 'noisy' dropped on ingest 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 @_NEEDS_DB