Coverage for tests / unit / ai / test_rerun.py: 100%
64 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 lintro.ai.rerun module."""
3from __future__ import annotations
5import os
6from pathlib import Path
7from unittest.mock import MagicMock, patch
9from assertpy import assert_that
11from lintro.ai.rerun import (
12 _tool_cwd,
13 apply_rerun_results,
14 paths_for_context,
15 rerun_tools,
16)
17from lintro.models.core.tool_result import ToolResult
18from lintro.parsers.base_issue import BaseIssue
20from .conftest import MockIssue
22_ByTool = dict[str, tuple[ToolResult, list[BaseIssue]]]
25# -- TestToolCwd: Tests for _tool_cwd context manager. -----------------------
28def test_tool_cwd_restores_original_cwd(tmp_path: Path) -> None:
29 """_tool_cwd changes cwd and restores it after exit."""
30 original = os.getcwd()
31 target = str(tmp_path)
33 with _tool_cwd(target):
34 assert_that(os.getcwd()).is_equal_to(target)
36 assert_that(os.getcwd()).is_equal_to(original)
39def test_tool_cwd_skips_when_none() -> None:
40 """When cwd is None, the context manager yields without changing directory."""
41 original = os.getcwd()
43 with _tool_cwd(None):
44 assert_that(os.getcwd()).is_equal_to(original)
46 assert_that(os.getcwd()).is_equal_to(original)
49# -- TestPathsForContext: Tests for paths_for_context. -----------------------
52def test_paths_for_context_relativizes_paths(tmp_path: Path) -> None:
53 """Given absolute paths and a cwd, returns relative paths for children."""
54 cwd = str(tmp_path)
55 child = str(tmp_path / "src" / "main.py")
56 outside = "/some/other/path/file.py"
58 result = paths_for_context(file_paths=[child, outside], cwd=cwd)
60 assert_that(result).is_length(2)
61 assert_that(result[0]).is_equal_to(str(Path("src") / "main.py"))
62 # Outside path stays absolute (resolved)
63 assert_that(result[1]).is_equal_to(str(Path(outside).resolve()))
66def test_paths_for_context_returns_originals_when_no_cwd() -> None:
67 """When cwd is None, returns original paths unchanged."""
68 paths = ["/a/b/c.py", "/d/e/f.py"]
70 result = paths_for_context(file_paths=paths, cwd=None)
72 assert_that(result).is_equal_to(paths)
75# -- TestRerunTools: Tests for rerun_tools. ----------------------------------
78def test_rerun_tools_with_missing_tools() -> None:
79 """When tool_manager.get_tool() raises KeyError, the tool is skipped."""
80 issue = MockIssue(
81 file="src/main.py",
82 line=1,
83 column=1,
84 message="test",
85 code="T001",
86 severity="low",
87 )
88 result = ToolResult(name="missing_tool", success=False, issues_count=1)
89 by_tool: _ByTool = {"missing_tool": (result, [issue])}
91 mock_tool_manager = MagicMock()
92 mock_tool_manager.get_tool.side_effect = KeyError("missing_tool")
94 with patch("lintro.tools.tool_manager", mock_tool_manager):
95 results = rerun_tools(by_tool)
97 assert_that(results).is_not_none()
98 assert_that(results).is_empty()
101# -- TestApplyRerunResults: Tests for apply_rerun_results. -------------------
104def test_apply_rerun_results_preserves_native_counters() -> None:
105 """Native initial/fixed counts are preserved after rerun."""
106 original_result = ToolResult(
107 name="ruff",
108 success=False,
109 issues_count=10,
110 initial_issues_count=10,
111 fixed_issues_count=7,
112 remaining_issues_count=3,
113 )
114 issue = MockIssue(
115 file="src/main.py",
116 line=1,
117 column=1,
118 message="remaining issue",
119 code="E501",
120 severity="warning",
121 )
122 by_tool: _ByTool = {"ruff": (original_result, [issue])}
124 rerun_result = ToolResult(
125 name="ruff",
126 success=True,
127 issues_count=2,
128 issues=[
129 MockIssue(
130 file="src/main.py",
131 line=1,
132 column=1,
133 message="issue 1",
134 code="E501",
135 severity="warning",
136 ),
137 MockIssue(
138 file="src/main.py",
139 line=5,
140 column=1,
141 message="issue 2",
142 code="E502",
143 severity="warning",
144 ),
145 ],
146 )
148 apply_rerun_results(by_tool=by_tool, rerun_results=[rerun_result])
150 assert_that(original_result.initial_issues_count).is_equal_to(10)
151 assert_that(original_result.fixed_issues_count).is_equal_to(7)
154def test_apply_rerun_results_updates_remaining_issues() -> None:
155 """Remaining issues are updated from rerun results."""
156 original_result = ToolResult(
157 name="ruff",
158 success=False,
159 issues_count=5,
160 initial_issues_count=5,
161 fixed_issues_count=3,
162 remaining_issues_count=2,
163 )
164 issue = MockIssue(
165 file="src/main.py",
166 line=1,
167 column=1,
168 message="old issue",
169 code="E501",
170 severity="warning",
171 )
172 by_tool: _ByTool = {"ruff": (original_result, [issue])}
174 refreshed_issue = MockIssue(
175 file="src/main.py",
176 line=10,
177 column=1,
178 message="remaining issue",
179 code="E501",
180 severity="warning",
181 )
182 rerun_result = ToolResult(
183 name="ruff",
184 success=True,
185 issues_count=1,
186 issues=[refreshed_issue],
187 )
189 apply_rerun_results(by_tool=by_tool, rerun_results=[rerun_result])
191 assert_that(original_result.remaining_issues_count).is_equal_to(1)
192 assert_that(original_result.issues_count).is_equal_to(1)
193 assert_that(original_result.issues).is_length(1)
194 assert_that(original_result.issues).is_not_none()
195 assert_that(original_result.issues[0].message).is_equal_to("remaining issue") # type: ignore[index] # assertpy is_not_none narrows this
196 assert_that(original_result.success).is_true()