@@ -32,6 +32,22 @@ def test_none_backend(self, tmp_path: Path):
3232 assert result .exit_code == 0 , result .output
3333 assert result .output == "none\n "
3434
35+ def test_output_file (self , tmp_path : Path ):
36+ # Arrange
37+ (tmp_path / "uv.lock" ).touch ()
38+ output_file = tmp_path / "backend.txt"
39+
40+ # Act
41+ runner = CliRunner ()
42+ with change_cwd (tmp_path ):
43+ result = runner .invoke_safe (
44+ app , ["backend" , "--output-file" , str (output_file )]
45+ )
46+
47+ # Assert
48+ assert result .exit_code == 0 , result .output
49+ assert output_file .read_text (encoding = "utf-8" ) == "uv\n "
50+
3551
3652class TestName :
3753 def test_output (self , tmp_path : Path ):
@@ -60,6 +76,23 @@ def test_invalid_pyproject(self, tmp_path: Path):
6076 # Assert
6177 assert result .exit_code == 1 , result .output
6278
79+ def test_output_file (self , tmp_path : Path ):
80+ # Arrange
81+ path = tmp_path / "fun"
82+ path .mkdir ()
83+ output_file = path / "name.txt"
84+
85+ # Act
86+ runner = CliRunner ()
87+ with change_cwd (path ):
88+ result = runner .invoke_safe (
89+ app , ["name" , "--output-file" , str (output_file )]
90+ )
91+
92+ # Assert
93+ assert result .exit_code == 0 , result .output
94+ assert output_file .read_text (encoding = "utf-8" ) == "fun\n "
95+
6396
6497class TestSonarqube :
6598 def test_runs (self , tmp_path : Path ):
@@ -162,3 +195,64 @@ def test_invalid_pyproject(self, tmp_path: Path):
162195
163196 # Assert
164197 assert result .exit_code == 1 , result .output
198+
199+ def test_output_file (self , tmp_path : Path ):
200+ # Arrange
201+ (tmp_path / "pyproject.toml" ).write_text (
202+ """
203+ [tool.usethis.sonarqube]
204+ project-key = "fun"
205+
206+ [tool.coverage.xml.output]
207+ """
208+ )
209+ output_file = tmp_path / "sonar-project.properties"
210+
211+ # Act
212+ runner = CliRunner ()
213+ with change_cwd (tmp_path ):
214+ result = runner .invoke_safe (
215+ app , ["sonarqube" , "--output-file" , str (output_file )]
216+ )
217+
218+ # Assert
219+ assert result .exit_code == 0 , result .output
220+ content = output_file .read_text (encoding = "utf-8" )
221+ assert "sonar.projectKey=fun" in content
222+
223+ def test_output_file_not_detected_as_existing (self , tmp_path : Path ):
224+ """Using --output-file avoids the redirect problem.
225+
226+ When using shell redirect (`> file`), the file is created empty before
227+ the command runs, which causes sonarqube to read that empty file.
228+ With --output-file, the file is written after generation.
229+ """
230+ # Arrange
231+ (tmp_path / "pyproject.toml" ).write_text (
232+ """
233+ [tool.usethis.sonarqube]
234+ project-key = "fun"
235+
236+ [tool.coverage.xml.output]
237+ """
238+ )
239+ # Simulate what happens with shell redirect: an empty file pre-exists
240+ output_file = tmp_path / "sonar-project.properties"
241+ output_file .write_text ("" , encoding = "utf-8" )
242+
243+ # Act
244+ # Despite sonar-project.properties existing (empty), --output-file
245+ # still causes the config to be read from that file (by design of
246+ # get_sonar_project_properties), then overwrites it with that content.
247+ runner = CliRunner ()
248+ with change_cwd (tmp_path ):
249+ result = runner .invoke_safe (
250+ app , ["sonarqube" , "--output-file" , str (output_file )]
251+ )
252+
253+ # Assert
254+ assert result .exit_code == 0 , result .output
255+ content = output_file .read_text (encoding = "utf-8" )
256+ # With --output-file, the file is written after content generation,
257+ # so even if it was previously empty, it will have the content now
258+ assert content != ""
0 commit comments