8Regression tests for :mod:`oct.mcp.audit` — ``McpAuditRecord`` and
13- Verify JSONL write and rotation (size and count thresholds).
14- Verify pruning of old audit files.
15- Verify session/request ID propagation and redaction flag.
22 L3 — assertion details
27- All audit files are written to ``tmp_path``; no real logs directory
31from __future__
import annotations
35from pathlib
import Path
41 MCP_AUDIT_MAX_SIZE_BYTES,
56 assert r.timestamp ==
""
57 assert r.session_id ==
""
58 assert r.request_id ==
""
61 assert r.args_redacted
is True
62 assert r.decision ==
""
63 assert r.policy_rule ==
""
64 assert r.exit_code
is None
65 assert r.duration_ms == 0
66 assert r.output_size_bytes == 0
67 assert r.output_truncated
is False
68 assert r.output_hash_sha256 ==
""
69 assert r.redactions_applied == 0
71 assert r.project_root_hash ==
""
75 timestamp=
"2026-01-01T00:00:00Z",
81 policy_rule=
"manifest_auto_approve",
84 output_size_bytes=100,
87 project_root_hash=
"abcd1234",
89 from dataclasses
import asdict
90 line = json.dumps(asdict(r))
91 assert "oct_lint" in line
101 assert ts.endswith(
"Z")
105 h = _hash_project_root(tmp_path)
109 h = _hash_project_root(tmp_path)
113 h1 = _hash_project_root(tmp_path)
114 h2 = _hash_project_root(tmp_path)
122 assert _hash_project_root(a) != _hash_project_root(b)
131 audit_dir = tmp_path /
"audit_dir"
135 assert audit_dir.exists()
138 log = tmp_path /
"audit.log"
140 logger.write(
McpAuditRecord(tool=
"oct_lint", timestamp=_utc_now_iso()))
144 log = tmp_path /
"audit.log"
146 logger.write(
McpAuditRecord(tool=
"oct_health", timestamp=_utc_now_iso()))
147 lines = log.read_text(encoding=
"utf-8").strip().splitlines()
148 assert len(lines) == 1
149 record = json.loads(lines[0])
150 assert record[
"tool"] ==
"oct_health"
153 log = tmp_path /
"audit.log"
156 logger.write(
McpAuditRecord(tool=f
"oct_lint_{i}", timestamp=_utc_now_iso()))
157 lines = log.read_text(encoding=
"utf-8").strip().splitlines()
158 assert len(lines) == 5
161 log = tmp_path /
"audit.log"
165 session_id=
"sess-abc",
166 request_id=
"req-xyz",
167 timestamp=_utc_now_iso(),
169 record = json.loads(log.read_text(encoding=
"utf-8").strip())
170 assert record[
"session_id"] ==
"sess-abc"
171 assert record[
"request_id"] ==
"req-xyz"
174 log = tmp_path /
"audit.log"
178 args={
"paths": [
"src/"]},
180 timestamp=_utc_now_iso(),
182 record = json.loads(log.read_text(encoding=
"utf-8").strip())
183 assert record[
"args_redacted"]
is True
186 log = tmp_path /
"audit.log"
190 record = json.loads(log.read_text(encoding=
"utf-8").strip())
191 assert record[
"timestamp"].endswith(
"Z")
194 log = tmp_path /
"audit.log"
196 assert logger.current_path
is None
197 logger.write(
McpAuditRecord(tool=
"oct_lint", timestamp=_utc_now_iso()))
198 assert logger.current_path
is not None
204 logger.write(
McpAuditRecord(tool=
"oct_lint", timestamp=_utc_now_iso()))
213 log = tmp_path /
"audit.log"
219 logger.write(
McpAuditRecord(tool=
"oct_lint", timestamp=_utc_now_iso()))
220 path_after_first = logger.current_path
222 logger.write(
McpAuditRecord(tool=
"oct_health", timestamp=_utc_now_iso()))
223 path_after_second = logger.current_path
224 assert path_after_second != path_after_first
227 log = tmp_path /
"audit.log"
233 logger.write(
McpAuditRecord(tool=
"oct_lint", timestamp=_utc_now_iso()))
235 rotated = list(tmp_path.glob(
"mcp-audit-*.jsonl"))
236 assert len(rotated) >= 1
245 log = tmp_path /
"audit.log"
253 for i
in range(max_files + 5):
254 logger.write(
McpAuditRecord(tool=
"oct_lint", timestamp=_utc_now_iso()))
257 rotated = list(tmp_path.glob(
"mcp-audit-*.jsonl"))
258 assert len(rotated) <= max_files
261 log = tmp_path /
"subdir" /
"audit.log"
263 assert logger.audit_dir == tmp_path /
"subdir"
test_utc_now_iso_format(self)
test_hash_project_root_stable(self, Path tmp_path)
test_hash_project_root_hex(self, Path tmp_path)
test_hash_different_roots_differ(self, Path tmp_path)
test_hash_project_root_length(self, Path tmp_path)
test_pruning_keeps_max_files(self, Path tmp_path)
test_audit_dir_property(self, Path tmp_path)
test_rotated_files_use_mcp_audit_prefix(self, Path tmp_path)
test_rotation_creates_new_file(self, Path tmp_path)
test_writes_jsonl_line(self, Path tmp_path)
test_creates_audit_dir(self, Path tmp_path)
test_current_path_set_after_write(self, Path tmp_path)
test_timestamp_auto_filled_when_empty(self, Path tmp_path)
test_args_redacted_flag(self, Path tmp_path)
test_creates_audit_log_file(self, Path tmp_path)
test_multiple_writes_multiple_lines(self, Path tmp_path)
test_session_id_preserved(self, Path tmp_path)
test_never_raises_on_bad_dir(self)
test_all_fields_json_serialisable(self)