Snapshot of D:\Ecw Process 65c92c5 (2026-10-30). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
130 lines
4.0 KiB
Python
130 lines
4.0 KiB
Python
r"""
|
|
core/run_events.py — every event also goes to `dbo.CV_RunEvent` (operator,
|
|
2026-10-30): the Portal on Dokploy cannot read the bot PC's Logs folder, so
|
|
its Runs page showed "No runs match" for every date.
|
|
|
|
The file stays the record of a Run; the database row is a copy. A database
|
|
that is off, missing the table or down never stops a Run — after the first
|
|
failure the copy is switched off for the rest of the Run, said once in the
|
|
log.
|
|
|
|
python tests\test_run_events_db.py
|
|
|
|
No database: the writer is a stand-in.
|
|
"""
|
|
|
|
import io
|
|
import sys
|
|
import json
|
|
import pathlib
|
|
import tempfile
|
|
import contextlib
|
|
|
|
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent / "src"))
|
|
|
|
from ecw_bot import settings as cfg # noqa: E402
|
|
from ecw_bot.core import run_events # noqa: E402
|
|
|
|
CASES = []
|
|
|
|
|
|
def case(fn):
|
|
CASES.append(fn)
|
|
return fn
|
|
|
|
|
|
def _start(tmp, writer):
|
|
run_events._db_writer = writer
|
|
log = pathlib.Path(tmp) / "run_20261030_101500.log"
|
|
run_events.start(log, "VERIFY")
|
|
return log
|
|
|
|
|
|
@case
|
|
def every_event_is_copied_with_its_run_id_and_kind(tmp):
|
|
rows = []
|
|
_start(tmp, lambda run_id, at, kind, body: rows.append(
|
|
(run_id, kind, json.loads(body))))
|
|
run_events.emit("claim_start", n=1, total=2, claim="9103000")
|
|
run_events.close()
|
|
assert [k for _, k, _ in rows] == ["run_start", "claim_start"], rows
|
|
assert {r for r, _, _ in rows} == {"20261030_101500"}
|
|
assert rows[1][2]["claim"] == "9103000" and rows[1][2]["t"], rows
|
|
|
|
|
|
@case
|
|
def the_file_and_the_row_carry_the_same_line(tmp):
|
|
bodies = []
|
|
log = _start(tmp, lambda run_id, at, kind, body: bodies.append(body))
|
|
run_events.emit("warning", code="x", message="y")
|
|
run_events.close()
|
|
lines = run_events.path_for(log).read_text(encoding="utf-8").splitlines()
|
|
assert lines == bodies, (lines, bodies)
|
|
|
|
|
|
@case
|
|
def a_database_error_switches_the_copy_off_and_the_run_goes_on(tmp):
|
|
calls = []
|
|
|
|
def boom(*a):
|
|
calls.append(a)
|
|
raise OSError("no such table")
|
|
out = io.StringIO()
|
|
with contextlib.redirect_stdout(out):
|
|
log = _start(tmp, boom)
|
|
run_events.emit("claim_start", n=1)
|
|
run_events.emit("claim_start", n=2)
|
|
run_events.close()
|
|
assert len(calls) == 1, "tried once, then off"
|
|
assert out.getvalue().count("[EVENTS]") == 1, out.getvalue()
|
|
assert len(run_events.path_for(log).read_text().splitlines()) == 3
|
|
|
|
|
|
@case
|
|
def an_event_added_after_a_stop_is_copied_too(tmp):
|
|
rows = []
|
|
run_events._db_writer = lambda run_id, at, kind, body: rows.append(kind)
|
|
log = pathlib.Path(tmp) / "run_20261030_101500.log"
|
|
run_events.append_to(log, "stopped", by="sam")
|
|
assert rows == ["stopped"], rows
|
|
|
|
|
|
@case
|
|
def a_run_outside_the_logs_folder_is_never_copied(tmp):
|
|
rows = []
|
|
run_events._db_writer = lambda *a: rows.append(a)
|
|
other = pathlib.Path(tmp) / "elsewhere"
|
|
other.mkdir()
|
|
run_events.start(other / "run_20261030_101500.log", "VERIFY")
|
|
run_events.emit("claim_start", n=1)
|
|
run_events.close()
|
|
run_events.append_to(other / "run_20261030_101500.log", "stopped")
|
|
assert rows == [], "a test's run never reaches the database"
|
|
|
|
|
|
def main() -> int:
|
|
failures = 0
|
|
real = run_events._db_writer
|
|
for fn in CASES:
|
|
name = fn.__name__.replace("_", " ")
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
cfg.LOGS = pathlib.Path(tmp) # a real-looking Logs folder
|
|
try:
|
|
fn(tmp)
|
|
print(f" ok {name}")
|
|
except AssertionError as exc:
|
|
print(f" FAIL {name}: {exc}")
|
|
failures += 1
|
|
except Exception as exc:
|
|
print(f" ERROR {name}: {type(exc).__name__}: {exc}")
|
|
failures += 1
|
|
finally:
|
|
run_events._reset()
|
|
run_events._db_writer = real
|
|
print(f"\n{len(CASES) - failures}/{len(CASES)} passed")
|
|
return 1 if failures else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|