"""Integration tests: AuditToolMiddleware + make_audit_recorder → audit.jsonl.""" from __future__ import annotations from pathlib import Path from typing import Any from unittest.mock import AsyncMock, MagicMock import pytest from my_deepagent.audit import make_audit_recorder, read_audit_records from my_deepagent.middleware.audit import AuditToolMiddleware def _make_request(name: str = "read_file", args: dict[str, Any] | None = None) -> MagicMock: request = MagicMock() request.tool_call = {"name": name, "args": args or {"path": "x.py"}} return request # --------------------------------------------------------------------------- # Success path: record is written to audit.jsonl # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_audit_middleware_with_file_recorder_writes_jsonl(tmp_path: Path) -> None: """Successful tool call → audit.jsonl gets one record with expected fields.""" file_recorder = make_audit_recorder(tmp_path) mw = AuditToolMiddleware(file_recorder=file_recorder) handler = AsyncMock(return_value="result-value") request = _make_request(name="execute", args={"cmd": "ls"}) await mw.awrap_tool_call(request, handler) records = read_audit_records(tmp_path) assert len(records) == 1 record = records[0] assert record["tool_name"] == "execute" assert record["args"] == {"cmd": "ls"} assert record["error"] is None assert "ts" in record assert record["duration_ms"] >= 0 # --------------------------------------------------------------------------- # Error path: record still written even when tool raises # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_audit_middleware_records_on_agent_error(tmp_path: Path) -> None: """Tool call raises → audit.jsonl still gets a record with error field set.""" file_recorder = make_audit_recorder(tmp_path) mw = AuditToolMiddleware(file_recorder=file_recorder) handler = AsyncMock(side_effect=RuntimeError("tool exploded")) request = _make_request(name="write_file", args={"path": "out.txt", "content": "x"}) with pytest.raises(RuntimeError, match="tool exploded"): await mw.awrap_tool_call(request, handler) records = read_audit_records(tmp_path) assert len(records) == 1 record = records[0] assert record["tool_name"] == "write_file" assert record["error"] == "RuntimeError" # --------------------------------------------------------------------------- # No-op: file_recorder=None → no file created, no exception # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_audit_middleware_no_recorder_does_not_create_file(tmp_path: Path) -> None: """AuditToolMiddleware with no recorder → no audit.jsonl created, no exception.""" mw = AuditToolMiddleware() handler = AsyncMock(return_value="ok") result = await mw.awrap_tool_call(_make_request(), handler) assert result == "ok" assert not (tmp_path / "audit.jsonl").exists()