fix(test): avoid autobegin-vs-begin collision in the log toggle/exclude test [M79 step 5]
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:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user