Skip to content

Commit b12690c

Browse files
committed
โœ… Add tests
1 parent 5700e55 commit b12690c

File tree

7 files changed

+1014
-0
lines changed

7 files changed

+1014
-0
lines changed

โ€Žtests/conftest.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import sys
2+
from typing import Generator
3+
4+
import pytest
5+
from fastapi_cli.logging import setup_logging
6+
from typer import rich_utils
7+
8+
9+
@pytest.fixture(autouse=True)
10+
def reset_syspath() -> Generator[None, None, None]:
11+
initial_python_path = sys.path.copy()
12+
try:
13+
yield
14+
finally:
15+
sys.path = initial_python_path
16+
17+
18+
@pytest.fixture(autouse=True, scope="session")
19+
def set_terminal_width() -> None:
20+
rich_utils.MAX_WIDTH = 3000
21+
setup_logging(terminal_width=3000)
22+
return

โ€Žtests/test_cli.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import subprocess
2+
import sys
3+
from pathlib import Path
4+
from unittest.mock import patch
5+
6+
import uvicorn
7+
from fastapi_cli.cli import app
8+
from typer.testing import CliRunner
9+
10+
from tests.utils import changing_dir
11+
12+
runner = CliRunner()
13+
14+
assets_path = Path(__file__).parent / "assets"
15+
16+
17+
def test_dev() -> None:
18+
with changing_dir(assets_path):
19+
with patch.object(uvicorn, "run") as mock_run:
20+
result = runner.invoke(app, ["dev", "single_file_app.py"])
21+
assert result.exit_code == 0, result.output
22+
assert mock_run.called
23+
assert mock_run.call_args
24+
assert mock_run.call_args.kwargs == {
25+
"app": "single_file_app:app",
26+
"host": "127.0.0.1",
27+
"port": 8000,
28+
"reload": True,
29+
"root_path": "",
30+
}
31+
assert "Using import string single_file_app:app" in result.output
32+
assert (
33+
"โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ FastAPI CLI - Development mode โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" in result.output
34+
)
35+
assert "โ”‚ Serving at: http://127.0.0.1:8000" in result.output
36+
assert "โ”‚ API docs: http://127.0.0.1:8000/docs" in result.output
37+
assert "โ”‚ Running in development mode, for production use:" in result.output
38+
assert "โ”‚ fastapi run" in result.output
39+
40+
41+
def test_dev_args() -> None:
42+
with changing_dir(assets_path):
43+
with patch.object(uvicorn, "run") as mock_run:
44+
result = runner.invoke(
45+
app,
46+
[
47+
"dev",
48+
"single_file_app.py",
49+
"--host",
50+
"192.168.0.2",
51+
"--port",
52+
"8080",
53+
"--no-reload",
54+
"--root-path",
55+
"/api",
56+
"--app",
57+
"api",
58+
],
59+
)
60+
assert result.exit_code == 0, result.output
61+
assert mock_run.called
62+
assert mock_run.call_args
63+
assert mock_run.call_args.kwargs == {
64+
"app": "single_file_app:api",
65+
"host": "192.168.0.2",
66+
"port": 8080,
67+
"reload": False,
68+
"root_path": "/api",
69+
}
70+
assert "Using import string single_file_app:api" in result.output
71+
assert (
72+
"โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ FastAPI CLI - Development mode โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" in result.output
73+
)
74+
assert "โ”‚ Serving at: http://192.168.0.2:8080" in result.output
75+
assert "โ”‚ API docs: http://192.168.0.2:8080/docs" in result.output
76+
assert "โ”‚ Running in development mode, for production use:" in result.output
77+
assert "โ”‚ fastapi run" in result.output
78+
79+
80+
def test_run() -> None:
81+
with changing_dir(assets_path):
82+
with patch.object(uvicorn, "run") as mock_run:
83+
result = runner.invoke(app, ["run", "single_file_app.py"])
84+
assert result.exit_code == 0, result.output
85+
assert mock_run.called
86+
assert mock_run.call_args
87+
assert mock_run.call_args.kwargs == {
88+
"app": "single_file_app:app",
89+
"host": "0.0.0.0",
90+
"port": 8000,
91+
"reload": False,
92+
"root_path": "",
93+
}
94+
assert "Using import string single_file_app:app" in result.output
95+
assert (
96+
"โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ FastAPI CLI - Production mode โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" in result.output
97+
)
98+
assert "โ”‚ Serving at: http://0.0.0.0:8000" in result.output
99+
assert "โ”‚ API docs: http://0.0.0.0:8000/docs" in result.output
100+
assert "โ”‚ Running in production mode, for development use:" in result.output
101+
assert "โ”‚ fastapi dev" in result.output
102+
103+
104+
def test_run_args() -> None:
105+
with changing_dir(assets_path):
106+
with patch.object(uvicorn, "run") as mock_run:
107+
result = runner.invoke(
108+
app,
109+
[
110+
"run",
111+
"single_file_app.py",
112+
"--host",
113+
"192.168.0.2",
114+
"--port",
115+
"8080",
116+
"--no-reload",
117+
"--root-path",
118+
"/api",
119+
"--app",
120+
"api",
121+
],
122+
)
123+
assert result.exit_code == 0, result.output
124+
assert mock_run.called
125+
assert mock_run.call_args
126+
assert mock_run.call_args.kwargs == {
127+
"app": "single_file_app:api",
128+
"host": "192.168.0.2",
129+
"port": 8080,
130+
"reload": False,
131+
"root_path": "/api",
132+
}
133+
assert "Using import string single_file_app:api" in result.output
134+
assert (
135+
"โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ FastAPI CLI - Production mode โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" in result.output
136+
)
137+
assert "โ”‚ Serving at: http://192.168.0.2:8080" in result.output
138+
assert "โ”‚ API docs: http://192.168.0.2:8080/docs" in result.output
139+
assert "โ”‚ Running in production mode, for development use:" in result.output
140+
assert "โ”‚ fastapi dev" in result.output
141+
142+
143+
def test_run_error() -> None:
144+
with changing_dir(assets_path):
145+
result = runner.invoke(app, ["run", "non_existing_file.py"])
146+
assert result.exit_code == 1, result.output
147+
assert "Path does not exist non_existing_file.py" in result.output
148+
149+
150+
def test_dev_help() -> None:
151+
result = runner.invoke(app, ["dev", "--help"])
152+
assert result.exit_code == 0, result.output
153+
assert "Run a FastAPI app in development mode." in result.output
154+
assert (
155+
"This is equivalent to fastapi run but with reload enabled and listening on the 127.0.0.1 address."
156+
in result.output
157+
)
158+
assert (
159+
"Otherwise, it uses the first FastAPI app found in the imported module or package."
160+
in result.output
161+
)
162+
assert "A path to a Python file or package directory" in result.output
163+
assert "The host to serve on." in result.output
164+
assert "The port to serve on." in result.output
165+
assert "Enable auto-reload of the server when (code) files change." in result.output
166+
assert "The root path is used to tell your app" in result.output
167+
assert "The name of the variable that contains the FastAPI app" in result.output
168+
169+
170+
def test_run_help() -> None:
171+
result = runner.invoke(app, ["run", "--help"])
172+
assert result.exit_code == 0, result.output
173+
assert "Run a FastAPI app in production mode." in result.output
174+
assert (
175+
"This is equivalent to fastapi dev but with reload disabled and listening on the 0.0.0.0 address."
176+
in result.output
177+
)
178+
assert (
179+
"Otherwise, it uses the first FastAPI app found in the imported module or package."
180+
in result.output
181+
)
182+
assert "A path to a Python file or package directory" in result.output
183+
assert "The host to serve on." in result.output
184+
assert "The port to serve on." in result.output
185+
assert "Enable auto-reload of the server when (code) files change." in result.output
186+
assert "The root path is used to tell your app" in result.output
187+
assert "The name of the variable that contains the FastAPI app" in result.output
188+
189+
190+
def test_callback_help() -> None:
191+
result = runner.invoke(app, ["--help"])
192+
assert result.exit_code == 0, result.output
193+
assert "FastAPI CLI - The fastapi command line app." in result.output
194+
assert "Show the version and exit." in result.output
195+
196+
197+
def test_version() -> None:
198+
result = runner.invoke(app, ["--version"])
199+
assert result.exit_code == 0, result.output
200+
assert "FastAPI CLI version:" in result.output
201+
202+
203+
def test_script() -> None:
204+
result = subprocess.run(
205+
[sys.executable, "-m", "coverage", "run", "-m", "fastapi_cli", "--help"],
206+
capture_output=True,
207+
encoding="utf-8",
208+
)
209+
assert "Usage" in result.stdout

โ€Žtests/test_utils_default_dir.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
from pathlib import Path
2+
3+
import pytest
4+
from fastapi_cli.discover import get_import_string
5+
from fastapi_cli.exceptions import FastAPICLIException
6+
from pytest import CaptureFixture
7+
8+
from .utils import changing_dir
9+
10+
assets_path = Path(__file__).parent / "assets"
11+
12+
13+
def test_app_dir_main(capsys: CaptureFixture[str]) -> None:
14+
with changing_dir(assets_path / "default_files" / "default_app_dir_main"):
15+
import_string = get_import_string()
16+
assert import_string == "app.main:app"
17+
18+
captured = capsys.readouterr()
19+
assert "Using path app/main.py" in captured.out
20+
assert "Resolved absolute path" in captured.out
21+
assert (
22+
"/fastapi-cli/tests/assets/default_files/default_app_dir_main/app/main.py"
23+
in captured.out
24+
)
25+
assert "Importing from" in captured.out
26+
assert "fastapi-cli/tests/assets/default_files/default_app_dir_main" in captured.out
27+
assert "โ•ญโ”€ Python package file structure โ”€โ•ฎ" in captured.out
28+
assert "โ”‚ ๐Ÿ“ app" in captured.out
29+
assert "โ”‚ โ”œโ”€โ”€ ๐Ÿ __init__.py" in captured.out
30+
assert "โ”‚ โ””โ”€โ”€ ๐Ÿ main.py" in captured.out
31+
assert "Importing module app.main" in captured.out
32+
assert "Found importable FastAPI app" in captured.out
33+
assert "Importable FastAPI app" in captured.out
34+
assert "from app.main import app" in captured.out
35+
assert "Using import string app.main:app" in captured.out
36+
37+
38+
def test_app_dir_app(capsys: CaptureFixture[str]) -> None:
39+
with changing_dir(assets_path / "default_files" / "default_app_dir_app"):
40+
import_string = get_import_string()
41+
assert import_string == "app.app:app"
42+
43+
captured = capsys.readouterr()
44+
assert "Using path app/app.py" in captured.out
45+
assert "Resolved absolute path" in captured.out
46+
assert (
47+
"/fastapi-cli/tests/assets/default_files/default_app_dir_app/app/app.py"
48+
in captured.out
49+
)
50+
assert "Importing from" in captured.out
51+
assert "fastapi-cli/tests/assets/default_files/default_app_dir_app" in captured.out
52+
assert "โ•ญโ”€ Python package file structure โ”€โ•ฎ" in captured.out
53+
assert "โ”‚ ๐Ÿ“ app" in captured.out
54+
assert "โ”‚ โ”œโ”€โ”€ ๐Ÿ __init__.py" in captured.out
55+
assert "โ”‚ โ””โ”€โ”€ ๐Ÿ app.py" in captured.out
56+
assert "Importing module app.app" in captured.out
57+
assert "Found importable FastAPI app" in captured.out
58+
assert "Importable FastAPI app" in captured.out
59+
assert "from app.app import app" in captured.out
60+
assert "Using import string app.app:app" in captured.out
61+
62+
63+
def test_app_dir_api(capsys: CaptureFixture[str]) -> None:
64+
with changing_dir(assets_path / "default_files" / "default_app_dir_api"):
65+
import_string = get_import_string()
66+
assert import_string == "app.api:app"
67+
68+
captured = capsys.readouterr()
69+
assert "Using path app/api.py" in captured.out
70+
assert "Resolved absolute path" in captured.out
71+
assert (
72+
"/fastapi-cli/tests/assets/default_files/default_app_dir_api/app/api.py"
73+
in captured.out
74+
)
75+
assert "Importing from" in captured.out
76+
assert "fastapi-cli/tests/assets/default_files/default_app_dir_api" in captured.out
77+
assert "โ•ญโ”€ Python package file structure โ”€โ•ฎ" in captured.out
78+
assert "โ”‚ ๐Ÿ“ app" in captured.out
79+
assert "โ”‚ โ”œโ”€โ”€ ๐Ÿ __init__.py" in captured.out
80+
assert "โ”‚ โ””โ”€โ”€ ๐Ÿ api.py" in captured.out
81+
assert "Importing module app.api" in captured.out
82+
assert "Found importable FastAPI app" in captured.out
83+
assert "Importable FastAPI app" in captured.out
84+
assert "from app.api import app" in captured.out
85+
assert "Using import string app.api:app" in captured.out
86+
87+
88+
def test_app_dir_non_default() -> None:
89+
with changing_dir(assets_path / "default_files" / "default_app_dir_non_default"):
90+
with pytest.raises(FastAPICLIException) as e:
91+
get_import_string()
92+
assert (
93+
"Could not find a default file to run, please provide an explicit path"
94+
in e.value.args[0]
95+
)

0 commit comments

Comments
ย (0)