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

1"""Unit tests for astro-check plugin check method execution.""" 

2 

3from __future__ import annotations 

4 

5from pathlib import Path 

6from typing import Any 

7from unittest.mock import patch 

8 

9import pytest 

10from assertpy import assert_that 

11 

12from lintro.parsers.astro_check.astro_check_issue import AstroCheckIssue 

13from lintro.tools.definitions.astro_check import AstroCheckPlugin 

14 

15 

16def _mock_subprocess_success(**kwargs: Any) -> tuple[bool, str]: 

17 """Mock subprocess that returns success with no output. 

18 

19 Args: 

20 **kwargs: Ignored keyword arguments. 

21 

22 Returns: 

23 Tuple of (success=True, empty string). 

24 """ 

25 return (True, "") 

26 

27 

28def _mock_subprocess_with_issues(**kwargs: Any) -> tuple[bool, str]: 

29 """Mock subprocess that returns output with type errors. 

30 

31 Args: 

32 **kwargs: Ignored keyword arguments. 

33 

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) 

44 

45 

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. 

51 

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

59 

60 result = astro_check_plugin.check([str(test_file)], {}) 

61 

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

65 

66 

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. 

72 

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

80 

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)], {}) 

87 

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

94 

95 

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. 

101 

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 {};") 

111 

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)], {}) 

118 

119 assert_that(result.success).is_true() 

120 assert_that(result.issues_count).is_equal_to(0) 

121 

122 

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. 

128 

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 {};") 

139 

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)], {}) 

146 

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) 

153 

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

160 

161 

162def test_fix_raises_not_implemented( 

163 astro_check_plugin: AstroCheckPlugin, 

164 tmp_path: Path, 

165) -> None: 

166 """Fix method raises NotImplementedError. 

167 

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)], {}) 

174 

175 

176def test_check_with_root_option( 

177 astro_check_plugin: AstroCheckPlugin, 

178 tmp_path: Path, 

179) -> None: 

180 """Check uses root option when provided. 

181 

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 {};") 

193 

194 captured_cmd: list[str] = [] 

195 

196 def capture_cmd(cmd: list[str], **kwargs: Any) -> tuple[bool, str]: 

197 captured_cmd.extend(cmd) 

198 return (True, "") 

199 

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 ) 

209 

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