test(asqav): cover blank and corrupt log lines, null latency and build errors

Six tests for edge cases the first commit left untested: verify skips
blank lines and reports a non-JSON line, a restart on a blank file
starts a fresh chain, a restart after a corrupt last line keeps logging
while verify flags the corrupt line, latency is null without datetime
timestamps, and an unexpected error while building a record neither
raises nor advances the chain. Patch coverage for asqav.py goes from
94% to 99%; the remaining line is the unreachable return after the
tail-read loop.
This commit is contained in:
jagmarques 2026-10-25 09:20:03 +01:00
parent 56a5f5d671
commit 8fae48e4ba
No known key found for this signature in database

View file

@ -108,6 +108,14 @@ def test_record_carries_usage_latency_and_call_metadata(tmp_path: Path) -> None:
assert record["finish_reason"] == "stop"
def test_latency_is_null_when_times_are_not_datetimes(tmp_path: Path) -> None:
path = tmp_path / "audit.jsonl"
_logger(path).log_success_event(_kwargs(), _response(), None, None)
[record] = _records(path)
assert record["latency_ms"] is None
def test_verify_detects_an_edited_field(tmp_path: Path) -> None:
path = tmp_path / "audit.jsonl"
logger = _logger(path)
@ -154,6 +162,29 @@ def test_verify_reports_a_missing_file(tmp_path: Path) -> None:
assert _logger(tmp_path / "audit.jsonl").verify_chain(str(missing)) == (False, f"log file not found: {missing}")
def test_verify_skips_blank_lines(tmp_path: Path) -> None:
path = tmp_path / "audit.jsonl"
logger = _logger(path)
_append(logger, 2)
first, second = path.read_text(encoding="utf-8").splitlines()
path.write_text(f"{first}\n\n{second}\n", encoding="utf-8")
assert logger.verify_chain() == (True, "ok")
def test_verify_reports_a_line_that_is_not_json(tmp_path: Path) -> None:
path = tmp_path / "audit.jsonl"
logger = _logger(path)
_append(logger, 1)
with path.open("a", encoding="utf-8") as fh:
fh.write("not json\n")
ok, reason = logger.verify_chain()
assert ok is False
assert reason.startswith("verification error:")
def test_chain_and_seq_resume_after_restart(tmp_path: Path) -> None:
path = tmp_path / "audit.jsonl"
_append(_logger(path), 5, prefix="first")
@ -184,6 +215,35 @@ def test_restart_resumes_when_last_record_exceeds_tail_window(tmp_path: Path) ->
assert restarted.verify_chain() == (True, "ok")
def test_restart_on_a_blank_file_starts_a_new_chain(tmp_path: Path) -> None:
path = tmp_path / "audit.jsonl"
path.write_text("\n\n", encoding="utf-8")
restarted = _logger(path)
_append(restarted, 1)
[record] = _records(path)
assert (record["seq"], record["prev_hash"]) == (0, GENESIS_HASH)
assert restarted.verify_chain() == (True, "ok")
def test_restart_after_a_corrupt_last_line_keeps_logging_and_verify_flags_it(tmp_path: Path) -> None:
path = tmp_path / "audit.jsonl"
_append(_logger(path), 1)
with path.open("a", encoding="utf-8") as fh:
fh.write("{truncated\n")
restarted = _logger(path)
_append(restarted, 1, prefix="after")
lines = path.read_text(encoding="utf-8").splitlines()
assert len(lines) == 3
assert json.loads(lines[2])["messages_digest"] is not None
ok, reason = restarted.verify_chain()
assert ok is False
assert reason.startswith("verification error:")
def test_failure_event_is_logged(tmp_path: Path) -> None:
path = tmp_path / "audit.jsonl"
_logger(path).log_failure_event(_kwargs(), None, START, END)
@ -218,6 +278,24 @@ def test_failed_write_does_not_raise_or_advance_the_chain(tmp_path: Path) -> Non
assert record["prev_hash"] == GENESIS_HASH
def test_unexpected_error_while_building_does_not_raise_or_advance_the_chain(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
path = tmp_path / "audit.jsonl"
logger = _logger(path)
def _explode(_record: Mapping[str, object]) -> bool:
raise RuntimeError("boom")
monkeypatch.setattr(logger, "_write_record", _explode)
logger.log_success_event(_kwargs(call_id="call-dropped"), _response(), START, END)
monkeypatch.undo()
logger.log_success_event(_kwargs(call_id="call-kept"), _response(), START, END)
[record] = _records(path)
assert (record["call_id"], record["seq"], record["prev_hash"]) == ("call-kept", 0, GENESIS_HASH)
def test_content_is_digested_by_default(tmp_path: Path) -> None:
path = tmp_path / "audit.jsonl"
_logger(path).log_success_event(_kwargs(content="my private prompt"), _response("my private answer"), START, END)