Coverage for tests / unit / utils / test_streaming_output.py: 100%
167 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.utils.streaming_output module."""
3from __future__ import annotations
5import json
6import tempfile
7from pathlib import Path
8from unittest.mock import MagicMock
10import pytest
11from assertpy import assert_that
13from lintro.enums.action import Action
14from lintro.models.core.tool_result import ToolResult
15from lintro.utils.streaming_output import (
16 StreamingResultHandler,
17 create_streaming_handler,
18)
21@pytest.fixture
22def mock_tool_result() -> ToolResult:
23 """Create a mock ToolResult for testing.
25 Returns:
26 A ToolResult with success=True and 2 issues.
27 """
28 return ToolResult(
29 name="test_tool",
30 success=True,
31 issues_count=2,
32 output="Test output",
33 )
36@pytest.fixture
37def mock_tool_result_with_issues() -> ToolResult:
38 """Create a mock ToolResult with issues.
40 Returns:
41 A ToolResult with success=False and 1 issue.
42 """
43 mock_issue = MagicMock()
44 mock_issue.file = "test.py"
45 mock_issue.line = 10
46 mock_issue.column = 5
47 mock_issue.message = "Test error"
49 return ToolResult(
50 name="test_tool",
51 success=False,
52 issues_count=1,
53 output="Error output",
54 issues=[mock_issue],
55 )
58@pytest.fixture
59def mock_fix_result() -> ToolResult:
60 """Create a mock ToolResult for fix action.
62 Returns:
63 A ToolResult with fix counts set.
64 """
65 return ToolResult(
66 name="test_tool",
67 success=True,
68 issues_count=0,
69 output="Fixed",
70 fixed_issues_count=3,
71 remaining_issues_count=1,
72 )
75def test_handler_stores_output_format() -> None:
76 """Handler stores output format."""
77 handler = StreamingResultHandler(output_format="json", action=Action.CHECK)
78 assert_that(handler.output_format).is_equal_to("json")
81def test_handler_stores_action() -> None:
82 """Handler stores action."""
83 handler = StreamingResultHandler(output_format="grid", action=Action.FIX)
84 assert_that(handler.action).is_equal_to(Action.FIX)
87def test_handler_initializes_totals() -> None:
88 """Handler initializes totals dictionary."""
89 handler = StreamingResultHandler(output_format="grid", action=Action.CHECK)
90 totals = handler.get_totals()
92 assert_that(totals).contains_key("issues", "fixed", "remaining")
93 assert_that(totals["issues"]).is_equal_to(0)
96def test_handle_result_updates_totals(mock_tool_result: ToolResult) -> None:
97 """Handle result updates totals.
99 Args:
100 mock_tool_result: Fixture providing a mock ToolResult.
101 """
102 handler = StreamingResultHandler(output_format="grid", action=Action.CHECK)
103 handler.handle_result(mock_tool_result)
105 totals = handler.get_totals()
106 assert_that(totals["tools_run"]).is_equal_to(1)
107 assert_that(totals["issues"]).is_equal_to(2)
110def test_handle_result_tracks_failures(
111 mock_tool_result_with_issues: ToolResult,
112) -> None:
113 """Handle result tracks failed tools.
115 Args:
116 mock_tool_result_with_issues: Fixture providing a failed ToolResult.
117 """
118 handler = StreamingResultHandler(output_format="grid", action=Action.CHECK)
119 handler.handle_result(mock_tool_result_with_issues)
121 totals = handler.get_totals()
122 assert_that(totals["tools_failed"]).is_equal_to(1)
125def test_handle_result_tracks_fix_counts(mock_fix_result: ToolResult) -> None:
126 """Handle result tracks fix counts for FIX action.
128 Args:
129 mock_fix_result: Fixture providing a ToolResult with fix counts.
130 """
131 handler = StreamingResultHandler(output_format="grid", action=Action.FIX)
132 handler.handle_result(mock_fix_result)
134 totals = handler.get_totals()
135 assert_that(totals["fixed"]).is_equal_to(3)
136 assert_that(totals["remaining"]).is_equal_to(1)
139def test_handle_result_buffers_results(mock_tool_result: ToolResult) -> None:
140 """Handle result buffers results.
142 Args:
143 mock_tool_result: Fixture providing a mock ToolResult.
144 """
145 handler = StreamingResultHandler(output_format="grid", action=Action.CHECK)
146 handler.handle_result(mock_tool_result)
148 results = handler.get_results()
149 assert_that(results).is_length(1)
150 assert_that(results[0].name).is_equal_to("test_tool")
153def test_get_exit_code_returns_zero_on_success() -> None:
154 """Get exit code returns 0 when all tools pass."""
155 success_result = ToolResult(
156 name="test_tool",
157 success=True,
158 issues_count=0,
159 )
160 handler = StreamingResultHandler(output_format="grid", action=Action.CHECK)
161 handler.handle_result(success_result)
163 assert_that(handler.get_exit_code()).is_equal_to(0)
166def test_get_exit_code_returns_one_on_failure(
167 mock_tool_result_with_issues: ToolResult,
168) -> None:
169 """Get exit code returns 1 when tools fail.
171 Args:
172 mock_tool_result_with_issues: Fixture providing a failed ToolResult.
173 """
174 handler = StreamingResultHandler(output_format="grid", action=Action.CHECK)
175 handler.handle_result(mock_tool_result_with_issues)
177 assert_that(handler.get_exit_code()).is_equal_to(1)
180def test_get_exit_code_returns_one_on_issues(mock_tool_result: ToolResult) -> None:
181 """Get exit code returns 1 when issues found in check mode.
183 Args:
184 mock_tool_result: Fixture providing a mock ToolResult.
185 """
186 handler = StreamingResultHandler(output_format="grid", action=Action.CHECK)
187 handler.handle_result(mock_tool_result)
189 assert_that(handler.get_exit_code()).is_equal_to(1)
192def test_context_manager_opens_file() -> None:
193 """Context manager opens output file."""
194 with tempfile.NamedTemporaryFile(delete=False, suffix=".json") as f:
195 temp_path = f.name
197 try:
198 handler = StreamingResultHandler(
199 output_format="jsonl",
200 action=Action.CHECK,
201 output_file=temp_path,
202 )
203 with handler:
204 assert_that(handler._file_handle).is_not_none()
205 finally:
206 Path(temp_path).unlink()
209def test_context_manager_closes_file() -> None:
210 """Context manager closes output file."""
211 with tempfile.NamedTemporaryFile(delete=False, suffix=".json") as f:
212 temp_path = f.name
214 try:
215 handler = StreamingResultHandler(
216 output_format="jsonl",
217 action=Action.CHECK,
218 output_file=temp_path,
219 )
220 with handler:
221 file_handle = handler._file_handle
222 assert_that(file_handle).is_not_none()
223 assert file_handle is not None
224 assert_that(file_handle.closed).is_true()
225 finally:
226 Path(temp_path).unlink()
229def test_json_format_writes_array_brackets() -> None:
230 """JSON format writes array brackets."""
231 with tempfile.NamedTemporaryFile(delete=False, suffix=".json") as f:
232 temp_path = f.name
234 try:
235 handler = StreamingResultHandler(
236 output_format="json",
237 action=Action.CHECK,
238 output_file=temp_path,
239 )
240 with handler:
241 pass
243 content = Path(temp_path).read_text()
244 assert_that(content).starts_with("[")
245 assert_that(content).ends_with("]")
246 finally:
247 Path(temp_path).unlink()
250def test_handles_file_open_error() -> None:
251 """Handler handles file open errors gracefully."""
252 handler = StreamingResultHandler(
253 output_format="jsonl",
254 action=Action.CHECK,
255 output_file="/nonexistent/directory/file.json",
256 )
257 with handler:
258 assert_that(handler._file_handle).is_none()
261def test_writes_jsonl_format(mock_tool_result: ToolResult) -> None:
262 """Handler writes JSONL format.
264 Args:
265 mock_tool_result: Fixture providing a mock ToolResult.
266 """
267 with tempfile.NamedTemporaryFile(delete=False, suffix=".jsonl") as f:
268 temp_path = f.name
270 try:
271 handler = StreamingResultHandler(
272 output_format="jsonl",
273 action=Action.CHECK,
274 output_file=temp_path,
275 )
276 with handler:
277 handler.handle_result(mock_tool_result)
279 content = Path(temp_path).read_text()
280 lines = content.strip().split("\n")
281 assert_that(lines).is_length(1)
283 data = json.loads(lines[0])
284 assert_that(data["tool"]).is_equal_to("test_tool")
285 finally:
286 Path(temp_path).unlink()
289def test_writes_json_array_format(mock_tool_result: ToolResult) -> None:
290 """Handler writes JSON array format.
292 Args:
293 mock_tool_result: Fixture providing a mock ToolResult.
294 """
295 with tempfile.NamedTemporaryFile(delete=False, suffix=".json") as f:
296 temp_path = f.name
298 try:
299 handler = StreamingResultHandler(
300 output_format="json",
301 action=Action.CHECK,
302 output_file=temp_path,
303 )
304 with handler:
305 handler.handle_result(mock_tool_result)
307 content = Path(temp_path).read_text()
308 data = json.loads(content)
309 assert_that(data).is_instance_of(list)
310 assert_that(data[0]["tool"]).is_equal_to("test_tool")
311 finally:
312 Path(temp_path).unlink()
315def test_writes_multiple_json_array_results() -> None:
316 """Handler writes multiple results in JSON array format."""
317 with tempfile.NamedTemporaryFile(delete=False, suffix=".json") as f:
318 temp_path = f.name
320 try:
321 result1 = ToolResult(name="tool1", success=True, issues_count=0)
322 result2 = ToolResult(name="tool2", success=True, issues_count=1)
324 handler = StreamingResultHandler(
325 output_format="json",
326 action=Action.CHECK,
327 output_file=temp_path,
328 )
329 with handler:
330 handler.handle_result(result1)
331 handler.handle_result(result2)
333 content = Path(temp_path).read_text()
334 data = json.loads(content)
335 assert_that(data).is_instance_of(list)
336 assert_that(data).is_length(2)
337 assert_that(data[0]["tool"]).is_equal_to("tool1")
338 assert_that(data[1]["tool"]).is_equal_to("tool2")
339 finally:
340 Path(temp_path).unlink()
343def test_result_to_dict_includes_basic_fields(mock_tool_result: ToolResult) -> None:
344 """Result dict includes basic fields.
346 Args:
347 mock_tool_result: Fixture providing a mock ToolResult.
348 """
349 handler = StreamingResultHandler(output_format="json", action=Action.CHECK)
350 data = handler._result_to_dict(mock_tool_result)
352 assert_that(data).contains_key("tool", "success", "issues_count")
353 assert_that(data["tool"]).is_equal_to("test_tool")
356def test_result_to_dict_includes_fix_counts(mock_fix_result: ToolResult) -> None:
357 """Result dict includes fix counts when present.
359 Args:
360 mock_fix_result: Fixture providing a ToolResult with fix counts.
361 """
362 handler = StreamingResultHandler(output_format="json", action=Action.FIX)
363 data = handler._result_to_dict(mock_fix_result)
365 assert_that(data).contains_key("fixed_issues_count", "remaining_issues_count")
366 assert_that(data["fixed_issues_count"]).is_equal_to(3)
369def test_create_streaming_handler_with_format() -> None:
370 """Create handler with specified format."""
371 handler = create_streaming_handler("json", Action.CHECK)
372 assert_that(handler.output_format).is_equal_to("json")
375def test_create_streaming_handler_with_action() -> None:
376 """Create handler with specified action."""
377 handler = create_streaming_handler("grid", Action.FIX)
378 assert_that(handler.action).is_equal_to(Action.FIX)
381def test_create_streaming_handler_with_output_file(tmp_path: Path) -> None:
382 """Create handler with output file.
384 Args:
385 tmp_path: Temporary path fixture.
386 """
387 output_file = tmp_path / "output.json"
388 handler = create_streaming_handler("json", Action.CHECK, str(output_file))
389 assert_that(handler.output_file).is_equal_to(str(output_file))