"""Tests for the cron usage_audit.jsonl logger. Covers: - successful write produces a single valid JSONL line with full schema - missing token info still writes a line with null fields - writer exception is swallowed (json.dumps raises) — call must return cleanly - file path is created if parent dir is missing - timestamp format is RFC3339 UTC with millisecond precision and 'Z' suffix - path resolves through _get_hermes_home() (profile-safe) """ from __future__ import annotations import json import re from pathlib import Path from unittest.mock import patch import pytest from cron import scheduler @pytest.fixture def tmp_hermes_home(tmp_path, monkeypatch): """Redirect _get_hermes_home() so the audit logger writes under tmp_path.""" fake_home = tmp_path / "home" / ".hermes" fake_home.mkdir(parents=True) monkeypatch.setattr(scheduler, "_get_hermes_home", lambda: fake_home) return fake_home def _read_jsonl(path: Path) -> list[dict]: return [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line] class TestUsageAuditPath: def test_resolves_through_get_hermes_home(self, tmp_hermes_home): p = scheduler._usage_audit_path() assert p == tmp_hermes_home / "cron" / "usage_audit.jsonl" def test_does_not_use_path_home(self, tmp_hermes_home): """Audit path must NOT hardcode Path.home() — it bypasses profile-aware resolution.""" with patch.object(Path, "home") as mock_home: p = scheduler._usage_audit_path() mock_home.assert_not_called() assert p == tmp_hermes_home / "cron" / "usage_audit.jsonl" class TestUtcnowIsoMs: def test_format_has_millisecond_precision_and_z(self): ts = scheduler._utcnow_iso_ms() # YYYY-MM-DDTHH:MM:SS.mmmZ assert re.match(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$", ts), ts class TestWriteUsageAudit: def test_successful_write_produces_valid_jsonl(self, tmp_hermes_home): record = { "ts": "2026-05-01T04:23:11.123Z", "job_id": "bluenode-dispatch-recommend-sweep", "fire_id": "deadbeefcafe", "prompt_tokens": 11894, "completion_tokens": 287, "total_tokens": 12181, "response_silent": False, "deliver_target": None, "model": "google/gemma-4-31b-it", "duration_ms": 4231, "error": None, } scheduler._write_usage_audit(record) path = scheduler._usage_audit_path() assert path.exists() lines = _read_jsonl(path) assert len(lines) == 1 assert lines[0] == record def test_missing_token_info_writes_line_with_null_fields(self, tmp_hermes_home): record = { "ts": "2026-05-01T04:23:11.123Z", "job_id": "j", "fire_id": "f", "prompt_tokens": None, "completion_tokens": None, "total_tokens": None, "response_silent": True, "deliver_target": "telegram", "model": None, "duration_ms": 12, "error": "boom", } scheduler._write_usage_audit(record) lines = _read_jsonl(scheduler._usage_audit_path()) assert len(lines) == 1 assert lines[0]["prompt_tokens"] is None assert lines[0]["completion_tokens"] is None assert lines[0]["total_tokens"] is None assert lines[0]["error"] == "boom" def test_writer_exception_swallowed(self, tmp_hermes_home, caplog): # Force json.dumps to raise — writer must NOT propagate. with patch("cron.scheduler.json.dumps", side_effect=RuntimeError("kaboom")): scheduler._write_usage_audit({"job_id": "x"}) # File never created. assert not scheduler._usage_audit_path().exists() # Warning logged with our marker. assert any("usage_audit write failed" in rec.message for rec in caplog.records) def test_parent_dir_created_if_missing(self, tmp_hermes_home): # Ensure the cron path does not exist yet. target = tmp_hermes_home / "cron" assert not target.exists() scheduler._write_usage_audit({"k": "v"}) assert target.exists() and target.is_dir() assert (target / "usage_audit.jsonl").exists() def test_appends_multiple_records(self, tmp_hermes_home): scheduler._write_usage_audit({"i": 1}) scheduler._write_usage_audit({"i": 2}) scheduler._write_usage_audit({"i": 3}) lines = _read_jsonl(scheduler._usage_audit_path()) assert [r["i"] for r in lines] == [1, 2, 3] def test_unicode_preserved_not_escaped(self, tmp_hermes_home): # ensure_ascii=False so non-ASCII model names / job names round-trip cleanly. scheduler._write_usage_audit({"job_id": "한글", "model": "gemma"}) text = scheduler._usage_audit_path().read_text(encoding="utf-8") assert "한글" in text