Files
heartbeat/tests/test_events_journal.py
T

111 lines
3.6 KiB
Python

"""Tests for the dedicated events journal (write path and read path)."""
import asyncio
import json
from hbd.server import journal
def _make_journal(tmp_path, **overrides):
cfg = {"journal_dir": str(tmp_path), "journal_file": "events.journal"}
cfg.update(overrides)
j = journal.MessageJournal(cfg)
assert asyncio.run(j.initialize())
return j
def _read_lines(tmp_path, name="events.journal"):
return (tmp_path / name).read_text(encoding="utf-8").splitlines()
EV1 = {"ts": 1000.0, "host": "h1", "level": "INFO", "service": None, "message": "host up"}
EV2 = {"ts": 2000.0, "host": "h2", "level": "CRITICAL", "service": "cpu", "message": "cpu high"}
def test_log_event_writes_one_json_line(tmp_path):
j = _make_journal(tmp_path)
asyncio.run(j.log_event(EV1))
asyncio.run(j.close())
lines = _read_lines(tmp_path)
assert len(lines) == 1
assert json.loads(lines[0]) == EV1
def test_log_event_appends_in_order(tmp_path):
j = _make_journal(tmp_path)
asyncio.run(j.log_event(EV1))
asyncio.run(j.log_event(EV2))
asyncio.run(j.close())
lines = _read_lines(tmp_path)
assert [json.loads(ln)["ts"] for ln in lines] == [1000.0, 2000.0]
def test_log_event_rotates_at_max_size(tmp_path):
# max_size fits one serialized event line (~77 bytes) but not two, so the
# second write triggers exactly one rotation
j = _make_journal(tmp_path, journal_max_size=120)
asyncio.run(j.log_event(EV1))
asyncio.run(j.log_event(EV2))
asyncio.run(j.close())
backups = list(tmp_path.glob("events.journal.*"))
assert len(backups) == 1
assert json.loads(backups[0].read_text().splitlines()[0]) == EV1
assert json.loads(_read_lines(tmp_path)[0]) == EV2
def test_log_event_noop_when_disabled(tmp_path):
j = journal.MessageJournal(
{"journal_dir": str(tmp_path), "journal_file": "events.journal", "journal_enabled": False}
)
asyncio.run(j.initialize())
asyncio.run(j.log_event(EV1))
assert not (tmp_path / "events.journal").exists()
def test_backfill_seeds_empty_journal(tmp_path):
j = _make_journal(tmp_path)
asyncio.run(j.backfill([EV1, EV2]))
asyncio.run(j.close())
lines = _read_lines(tmp_path)
assert len(lines) == 2
assert json.loads(lines[0]) == EV1
def test_backfill_skipped_when_journal_nonempty(tmp_path):
(tmp_path / "events.journal").write_text(json.dumps(EV1) + "\n")
j = _make_journal(tmp_path) # initialize() picks up the existing size
asyncio.run(j.backfill([EV2]))
asyncio.run(j.close())
assert len(_read_lines(tmp_path)) == 1
def test_get_events_journal_uses_events_config_keys(tmp_path):
journal._events_journal_instance = None
try:
ej = journal.get_events_journal(
{
"journal_dir": str(tmp_path),
"events_journal_file": "ev.jsonl",
"events_journal_max_size": 12345,
"events_journal_max_backups": 3,
}
)
assert ej.journal_file == "ev.jsonl"
assert ej.max_size == 12345
assert ej.max_backups == 3
assert ej.journal_dir == tmp_path
# singleton: second call returns the same instance
assert journal.get_events_journal() is ej
finally:
journal._events_journal_instance = None
def test_get_events_journal_defaults(tmp_path):
journal._events_journal_instance = None
try:
ej = journal.get_events_journal({"journal_dir": str(tmp_path)})
assert ej.journal_file == "events.journal"
assert ej.max_size == 10 * 1024 * 1024
assert ej.max_backups == 10
finally:
journal._events_journal_instance = None