Skip to content

Commit 4fbd90f

Browse files
Add more tests
1 parent 49029df commit 4fbd90f

File tree

1 file changed

+114
-60
lines changed

1 file changed

+114
-60
lines changed

tests/test_cli_new.py

Lines changed: 114 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import shutil
2+
import subprocess
23
from pathlib import Path
34
from typing import Any
45

@@ -25,104 +26,157 @@ def check_uv_installed() -> None:
2526

2627

2728
class TestNewCommand:
29+
def _assert_project_created(
30+
self, project_path: Path, check_version_file: bool = False
31+
) -> None:
32+
assert (project_path / "main.py").exists()
33+
assert (project_path / "README.md").exists()
34+
assert (project_path / "pyproject.toml").exists()
35+
if check_version_file:
36+
assert (project_path / ".python-version").exists()
37+
2838
def test_creates_project_successfully(self, temp_project_dir: Path) -> None:
2939
result = runner.invoke(app, ["new", "my_fastapi_project"])
3040

3141
assert result.exit_code == 0
3242
project_path = temp_project_dir / "my_fastapi_project"
33-
assert (project_path / "main.py").exists()
34-
assert (project_path / "README.md").exists()
35-
assert (project_path / "pyproject.toml").exists()
43+
self._assert_project_created(project_path)
3644
assert "Success!" in result.output
3745
assert "my_fastapi_project" in result.output
3846

39-
def test_creates_project_with_python_flag(self, temp_project_dir: Path) -> None:
40-
result = runner.invoke(app, ["new", "my_fastapi_project", "--python", "3.12"])
41-
47+
def test_creates_project_with_python_version(self, temp_project_dir: Path) -> None:
48+
# Test long form
49+
result = runner.invoke(app, ["new", "project_long", "--python", "3.12"])
4250
assert result.exit_code == 0
43-
project_path = temp_project_dir / "my_fastapi_project"
44-
assert (project_path / "main.py").exists()
45-
assert (project_path / "README.md").exists()
46-
assert (project_path / ".python-version").exists()
47-
python_version_file = (project_path / ".python-version").read_text()
48-
assert "3.12" in python_version_file
49-
assert "Success!" in result.output
51+
project_path = temp_project_dir / "project_long"
52+
self._assert_project_created(project_path, check_version_file=True)
53+
assert "3.12" in (project_path / ".python-version").read_text()
5054

51-
def test_creates_project_with_python_flag_short(
52-
self, temp_project_dir: Path
53-
) -> None:
54-
result = runner.invoke(app, ["new", "another_project", "-p", "3.9"])
55+
# Test short form
56+
result = runner.invoke(app, ["new", "project_short", "-p", "3.9"])
5557
assert result.exit_code == 0
56-
project_path = temp_project_dir / "another_project"
57-
assert (project_path / ".python-version").exists()
58-
python_version_file = (project_path / ".python-version").read_text()
59-
assert "3.9" in python_version_file
58+
project_path = temp_project_dir / "project_short"
59+
assert "3.9" in (project_path / ".python-version").read_text()
6060

61-
def test_creates_project_with_multiple_flags(self, temp_project_dir: Path) -> None:
61+
def test_creates_project_with_extra_uv_flags(self, temp_project_dir: Path) -> None:
62+
"""Test that extra flags are passed through to uv."""
6263
result = runner.invoke(
6364
app, ["new", "my_fastapi_project", "--python", "3.12", "--lib"]
6465
)
6566

6667
assert result.exit_code == 0
6768
project_path = temp_project_dir / "my_fastapi_project"
68-
# With --lib flag, uv creates a library structure (no main.py by default)
69-
assert (project_path / "pyproject.toml").exists()
70-
# Our template files should still be created
71-
assert (project_path / "main.py").exists()
72-
assert (project_path / "README.md").exists()
69+
self._assert_project_created(project_path)
7370

74-
def test_rejects_python_below_3_8(self, temp_project_dir: Path) -> None:
75-
result = runner.invoke(app, ["new", "my_fastapi_project", "--python", "3.7"])
71+
def test_validates_template_file_contents(self, temp_project_dir: Path) -> None:
72+
result = runner.invoke(app, ["new", "sample_project"])
73+
assert result.exit_code == 0
7674

77-
assert result.exit_code == 1
78-
assert "Python 3.7 is not supported" in result.output
79-
assert "FastAPI requires Python 3.8" in result.output
75+
project_path = temp_project_dir / "sample_project"
76+
77+
main_py_content = (project_path / "main.py").read_text()
78+
assert "from fastapi import FastAPI" in main_py_content
79+
assert "app = FastAPI()" in main_py_content
80+
81+
# Check README.md
82+
readme_content = (project_path / "README.md").read_text()
83+
assert "# sample_project" in readme_content
84+
assert "A project created with FastAPI Cloud CLI." in readme_content
85+
86+
# Check pyproject.toml
87+
pyproject_content = (project_path / "pyproject.toml").read_text()
88+
assert 'name = "sample-project"' in pyproject_content
89+
assert "fastapi[standard]" in pyproject_content
90+
91+
def test_initializes_in_current_directory(self, temp_project_dir: Path) -> None:
92+
result = runner.invoke(app, ["new"])
93+
94+
assert result.exit_code == 0
95+
assert "No project name provided" in result.output
96+
assert "Initializing in current directory" in result.output
97+
self._assert_project_created(temp_project_dir)
8098

8199
def test_rejects_existing_directory(self, temp_project_dir: Path) -> None:
82100
existing_dir = temp_project_dir / "existing_project"
83101
existing_dir.mkdir()
84102

85103
result = runner.invoke(app, ["new", "existing_project"])
86-
87104
assert result.exit_code == 1
88105
assert "Directory 'existing_project' already exists." in result.output
89106

90-
def test_initializes_in_current_directory_when_no_name_provided(
107+
def test_rejects_python_below_3_8(self, temp_project_dir: Path) -> None:
108+
result = runner.invoke(app, ["new", "test_project", "--python", "3.7"])
109+
assert result.exit_code == 1
110+
assert "Python 3.7 is not supported" in result.output
111+
assert "FastAPI requires Python 3.8" in result.output
112+
113+
def test_passes_single_digit_python_version_to_uv(
91114
self, temp_project_dir: Path
92115
) -> None:
93-
result = runner.invoke(app, ["new"])
94-
116+
result = runner.invoke(app, ["new", "test_project", "--python", "3"])
95117
assert result.exit_code == 0
96-
assert "No project name provided" in result.output
97-
assert "Initializing in current directory" in result.output
118+
project_path = temp_project_dir / "test_project"
119+
self._assert_project_created(project_path)
98120

99-
assert "Initialized FastAPI project in current directory" in result.output
100121

101-
# Files should be created in current directory
102-
assert (temp_project_dir / "main.py").exists()
103-
assert (temp_project_dir / "README.md").exists()
104-
assert (temp_project_dir / "pyproject.toml").exists()
122+
class TestNewCommandUvFailures:
123+
"""Tests for error handling in the new command when uv fails."""
105124

106-
def test_validate_file_contents(self, temp_project_dir: Path) -> None:
107-
result = runner.invoke(app, ["new", "sample_project"])
125+
def test_failed_to_initialize_with_uv(self, monkeypatch: Any) -> None:
126+
def mock_run(*args: Any, **kwargs: Any) -> None:
127+
# Let the first check for 'uv' succeed, but fail on 'uv init'
128+
if args[0][0] == "uv" and args[0][1] == "init":
129+
raise subprocess.CalledProcessError(
130+
1, args[0], stderr=b"uv init failed for some reason"
131+
)
108132

109-
assert result.exit_code == 0
110-
project_path = temp_project_dir / "sample_project"
133+
monkeypatch.setattr(subprocess, "run", mock_run)
111134

112-
main_py_content = (project_path / "main.py").read_text()
113-
assert "from fastapi import FastAPI" in main_py_content
114-
assert "app = FastAPI()" in main_py_content
135+
result = runner.invoke(app, ["new", "failing_project"])
136+
assert result.exit_code == 1
137+
assert "Failed to initialize project with uv" in result.output
115138

116-
readme_content = (project_path / "README.md").read_text()
117-
assert "# sample_project" in readme_content
118-
assert "A project created with FastAPI Cloud CLI." in readme_content
139+
def test_failed_to_add_dependencies(
140+
self, temp_project_dir: Path, monkeypatch: Any
141+
) -> None:
142+
"""Test error handling when uv add fails."""
119143

120-
def test_validate_pyproject_toml_contents(self, temp_project_dir: Path) -> None:
121-
result = runner.invoke(app, ["new", "test_project"])
144+
def mock_run(*args: Any, **kwargs: Any) -> None:
145+
# Let 'uv init' succeed, but fail on 'uv add'
146+
if args[0][0] == "uv" and args[0][1] == "add":
147+
raise subprocess.CalledProcessError(
148+
1, args[0], stderr=b"Failed to resolve dependencies"
149+
)
122150

123-
assert result.exit_code == 0
124-
project_path = temp_project_dir / "test_project"
151+
monkeypatch.setattr(subprocess, "run", mock_run)
125152

126-
pyproject_content = (project_path / "pyproject.toml").read_text()
127-
assert 'name = "test-project"' in pyproject_content
128-
assert "fastapi[standard]" in pyproject_content
153+
result = runner.invoke(app, ["new", "failing_deps"])
154+
assert result.exit_code == 1
155+
assert "Failed to install dependencies" in result.output
156+
157+
def test_file_write_failure(self, temp_project_dir: Path, monkeypatch: Any) -> None:
158+
"""Test error handling when template file writing fails."""
159+
original_write_text = Path.write_text
160+
161+
def mock_write_text(self: Path, *args: Any, **kwargs: Any) -> None:
162+
# Fail when trying to write main.py (our template file)
163+
if self.name == "main.py":
164+
raise PermissionError("Permission denied")
165+
return original_write_text(self, *args, **kwargs)
166+
167+
monkeypatch.setattr(Path, "write_text", mock_write_text)
168+
169+
result = runner.invoke(app, ["new", "test_write_fail"])
170+
assert result.exit_code == 1
171+
assert "Failed to write template files" in result.output
172+
173+
def test_uv_not_installed(self, temp_project_dir: Path, monkeypatch: Any) -> None:
174+
"""Test error when uv is not installed."""
175+
# The check_uv_installed fixture runs before this, but we want to test
176+
# the case where uv disappears after that check
177+
monkeypatch.setattr(shutil, "which", lambda _: None)
178+
179+
result = runner.invoke(app, ["new", "test_uv_missing_project"])
180+
assert result.exit_code == 1
181+
assert "uv is required to create new projects" in result.output
182+
assert "https://uv.run/docs/installation/" in result.output

0 commit comments

Comments
 (0)