Coverage for tests / unit / ai / test_audit.py: 100%
39 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Tests for the AI audit log writer."""
3from __future__ import annotations
5import json
6from pathlib import Path
8import pytest
9from assertpy import assert_that
11from lintro.ai.audit import AUDIT_DIR, AUDIT_FILE, write_audit_log
12from lintro.ai.models import AIFixSuggestion
15@pytest.fixture
16def suggestion() -> AIFixSuggestion:
17 """A sample AIFixSuggestion for testing."""
18 return AIFixSuggestion(
19 file="src/app.py",
20 line=42,
21 code="E501",
22 tool_name="ruff",
23 original_code="x = 1",
24 suggested_code="x = 2",
25 diff="--- a\n+++ b\n",
26 explanation="shortened line",
27 confidence="high",
28 risk_level="safe-style",
29 input_tokens=100,
30 output_tokens=50,
31 cost_estimate=0.001,
32 )
35def test_writes_json_file(tmp_path: Path, suggestion: AIFixSuggestion) -> None:
36 """write_audit_log creates a JSON audit file."""
37 write_audit_log(tmp_path, [suggestion], rejected_count=1, total_cost=0.005)
39 audit_file = tmp_path / AUDIT_DIR / AUDIT_FILE
40 assert_that(audit_file.exists()).is_true()
42 data = json.loads(audit_file.read_text())
43 assert_that(data).contains_key(
44 "timestamp",
45 "applied_count",
46 "rejected_count",
47 "total_cost_usd",
48 "entries",
49 )
52def test_contains_correct_fields(tmp_path: Path, suggestion: AIFixSuggestion) -> None:
53 """Audit entries contain all expected fields from the suggestion."""
54 write_audit_log(tmp_path, [suggestion], rejected_count=2, total_cost=0.005)
56 data = json.loads((tmp_path / AUDIT_DIR / AUDIT_FILE).read_text())
57 assert_that(data["applied_count"]).is_equal_to(1)
58 assert_that(data["rejected_count"]).is_equal_to(2)
59 assert_that(data["entries"]).is_length(1)
61 entry = data["entries"][0]
62 assert_that(entry["file"]).is_equal_to("src/app.py")
63 assert_that(entry["line"]).is_equal_to(42)
64 assert_that(entry["code"]).is_equal_to("E501")
65 assert_that(entry["tool"]).is_equal_to("ruff")
66 assert_that(entry["action"]).is_equal_to("applied")
67 assert_that(entry["confidence"]).is_equal_to("high")
68 assert_that(entry["risk_level"]).is_equal_to("safe-style")
71def test_handles_empty_applied_list(tmp_path: Path) -> None:
72 """An empty applied list produces zero entries."""
73 write_audit_log(tmp_path, [], rejected_count=5, total_cost=0.0)
75 data = json.loads((tmp_path / AUDIT_DIR / AUDIT_FILE).read_text())
76 assert_that(data["applied_count"]).is_equal_to(0)
77 assert_that(data["entries"]).is_empty()
80def test_rounds_cost_properly(tmp_path: Path) -> None:
81 """Total cost is rounded to 6 decimal places."""
82 write_audit_log(tmp_path, [], rejected_count=0, total_cost=0.1234567890)
84 data = json.loads((tmp_path / AUDIT_DIR / AUDIT_FILE).read_text())
85 assert_that(data["total_cost_usd"]).is_equal_to(0.123457)