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

1"""Tests for the AI audit log writer.""" 

2 

3from __future__ import annotations 

4 

5import json 

6from pathlib import Path 

7 

8import pytest 

9from assertpy import assert_that 

10 

11from lintro.ai.audit import AUDIT_DIR, AUDIT_FILE, write_audit_log 

12from lintro.ai.models import AIFixSuggestion 

13 

14 

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 ) 

33 

34 

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) 

38 

39 audit_file = tmp_path / AUDIT_DIR / AUDIT_FILE 

40 assert_that(audit_file.exists()).is_true() 

41 

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 ) 

50 

51 

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) 

55 

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) 

60 

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") 

69 

70 

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) 

74 

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() 

78 

79 

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) 

83 

84 data = json.loads((tmp_path / AUDIT_DIR / AUDIT_FILE).read_text()) 

85 assert_that(data["total_cost_usd"]).is_equal_to(0.123457)