Coverage for tests / unit / tools / astro_check / test_execution.py: 100%
77 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"""Unit tests for astro-check plugin check method execution."""
3from __future__ import annotations
5from pathlib import Path
6from typing import Any
7from unittest.mock import patch
9import pytest
10from assertpy import assert_that
12from lintro.parsers.astro_check.astro_check_issue import AstroCheckIssue
13from lintro.tools.definitions.astro_check import AstroCheckPlugin
16def _mock_subprocess_success(**kwargs: Any) -> tuple[bool, str]:
17 """Mock subprocess that returns success with no output.
19 Args:
20 **kwargs: Ignored keyword arguments.
22 Returns:
23 Tuple of (success=True, empty string).
24 """
25 return (True, "")
28def _mock_subprocess_with_issues(**kwargs: Any) -> tuple[bool, str]:
29 """Mock subprocess that returns output with type errors.
31 Args:
32 **kwargs: Ignored keyword arguments.
34 Returns:
35 Tuple of (success=False, error output).
36 """
37 output = (
38 "src/pages/index.astro:10:5 - error ts2322: "
39 "Type 'string' is not assignable to type 'number'.\n"
40 "src/components/Card.astro:15:10 - error ts2339: "
41 "Property 'foo' does not exist on type 'Props'."
42 )
43 return (False, output)
46def test_check_no_astro_files(
47 astro_check_plugin: AstroCheckPlugin,
48 tmp_path: Path,
49) -> None:
50 """Check returns early when no Astro files found.
52 Args:
53 astro_check_plugin: The AstroCheckPlugin instance to test.
54 tmp_path: Temporary directory path for test files.
55 """
56 # Create a non-Astro file
57 test_file = tmp_path / "test.ts"
58 test_file.write_text("const x = 1;")
60 result = astro_check_plugin.check([str(test_file)], {})
62 assert_that(result.success).is_true()
63 assert_that(result.issues_count).is_equal_to(0)
64 assert_that(result.output).contains("No Astro files to check.")
67def test_check_no_astro_config_proceeds_with_defaults(
68 astro_check_plugin: AstroCheckPlugin,
69 tmp_path: Path,
70) -> None:
71 """Check proceeds with defaults when no Astro config found.
73 Args:
74 astro_check_plugin: The AstroCheckPlugin instance to test.
75 tmp_path: Temporary directory path for test files.
76 """
77 # Create an Astro file but no config
78 astro_file = tmp_path / "test.astro"
79 astro_file.write_text("---\nconst message = 'Hello';\n---\n<h1>{message}</h1>")
81 with patch.object(
82 astro_check_plugin,
83 "_run_subprocess",
84 side_effect=_mock_subprocess_success,
85 ) as mock_run:
86 result = astro_check_plugin.check([str(tmp_path)], {})
88 assert_that(result.success).is_true()
89 assert_that(result.issues_count).is_equal_to(0)
90 mock_run.assert_called_once()
91 _, kwargs = mock_run.call_args
92 assert_that(kwargs["cwd"]).is_equal_to(str(tmp_path))
93 assert_that(kwargs["cmd"]).contains("check")
96def test_check_with_mocked_subprocess_success(
97 astro_check_plugin: AstroCheckPlugin,
98 tmp_path: Path,
99) -> None:
100 """Check returns success when astro check finds no issues.
102 Args:
103 astro_check_plugin: The AstroCheckPlugin instance to test.
104 tmp_path: Temporary directory path for test files.
105 """
106 # Create Astro file and config
107 astro_file = tmp_path / "test.astro"
108 astro_file.write_text("---\nconst message = 'Hello';\n---\n<h1>{message}</h1>")
109 config_file = tmp_path / "astro.config.mjs"
110 config_file.write_text("export default {};")
112 with patch.object(
113 astro_check_plugin,
114 "_run_subprocess",
115 side_effect=_mock_subprocess_success,
116 ):
117 result = astro_check_plugin.check([str(tmp_path)], {})
119 assert_that(result.success).is_true()
120 assert_that(result.issues_count).is_equal_to(0)
123def test_check_with_mocked_subprocess_issues_found(
124 astro_check_plugin: AstroCheckPlugin,
125 tmp_path: Path,
126) -> None:
127 """Check returns issues when astro check finds type errors.
129 Args:
130 astro_check_plugin: The AstroCheckPlugin instance to test.
131 tmp_path: Temporary directory path for test files.
132 """
133 # Create Astro file and config in the same directory
134 # (plugin searches for config in cwd, which is the file's directory)
135 astro_file = tmp_path / "index.astro"
136 astro_file.write_text("---\nconst x: number = 'bad';\n---\n<h1>{x}</h1>")
137 config_file = tmp_path / "astro.config.mjs"
138 config_file.write_text("export default {};")
140 with patch.object(
141 astro_check_plugin,
142 "_run_subprocess",
143 side_effect=_mock_subprocess_with_issues,
144 ):
145 result = astro_check_plugin.check([str(tmp_path)], {})
147 assert_that(result.success).is_false()
148 assert_that(result.issues_count).is_equal_to(2)
149 issues = result.issues
150 assert_that(issues).is_not_none()
151 assert issues is not None
152 assert_that(issues).is_length(2)
154 # Verify first issue
155 first_issue = issues[0]
156 assert isinstance(first_issue, AstroCheckIssue)
157 assert_that(first_issue.file).is_equal_to("src/pages/index.astro")
158 assert_that(first_issue.line).is_equal_to(10)
159 assert_that(first_issue.code).is_equal_to("TS2322")
162def test_fix_raises_not_implemented(
163 astro_check_plugin: AstroCheckPlugin,
164 tmp_path: Path,
165) -> None:
166 """Fix method raises NotImplementedError.
168 Args:
169 astro_check_plugin: The AstroCheckPlugin instance to test.
170 tmp_path: Temporary directory path for test files.
171 """
172 with pytest.raises(NotImplementedError, match="cannot automatically fix"):
173 astro_check_plugin.fix([str(tmp_path)], {})
176def test_check_with_root_option(
177 astro_check_plugin: AstroCheckPlugin,
178 tmp_path: Path,
179) -> None:
180 """Check uses root option when provided.
182 Args:
183 astro_check_plugin: The AstroCheckPlugin instance to test.
184 tmp_path: Temporary directory path for test files.
185 """
186 # Create Astro file and config in a subdirectory
187 project_dir = tmp_path / "packages" / "web"
188 project_dir.mkdir(parents=True)
189 astro_file = project_dir / "test.astro"
190 astro_file.write_text("---\nconst message = 'Hello';\n---\n<h1>{message}</h1>")
191 config_file = project_dir / "astro.config.mjs"
192 config_file.write_text("export default {};")
194 captured_cmd: list[str] = []
196 def capture_cmd(cmd: list[str], **kwargs: Any) -> tuple[bool, str]:
197 captured_cmd.extend(cmd)
198 return (True, "")
200 with patch.object(
201 astro_check_plugin,
202 "_run_subprocess",
203 side_effect=capture_cmd,
204 ):
205 astro_check_plugin.check(
206 [str(tmp_path)],
207 {"root": str(project_dir)},
208 )
210 # Verify --root was passed with the correct project directory path
211 assert_that(captured_cmd).contains("--root")
212 root_idx = captured_cmd.index("--root")
213 assert_that(captured_cmd[root_idx + 1]).is_equal_to(str(project_dir))