Coverage for tests/test_new.py: 100%

114 statements  

« prev     ^ index     » next       coverage.py v7.11.2, created at 2026-11-04 19:57 +0000

1import shutil 1abcdefgh

2import subprocess 1abcdefgh

3from pathlib import Path 1abcdefgh

4from typing import Any 1abcdefgh

5 

6import pytest 1abcdefgh

7from typer.testing import CliRunner 1abcdefgh

8 

9from fastapi_new.cli import app 1abcdefgh

10 

11runner = CliRunner() 1abcdefgh

12 

13 

14@pytest.fixture 1abcdefgh

15def temp_project_dir(tmp_path: Path, monkeypatch: Any) -> Path: 1abcdefgh

16 """Create a temporary directory and cd into it.""" 

17 monkeypatch.chdir(tmp_path) 1abcdefgh

18 return tmp_path 1abcdefgh

19 

20 

21def _assert_project_created(project_path: Path) -> None: 1abcdefgh

22 assert (project_path / "main.py").exists() 1yiWXzYAjZ0B1Ck23D4El56F7Gm89H!In#$J%Ko'(L)Mp*+N,

23 assert (project_path / "README.md").exists() 1yiWXzYAjZ0B1Ck23D4El56F7Gm89H!In#$J%Ko'(L)Mp*+N,

24 assert (project_path / "pyproject.toml").exists() 1yiWXzYAjZ0B1Ck23D4El56F7Gm89H!In#$J%Ko'(L)Mp*+N,

25 

26 

27def test_creates_project_successfully(temp_project_dir: Path) -> None: 1abcdefgh

28 result = runner.invoke(app, ["my_fastapi_project"]) 1yACEGIKM

29 

30 assert result.exit_code == 0 1yACEGIKM

31 project_path = temp_project_dir / "my_fastapi_project" 1yACEGIKM

32 _assert_project_created(project_path) 1yACEGIKM

33 assert "Success!" in result.output 1yACEGIKM

34 assert "my_fastapi_project" in result.output 1yACEGIKM

35 

36 

37def test_creates_project_with_python_version(temp_project_dir: Path) -> None: 1abcdefgh

38 # Test long form 

39 result = runner.invoke(app, ["project_long", "--python", "3.12"]) 1ijklmnop

40 assert result.exit_code == 0 1ijklmnop

41 project_path = temp_project_dir / "project_long" 1ijklmnop

42 _assert_project_created(project_path) 1ijklmnop

43 assert "3.12" in (project_path / "pyproject.toml").read_text() 1ijklmnop

44 

45 # Test short form 

46 result = runner.invoke(app, ["project_short", "-p", "3.11"]) 1ijklmnop

47 assert result.exit_code == 0 1ijklmnop

48 project_path = temp_project_dir / "project_short" 1ijklmnop

49 assert "3.11" in (project_path / "pyproject.toml").read_text() 1ijklmnop

50 

51 

52def test_validates_template_file_contents(temp_project_dir: Path) -> None: 1abcdefgh

53 result = runner.invoke(app, ["sample_project"]) 1qrstuvwx

54 assert result.exit_code == 0 1qrstuvwx

55 

56 project_path = temp_project_dir / "sample_project" 1qrstuvwx

57 

58 main_py_content = (project_path / "main.py").read_text() 1qrstuvwx

59 assert "from fastapi import FastAPI" in main_py_content 1qrstuvwx

60 assert "app = FastAPI()" in main_py_content 1qrstuvwx

61 

62 # Check README.md 

63 readme_content = (project_path / "README.md").read_text() 1qrstuvwx

64 assert "# sample_project" in readme_content 1qrstuvwx

65 assert "A project created with FastAPI" in readme_content 1qrstuvwx

66 

67 # Check pyproject.toml 

68 pyproject_content = (project_path / "pyproject.toml").read_text() 1qrstuvwx

69 assert 'name = "sample-project"' in pyproject_content 1qrstuvwx

70 assert "fastapi[standard]" in pyproject_content 1qrstuvwx

71 

72 

73def test_initializes_in_current_directory(temp_project_dir: Path) -> None: 1abcdefgh

74 result = runner.invoke(app, []) 1X0369$(+

75 

76 assert result.exit_code == 0 1X0369$(+

77 assert "Initializing in current directory" in result.output 1X0369$(+

78 _assert_project_created(temp_project_dir) 1X0369$(+

79 

80 

81def test_initializes_in_current_directory_with_dot(temp_project_dir: Path) -> None: 1abcdefgh

82 result = runner.invoke(app, ["."]) 1zBDFHJLN

83 

84 assert result.exit_code == 0 1zBDFHJLN

85 assert "Initializing in current directory" in result.output 1zBDFHJLN

86 _assert_project_created(temp_project_dir) 1zBDFHJLN

87 

88 readme_content = (temp_project_dir / "README.md").read_text() 1zBDFHJLN

89 assert f"# {temp_project_dir.name}" in readme_content 1zBDFHJLN

90 

91 

92def test_rejects_existing_directory(temp_project_dir: Path) -> None: 1abcdefgh

93 existing_dir = temp_project_dir / "existing_project" 2~ abbbcbdbebfbgb

94 existing_dir.mkdir() 2~ abbbcbdbebfbgb

95 

96 result = runner.invoke(app, ["existing_project"]) 2~ abbbcbdbebfbgb

97 assert result.exit_code == 1 2~ abbbcbdbebfbgb

98 assert "Directory 'existing_project' already exists." in result.output 2~ abbbcbdbebfbgb

99 

100 

101def test_rejects_python_below_3_10(temp_project_dir: Path) -> None: 1abcdefgh

102 result = runner.invoke(app, ["test_project", "--python", "3.9"]) 2pbqbrbsbtbubvbwb

103 assert result.exit_code == 1 2pbqbrbsbtbubvbwb

104 assert "Python 3.9 is not supported" in result.output 2pbqbrbsbtbubvbwb

105 assert "FastAPI requires Python 3.10" in result.output 2pbqbrbsbtbubvbwb

106 

107 

108def test_passes_single_digit_python_version_to_uv(temp_project_dir: Path) -> None: 1abcdefgh

109 result = runner.invoke(app, ["test_project", "--python", "3"]) 1Y147!%),

110 assert result.exit_code == 0 1Y147!%),

111 project_path = temp_project_dir / "test_project" 1Y147!%),

112 _assert_project_created(project_path) 1Y147!%),

113 

114 

115def test_passes_malformed_python_version_to_uv(temp_project_dir: Path) -> None: 1abcdefgh

116 result = runner.invoke(app, ["test_project", "--python", "abc.def"]) 2xbybzbAbBbCbDbEb

117 # uv will reject this, we just verify we don't crash during validation 

118 assert result.exit_code == 1 2xbybzbAbBbCbDbEb

119 

120 

121def test_creates_project_without_python_flag(temp_project_dir: Path) -> None: 1abcdefgh

122 result = runner.invoke(app, ["test_project"]) 1WZ258#'*

123 assert result.exit_code == 0 1WZ258#'*

124 project_path = temp_project_dir / "test_project" 1WZ258#'*

125 _assert_project_created(project_path) 1WZ258#'*

126 

127 

128def test_failed_to_initialize_with_uv(monkeypatch: Any) -> None: 1abcdefgh

129 def mock_run(*args: Any, **kwargs: Any) -> None: 1-./:;=?@

130 # Let the first check for 'uv' succeed, but fail on 'uv init' 

131 if args[0][0] == "uv" and args[0][1] == "init": 1-./:;=?@

132 raise subprocess.CalledProcessError( 1-./:;=?@

133 1, args[0], stderr=b"uv init failed for some reason" 

134 ) 

135 

136 monkeypatch.setattr(subprocess, "run", mock_run) 1-./:;=?@

137 

138 result = runner.invoke(app, ["failing_project"]) 1-./:;=?@

139 assert result.exit_code == 1 1-./:;=?@

140 assert "Failed to initialize project with uv" in result.output 1-./:;=?@

141 

142 

143def test_failed_to_add_dependencies(temp_project_dir: Path, monkeypatch: Any) -> None: 1abcdefgh

144 def mock_run(*args: Any, **kwargs: Any) -> None: 1[]^_`{|}

145 # Let 'uv init' succeed, but fail on 'uv add' 

146 if args[0][0] == "uv" and args[0][1] == "add": 1[]^_`{|}

147 raise subprocess.CalledProcessError( 1[]^_`{|}

148 1, args[0], stderr=b"Failed to resolve dependencies" 

149 ) 

150 

151 monkeypatch.setattr(subprocess, "run", mock_run) 1[]^_`{|}

152 

153 result = runner.invoke(app, ["failing_deps"]) 1[]^_`{|}

154 assert result.exit_code == 1 1[]^_`{|}

155 assert "Failed to install dependencies" in result.output 1[]^_`{|}

156 

157 

158def test_file_write_failure(temp_project_dir: Path, monkeypatch: Any) -> None: 1abcdefgh

159 original_write_text = Path.write_text 1OPQRSTUV

160 

161 def mock_write_text(self: Path, *args: Any, **kwargs: Any) -> None: 1OPQRSTUV

162 # Fail when trying to write README.md (let main.py succeed first) 

163 if self.name == "README.md": 1OPQRSTUV

164 raise PermissionError("Permission denied") 1OPQRSTUV

165 original_write_text(self, *args, **kwargs) 1OPQRSTUV

166 

167 monkeypatch.setattr(Path, "write_text", mock_write_text) 1OPQRSTUV

168 

169 result = runner.invoke(app, ["test_write_fail"]) 1OPQRSTUV

170 assert result.exit_code == 1 1OPQRSTUV

171 assert "Failed to write template files" in result.output 1OPQRSTUV

172 

173 

174def test_uv_not_installed(temp_project_dir: Path, monkeypatch: Any) -> None: 1abcdefgh

175 monkeypatch.setattr(shutil, "which", lambda _: None) 2hbibjbkblbmbnbob

176 

177 result = runner.invoke(app, ["test_uv_missing_project"]) 2hbibjbkblbmbnbob

178 assert result.exit_code == 1 2hbibjbkblbmbnbob

179 assert "uv is required to create new projects" in result.output 2hbibjbkblbmbnbob

180 assert "https://docs.astral.sh/uv/" in result.output 2hbibjbkblbmbnbob