fastapi-cloud-cli 0.1.1__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/PKG-INFO +2 -2
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/pyproject.toml +2 -2
- fastapi_cloud_cli-0.1.2/src/fastapi_cloud_cli/__init__.py +1 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/commands/deploy.py +3 -3
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/commands/env.py +1 -3
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/commands/whoami.py +2 -2
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/utils/cli.py +1 -1
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/conftest.py +18 -6
- fastapi_cloud_cli-0.1.2/tests/test_cli.py +11 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/test_cli_deploy.py +146 -0
- fastapi_cloud_cli-0.1.2/tests/test_cli_login.py +163 -0
- fastapi_cloud_cli-0.1.2/tests/test_cli_logout.py +31 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/test_cli_whoami.py +18 -5
- fastapi_cloud_cli-0.1.2/tests/test_deploy_utils.py +64 -0
- fastapi_cloud_cli-0.1.2/tests/test_sentry.py +22 -0
- fastapi_cloud_cli-0.1.1/src/fastapi_cloud_cli/__init__.py +0 -1
- fastapi_cloud_cli-0.1.1/tests/test_cli_login.py +0 -65
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/LICENSE +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/README.md +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/requirements-tests.txt +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/requirements.txt +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/scripts/format.sh +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/scripts/lint.sh +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/scripts/test-cov-html.sh +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/scripts/test.sh +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/__main__.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/cli.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/commands/__init__.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/commands/login.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/commands/logout.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/config.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/logging.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/py.typed +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/utils/__init__.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/utils/api.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/utils/apps.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/utils/auth.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/utils/config.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/utils/env.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/utils/sentry.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/__init__.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/broken_package/mod/__init__.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/broken_package/mod/app.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/broken_package/utils.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_api/api.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_app/api.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_app/app.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_app_dir_api/app/__init__.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_app_dir_api/app/api.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_app_dir_app/app/__init__.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_app_dir_app/app/api.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_app_dir_app/app/app.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_app_dir_main/app/__init__.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_app_dir_main/app/api.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_app_dir_main/app/app.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_app_dir_main/app/main.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_app_dir_non_default/app/__init__.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_app_dir_non_default/app/nondefault.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_main/api.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_main/app.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_main/main.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/non_default/nonstandard.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/package/__init__.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/package/core/__init__.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/package/core/utils.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/package/mod/__init__.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/package/mod/api.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/package/mod/app.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/package/mod/other.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/single_file_api.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/single_file_app.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/single_file_other.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/test_config.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/test_env_delete.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/test_env_list.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/test_env_set.py +0 -0
- {fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fastapi-cloud-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Deploy and manage FastAPI Cloud apps from the command line 🚀
|
|
5
5
|
Author-Email: Patrick Arminio <patrick@fastapilabs.com>
|
|
6
6
|
License: MIT
|
|
@@ -35,7 +35,7 @@ Requires-Python: >=3.8
|
|
|
35
35
|
Requires-Dist: typer>=0.12.3
|
|
36
36
|
Requires-Dist: uvicorn[standard]>=0.15.0
|
|
37
37
|
Requires-Dist: rignore>=0.5.1
|
|
38
|
-
Requires-Dist: httpx
|
|
38
|
+
Requires-Dist: httpx>=0.27.0
|
|
39
39
|
Requires-Dist: rich-toolkit>=0.14.5
|
|
40
40
|
Requires-Dist: pydantic[email]>=1.6.1
|
|
41
41
|
Requires-Dist: sentry-sdk>=2.20.0
|
|
@@ -35,12 +35,12 @@ dependencies = [
|
|
|
35
35
|
"typer >= 0.12.3",
|
|
36
36
|
"uvicorn[standard] >= 0.15.0",
|
|
37
37
|
"rignore >= 0.5.1",
|
|
38
|
-
"httpx >= 0.27.0
|
|
38
|
+
"httpx >= 0.27.0",
|
|
39
39
|
"rich-toolkit >= 0.14.5",
|
|
40
40
|
"pydantic[email] >= 1.6.1",
|
|
41
41
|
"sentry-sdk >= 2.20.0",
|
|
42
42
|
]
|
|
43
|
-
version = "0.1.
|
|
43
|
+
version = "0.1.2"
|
|
44
44
|
|
|
45
45
|
[project.license]
|
|
46
46
|
text = "MIT"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.2"
|
{fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/commands/deploy.py
RENAMED
|
@@ -375,12 +375,12 @@ def _wait_for_deployment(
|
|
|
375
375
|
raise typer.Exit(1)
|
|
376
376
|
|
|
377
377
|
if time_elapsed > 30:
|
|
378
|
-
messages = cycle(LONG_WAIT_MESSAGES)
|
|
378
|
+
messages = cycle(LONG_WAIT_MESSAGES) # pragma: no cover
|
|
379
379
|
|
|
380
380
|
if (time.monotonic() - last_message_changed_at) > 2:
|
|
381
|
-
progress.title = next(messages)
|
|
381
|
+
progress.title = next(messages) # pragma: no cover
|
|
382
382
|
|
|
383
|
-
last_message_changed_at = time.monotonic()
|
|
383
|
+
last_message_changed_at = time.monotonic() # pragma: no cover
|
|
384
384
|
|
|
385
385
|
|
|
386
386
|
def _setup_environment_variables(toolkit: RichToolkit, app_id: str) -> None:
|
|
@@ -161,11 +161,9 @@ def delete(
|
|
|
161
161
|
{"name": env_var.name, "value": env_var.name}
|
|
162
162
|
for env_var in environment_variables.data
|
|
163
163
|
],
|
|
164
|
-
default=None,
|
|
165
164
|
)
|
|
166
165
|
|
|
167
|
-
|
|
168
|
-
return
|
|
166
|
+
assert name
|
|
169
167
|
else:
|
|
170
168
|
if not validate_environment_variable_name(name):
|
|
171
169
|
toolkit.print(
|
{fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/commands/whoami.py
RENAMED
|
@@ -17,11 +17,11 @@ def whoami() -> Any:
|
|
|
17
17
|
return
|
|
18
18
|
|
|
19
19
|
with APIClient() as client:
|
|
20
|
-
with Progress(title="⚡Fetching profile", transient=True) as progress:
|
|
20
|
+
with Progress(title="⚡ Fetching profile", transient=True) as progress:
|
|
21
21
|
with handle_http_errors(progress, message=""):
|
|
22
22
|
response = client.get("/users/me")
|
|
23
23
|
response.raise_for_status()
|
|
24
24
|
|
|
25
25
|
data = response.json()
|
|
26
26
|
|
|
27
|
-
print(f"⚡[bold]{data['email']}[/bold]")
|
|
27
|
+
print(f"⚡ [bold]{data['email']}[/bold]")
|
|
@@ -85,7 +85,7 @@ def handle_http_errors(
|
|
|
85
85
|
|
|
86
86
|
# Handle validation errors from Pydantic models, this should make it easier to debug :)
|
|
87
87
|
if isinstance(e, HTTPStatusError) and e.response.status_code == 422:
|
|
88
|
-
logger.debug(e.response.json())
|
|
88
|
+
logger.debug(e.response.json()) # pragma: no cover
|
|
89
89
|
|
|
90
90
|
if isinstance(e, HTTPStatusError) and e.response.status_code in (401, 403):
|
|
91
91
|
message = "The specified token is not valid. Use `fastapi login` to generate a new token."
|
|
@@ -25,15 +25,17 @@ def setup_terminal() -> None:
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
@pytest.fixture
|
|
28
|
-
def logged_in_cli() -> Generator[None, None, None]:
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
def logged_in_cli(temp_auth_config: Path) -> Generator[None, None, None]:
|
|
29
|
+
temp_auth_config.write_text('{"access_token": "test_token_12345"}')
|
|
30
|
+
|
|
31
|
+
yield
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
@pytest.fixture
|
|
34
|
-
def logged_out_cli() -> Generator[None, None, None]:
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
def logged_out_cli(temp_auth_config: Path) -> Generator[None, None, None]:
|
|
36
|
+
assert not temp_auth_config.exists()
|
|
37
|
+
|
|
38
|
+
yield
|
|
37
39
|
|
|
38
40
|
|
|
39
41
|
@dataclass
|
|
@@ -54,3 +56,13 @@ def configured_app(tmp_path: Path) -> ConfiguredApp:
|
|
|
54
56
|
config_path.write_text(f'{{"app_id": "{app_id}", "team_id": "{team_id}"}}')
|
|
55
57
|
|
|
56
58
|
return ConfiguredApp(app_id=app_id, team_id=team_id, path=tmp_path)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@pytest.fixture
|
|
62
|
+
def temp_auth_config(tmp_path: Path) -> Generator[Path, None, None]:
|
|
63
|
+
"""Provides a temporary auth config setup for testing file operations."""
|
|
64
|
+
|
|
65
|
+
with patch(
|
|
66
|
+
"fastapi_cloud_cli.utils.config.get_config_folder", return_value=tmp_path
|
|
67
|
+
):
|
|
68
|
+
yield tmp_path / "auth.json"
|
|
@@ -652,3 +652,149 @@ def test_does_not_duplicate_entry_in_git_ignore(
|
|
|
652
652
|
_deploy_without_waiting(respx_mock, tmp_path)
|
|
653
653
|
|
|
654
654
|
assert git_ignore_path.read_text() == ".fastapicloud\n"
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
658
|
+
def test_creates_environment_variables_during_app_setup(
|
|
659
|
+
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
|
|
660
|
+
) -> None:
|
|
661
|
+
steps = [
|
|
662
|
+
Keys.ENTER, # Setup and deploy
|
|
663
|
+
Keys.ENTER, # Select team
|
|
664
|
+
Keys.ENTER, # Create new app
|
|
665
|
+
*"demo", # App name
|
|
666
|
+
Keys.ENTER,
|
|
667
|
+
Keys.ENTER, # Setup environment variables (Yes)
|
|
668
|
+
*"API_KEY", # Environment variable name
|
|
669
|
+
Keys.ENTER,
|
|
670
|
+
*"secret123", # Environment variable value
|
|
671
|
+
Keys.ENTER,
|
|
672
|
+
Keys.ENTER, # Empty key to finish
|
|
673
|
+
Keys.CTRL_C, # Exit before deployment
|
|
674
|
+
]
|
|
675
|
+
|
|
676
|
+
team = _get_random_team()
|
|
677
|
+
app_data = _get_random_app(team_id=team["id"])
|
|
678
|
+
|
|
679
|
+
respx_mock.get("/teams/").mock(return_value=Response(200, json={"data": [team]}))
|
|
680
|
+
|
|
681
|
+
respx_mock.post("/apps/", json={"name": "demo", "team_id": team["id"]}).mock(
|
|
682
|
+
return_value=Response(201, json=app_data)
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
env_vars_request = respx_mock.patch(
|
|
686
|
+
f"/apps/{app_data['id']}/environment-variables/", json={"API_KEY": "secret123"}
|
|
687
|
+
).mock(return_value=Response(200))
|
|
688
|
+
|
|
689
|
+
with changing_dir(tmp_path), patch("click.getchar") as mock_getchar:
|
|
690
|
+
mock_getchar.side_effect = steps
|
|
691
|
+
|
|
692
|
+
result = runner.invoke(app, ["deploy"])
|
|
693
|
+
|
|
694
|
+
assert result.exit_code == 1
|
|
695
|
+
assert env_vars_request.called
|
|
696
|
+
assert "Environment variables set up successfully!" in result.output
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
700
|
+
def test_rejects_invalid_environment_variable_names(
|
|
701
|
+
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
|
|
702
|
+
) -> None:
|
|
703
|
+
steps = [
|
|
704
|
+
Keys.ENTER, # Setup and deploy
|
|
705
|
+
Keys.ENTER, # Select team
|
|
706
|
+
Keys.ENTER, # Create new app
|
|
707
|
+
*"demo", # App name
|
|
708
|
+
Keys.ENTER,
|
|
709
|
+
Keys.ENTER, # Setup environment variables (Yes)
|
|
710
|
+
*"123-invalid", # Invalid environment variable name (starts with digit, contains hyphen)
|
|
711
|
+
Keys.ENTER,
|
|
712
|
+
*"VALID_KEY", # Valid environment variable name
|
|
713
|
+
Keys.ENTER,
|
|
714
|
+
*"value123", # Environment variable value
|
|
715
|
+
Keys.ENTER,
|
|
716
|
+
Keys.ENTER, # Empty key to finish
|
|
717
|
+
Keys.CTRL_C, # Exit before deployment
|
|
718
|
+
]
|
|
719
|
+
|
|
720
|
+
team = _get_random_team()
|
|
721
|
+
app_data = _get_random_app(team_id=team["id"])
|
|
722
|
+
|
|
723
|
+
respx_mock.get("/teams/").mock(return_value=Response(200, json={"data": [team]}))
|
|
724
|
+
|
|
725
|
+
respx_mock.post("/apps/", json={"name": "demo", "team_id": team["id"]}).mock(
|
|
726
|
+
return_value=Response(201, json=app_data)
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
env_vars_request = respx_mock.patch(
|
|
730
|
+
f"/apps/{app_data['id']}/environment-variables/", json={"VALID_KEY": "value123"}
|
|
731
|
+
).mock(return_value=Response(200))
|
|
732
|
+
|
|
733
|
+
with changing_dir(tmp_path), patch("click.getchar") as mock_getchar:
|
|
734
|
+
mock_getchar.side_effect = steps
|
|
735
|
+
|
|
736
|
+
result = runner.invoke(app, ["deploy"])
|
|
737
|
+
|
|
738
|
+
assert result.exit_code == 1
|
|
739
|
+
assert env_vars_request.called
|
|
740
|
+
assert "Invalid environment variable name." in result.output
|
|
741
|
+
assert "Environment variables set up successfully!" in result.output
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
745
|
+
def test_shows_error_for_invalid_waitlist_form_data(
|
|
746
|
+
logged_out_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
|
|
747
|
+
) -> None:
|
|
748
|
+
steps = [
|
|
749
|
+
*"test@example.com",
|
|
750
|
+
Keys.ENTER,
|
|
751
|
+
Keys.ENTER, # Choose to provide more information
|
|
752
|
+
Keys.CTRL_C, # Interrupt to avoid infinite loop
|
|
753
|
+
]
|
|
754
|
+
|
|
755
|
+
with changing_dir(tmp_path), patch(
|
|
756
|
+
"rich_toolkit.menu.click.getchar"
|
|
757
|
+
) as mock_getchar, patch("rich_toolkit.form.Form.run") as mock_form_run:
|
|
758
|
+
mock_getchar.side_effect = steps
|
|
759
|
+
# Simulate form returning data with invalid email field to trigger ValidationError
|
|
760
|
+
mock_form_run.return_value = {
|
|
761
|
+
"email": "invalid-email-format",
|
|
762
|
+
"name": "John Doe",
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
result = runner.invoke(app, ["deploy"])
|
|
766
|
+
|
|
767
|
+
assert result.exit_code == 1
|
|
768
|
+
assert "Invalid form data. Please try again." in result.output
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
772
|
+
def test_shows_no_apps_found_message_when_team_has_no_apps(
|
|
773
|
+
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
|
|
774
|
+
) -> None:
|
|
775
|
+
steps = [
|
|
776
|
+
Keys.ENTER, # Setup and deploy
|
|
777
|
+
Keys.ENTER, # Select team
|
|
778
|
+
Keys.RIGHT_ARROW, # Choose existing app (No)
|
|
779
|
+
Keys.ENTER,
|
|
780
|
+
]
|
|
781
|
+
|
|
782
|
+
team = _get_random_team()
|
|
783
|
+
|
|
784
|
+
respx_mock.get("/teams/").mock(return_value=Response(200, json={"data": [team]}))
|
|
785
|
+
|
|
786
|
+
# Mock empty apps list for the team
|
|
787
|
+
respx_mock.get("/apps/", params={"team_id": team["id"]}).mock(
|
|
788
|
+
return_value=Response(200, json={"data": []})
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
with changing_dir(tmp_path), patch("click.getchar") as mock_getchar:
|
|
792
|
+
mock_getchar.side_effect = steps
|
|
793
|
+
|
|
794
|
+
result = runner.invoke(app, ["deploy"])
|
|
795
|
+
|
|
796
|
+
assert result.exit_code == 1
|
|
797
|
+
assert (
|
|
798
|
+
"No apps found in this team. You can create a new app instead."
|
|
799
|
+
in result.output
|
|
800
|
+
)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
import pytest
|
|
6
|
+
import respx
|
|
7
|
+
from httpx import Response
|
|
8
|
+
from typer.testing import CliRunner
|
|
9
|
+
|
|
10
|
+
from fastapi_cloud_cli.cli import app
|
|
11
|
+
from fastapi_cloud_cli.config import settings
|
|
12
|
+
|
|
13
|
+
runner = CliRunner()
|
|
14
|
+
|
|
15
|
+
assets_path = Path(__file__).parent / "assets"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
19
|
+
def test_shows_a_message_if_something_is_wrong(respx_mock: respx.MockRouter) -> None:
|
|
20
|
+
with patch("fastapi_cloud_cli.commands.login.typer.launch") as mock_open:
|
|
21
|
+
respx_mock.post(
|
|
22
|
+
"/login/device/authorization", data={"client_id": settings.client_id}
|
|
23
|
+
).mock(return_value=Response(500))
|
|
24
|
+
|
|
25
|
+
result = runner.invoke(app, ["login"])
|
|
26
|
+
|
|
27
|
+
assert result.exit_code == 1
|
|
28
|
+
assert (
|
|
29
|
+
"Something went wrong while contacting the FastAPI Cloud server."
|
|
30
|
+
in result.output
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
assert not mock_open.called
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
37
|
+
def test_full_login(respx_mock: respx.MockRouter, temp_auth_config: Path) -> None:
|
|
38
|
+
with patch("fastapi_cloud_cli.commands.login.typer.launch") as mock_open:
|
|
39
|
+
respx_mock.post(
|
|
40
|
+
"/login/device/authorization", data={"client_id": settings.client_id}
|
|
41
|
+
).mock(
|
|
42
|
+
return_value=Response(
|
|
43
|
+
200,
|
|
44
|
+
json={
|
|
45
|
+
"verification_uri_complete": "http://test.com",
|
|
46
|
+
"verification_uri": "http://test.com",
|
|
47
|
+
"user_code": "1234",
|
|
48
|
+
"device_code": "5678",
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
respx_mock.post(
|
|
53
|
+
"/login/device/token",
|
|
54
|
+
data={
|
|
55
|
+
"device_code": "5678",
|
|
56
|
+
"client_id": settings.client_id,
|
|
57
|
+
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
|
58
|
+
},
|
|
59
|
+
).mock(return_value=Response(200, json={"access_token": "test_token_1234"}))
|
|
60
|
+
|
|
61
|
+
# Verify no auth file exists before login
|
|
62
|
+
assert not temp_auth_config.exists()
|
|
63
|
+
|
|
64
|
+
result = runner.invoke(app, ["login"])
|
|
65
|
+
|
|
66
|
+
assert result.exit_code == 0
|
|
67
|
+
assert mock_open.called
|
|
68
|
+
assert mock_open.call_args.args == ("http://test.com",)
|
|
69
|
+
assert "Now you are logged in!" in result.output
|
|
70
|
+
|
|
71
|
+
# Verify auth file was created with correct content
|
|
72
|
+
assert temp_auth_config.exists()
|
|
73
|
+
assert '"access_token":"test_token_1234"' in temp_auth_config.read_text()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
77
|
+
def test_fetch_access_token_success_immediately(respx_mock: respx.MockRouter) -> None:
|
|
78
|
+
from fastapi_cloud_cli.commands.login import _fetch_access_token
|
|
79
|
+
from fastapi_cloud_cli.utils.api import APIClient
|
|
80
|
+
|
|
81
|
+
respx_mock.post(
|
|
82
|
+
"/login/device/token",
|
|
83
|
+
data={
|
|
84
|
+
"device_code": "test_device_code",
|
|
85
|
+
"client_id": settings.client_id,
|
|
86
|
+
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
|
87
|
+
},
|
|
88
|
+
).mock(return_value=Response(200, json={"access_token": "test_token_success"}))
|
|
89
|
+
|
|
90
|
+
with APIClient() as client:
|
|
91
|
+
access_token = _fetch_access_token(client, "test_device_code", 5)
|
|
92
|
+
|
|
93
|
+
assert access_token == "test_token_success"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
97
|
+
def test_fetch_access_token_authorization_pending_then_success(
|
|
98
|
+
respx_mock: respx.MockRouter,
|
|
99
|
+
) -> None:
|
|
100
|
+
from fastapi_cloud_cli.commands.login import _fetch_access_token
|
|
101
|
+
from fastapi_cloud_cli.utils.api import APIClient
|
|
102
|
+
|
|
103
|
+
# First call returns authorization pending, second call succeeds
|
|
104
|
+
respx_mock.post(
|
|
105
|
+
"/login/device/token",
|
|
106
|
+
data={
|
|
107
|
+
"device_code": "test_device_code",
|
|
108
|
+
"client_id": settings.client_id,
|
|
109
|
+
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
|
110
|
+
},
|
|
111
|
+
).mock(
|
|
112
|
+
side_effect=[
|
|
113
|
+
Response(400, json={"error": "authorization_pending"}),
|
|
114
|
+
Response(200, json={"access_token": "test_token_after_pending"}),
|
|
115
|
+
]
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
with patch("fastapi_cloud_cli.commands.login.time.sleep") as mock_sleep:
|
|
119
|
+
with APIClient() as client:
|
|
120
|
+
access_token = _fetch_access_token(client, "test_device_code", 3)
|
|
121
|
+
|
|
122
|
+
assert access_token == "test_token_after_pending"
|
|
123
|
+
mock_sleep.assert_called_once_with(3)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
127
|
+
def test_fetch_access_token_handles_400_error_not_authorization_pending(
|
|
128
|
+
respx_mock: respx.MockRouter,
|
|
129
|
+
) -> None:
|
|
130
|
+
from fastapi_cloud_cli.commands.login import _fetch_access_token
|
|
131
|
+
from fastapi_cloud_cli.utils.api import APIClient
|
|
132
|
+
|
|
133
|
+
respx_mock.post(
|
|
134
|
+
"/login/device/token",
|
|
135
|
+
data={
|
|
136
|
+
"device_code": "test_device_code",
|
|
137
|
+
"client_id": settings.client_id,
|
|
138
|
+
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
|
139
|
+
},
|
|
140
|
+
).mock(return_value=Response(400, json={"error": "access_denied"}))
|
|
141
|
+
|
|
142
|
+
with APIClient() as client:
|
|
143
|
+
with pytest.raises(httpx.HTTPStatusError):
|
|
144
|
+
_fetch_access_token(client, "test_device_code", 5)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
148
|
+
def test_fetch_access_token_handles_500_error(respx_mock: respx.MockRouter) -> None:
|
|
149
|
+
from fastapi_cloud_cli.commands.login import _fetch_access_token
|
|
150
|
+
from fastapi_cloud_cli.utils.api import APIClient
|
|
151
|
+
|
|
152
|
+
respx_mock.post(
|
|
153
|
+
"/login/device/token",
|
|
154
|
+
data={
|
|
155
|
+
"device_code": "test_device_code",
|
|
156
|
+
"client_id": settings.client_id,
|
|
157
|
+
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
|
158
|
+
},
|
|
159
|
+
).mock(return_value=Response(500))
|
|
160
|
+
|
|
161
|
+
with APIClient() as client:
|
|
162
|
+
with pytest.raises(httpx.HTTPStatusError):
|
|
163
|
+
_fetch_access_token(client, "test_device_code", 5)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from typer.testing import CliRunner
|
|
4
|
+
|
|
5
|
+
from fastapi_cloud_cli.cli import app
|
|
6
|
+
|
|
7
|
+
runner = CliRunner()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_logout_with_existing_auth_file(temp_auth_config: Path) -> None:
|
|
11
|
+
temp_auth_config.write_text('{"access_token": "test_token"}')
|
|
12
|
+
|
|
13
|
+
assert temp_auth_config.exists()
|
|
14
|
+
|
|
15
|
+
result = runner.invoke(app, ["logout"])
|
|
16
|
+
|
|
17
|
+
assert result.exit_code == 0
|
|
18
|
+
assert "You are now logged out! 🚀" in result.output
|
|
19
|
+
|
|
20
|
+
assert not temp_auth_config.exists()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_logout_with_no_auth_file(temp_auth_config: Path) -> None:
|
|
24
|
+
assert not temp_auth_config.exists()
|
|
25
|
+
|
|
26
|
+
result = runner.invoke(app, ["logout"])
|
|
27
|
+
|
|
28
|
+
assert result.exit_code == 0
|
|
29
|
+
assert "You are now logged out! 🚀" in result.output
|
|
30
|
+
|
|
31
|
+
assert not temp_auth_config.exists()
|
|
@@ -14,20 +14,24 @@ assets_path = Path(__file__).parent / "assets"
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
17
|
-
def test_shows_a_message_if_something_is_wrong(
|
|
17
|
+
def test_shows_a_message_if_something_is_wrong(
|
|
18
|
+
logged_in_cli: None, respx_mock: respx.MockRouter
|
|
19
|
+
) -> None:
|
|
18
20
|
respx_mock.get("/users/me").mock(return_value=Response(500))
|
|
19
21
|
|
|
20
22
|
result = runner.invoke(app, ["whoami"])
|
|
21
23
|
|
|
22
|
-
assert result.exit_code == 1
|
|
23
24
|
assert (
|
|
24
25
|
"Something went wrong while contacting the FastAPI Cloud server."
|
|
25
26
|
in result.output
|
|
26
27
|
)
|
|
28
|
+
assert result.exit_code == 1
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
30
|
-
def
|
|
32
|
+
def test_shows_a_message_when_token_is_invalid(
|
|
33
|
+
logged_in_cli: None, respx_mock: respx.MockRouter
|
|
34
|
+
) -> None:
|
|
31
35
|
respx_mock.get("/users/me").mock(return_value=Response(401))
|
|
32
36
|
|
|
33
37
|
result = runner.invoke(app, ["whoami"])
|
|
@@ -37,7 +41,7 @@ def test_shows_a_message_when_not_logged_in(respx_mock: respx.MockRouter) -> Non
|
|
|
37
41
|
|
|
38
42
|
|
|
39
43
|
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
40
|
-
def test_shows_email(respx_mock: respx.MockRouter) -> None:
|
|
44
|
+
def test_shows_email(logged_in_cli: None, respx_mock: respx.MockRouter) -> None:
|
|
41
45
|
respx_mock.get("/users/me").mock(
|
|
42
46
|
return_value=Response(200, json={"email": "email@fastapi.com"})
|
|
43
47
|
)
|
|
@@ -49,10 +53,19 @@ def test_shows_email(respx_mock: respx.MockRouter) -> None:
|
|
|
49
53
|
|
|
50
54
|
|
|
51
55
|
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
52
|
-
def test_handles_read_timeout(
|
|
56
|
+
def test_handles_read_timeout(
|
|
57
|
+
logged_in_cli: None, respx_mock: respx.MockRouter
|
|
58
|
+
) -> None:
|
|
53
59
|
respx_mock.get("/users/me").mock(side_effect=ReadTimeout)
|
|
54
60
|
|
|
55
61
|
result = runner.invoke(app, ["whoami"])
|
|
56
62
|
|
|
57
63
|
assert result.exit_code == 1
|
|
58
64
|
assert "The request to the FastAPI Cloud server timed out" in result.output
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_prints_not_logged_in(logged_out_cli: None) -> None:
|
|
68
|
+
result = runner.invoke(app, ["whoami"])
|
|
69
|
+
|
|
70
|
+
assert result.exit_code == 0
|
|
71
|
+
assert "No credentials found. Use `fastapi login` to login." in result.output
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from fastapi_cloud_cli.commands.deploy import DeploymentStatus, _should_exclude_entry
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.parametrize(
|
|
9
|
+
"path",
|
|
10
|
+
[
|
|
11
|
+
Path("/project/.venv/lib/python3.11/site-packages/some_package"),
|
|
12
|
+
Path("/project/src/__pycache__/module.cpython-311.pyc"),
|
|
13
|
+
Path("/project/.mypy_cache/3.11/module.meta.json"),
|
|
14
|
+
Path("/project/.pytest_cache/v/cache/lastfailed"),
|
|
15
|
+
Path("/project/src/module.pyc"),
|
|
16
|
+
Path("/project/src/subdir/another/module.pyc"),
|
|
17
|
+
Path("/project/subproject/.venv/lib/python3.11/site-packages"),
|
|
18
|
+
Path("/project/.venv/lib/__pycache__/module.pyc"),
|
|
19
|
+
Path(".venv"),
|
|
20
|
+
Path("__pycache__"),
|
|
21
|
+
Path("module.pyc"),
|
|
22
|
+
],
|
|
23
|
+
)
|
|
24
|
+
def test_excludes_paths(path: Path) -> None:
|
|
25
|
+
"""Should exclude paths that match exclusion criteria."""
|
|
26
|
+
assert _should_exclude_entry(path) is True
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.parametrize(
|
|
30
|
+
"path",
|
|
31
|
+
[
|
|
32
|
+
Path("/project/src/module.py"),
|
|
33
|
+
Path("/project/src/utils"),
|
|
34
|
+
Path("/project/src/my_cache_utils.py"),
|
|
35
|
+
Path("/project/venv/lib/python3.11/site-packages"), # no leading dot
|
|
36
|
+
Path("/project/pycache/some_file.py"), # no underscores
|
|
37
|
+
Path("/project/src/module.pyx"), # similar to .pyc but different
|
|
38
|
+
Path("/project/config.json"),
|
|
39
|
+
Path("/project/README.md"),
|
|
40
|
+
],
|
|
41
|
+
)
|
|
42
|
+
def test_includes_paths(path: Path) -> None:
|
|
43
|
+
"""Should not exclude paths that don't match exclusion criteria."""
|
|
44
|
+
assert _should_exclude_entry(path) is False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.mark.parametrize(
|
|
48
|
+
"status,expected",
|
|
49
|
+
[
|
|
50
|
+
(DeploymentStatus.waiting_upload, "Waiting for upload"),
|
|
51
|
+
(DeploymentStatus.ready_for_build, "Ready for build"),
|
|
52
|
+
(DeploymentStatus.building, "Building"),
|
|
53
|
+
(DeploymentStatus.extracting, "Extracting"),
|
|
54
|
+
(DeploymentStatus.building_image, "Building image"),
|
|
55
|
+
(DeploymentStatus.deploying, "Deploying"),
|
|
56
|
+
(DeploymentStatus.success, "Success"),
|
|
57
|
+
(DeploymentStatus.failed, "Failed"),
|
|
58
|
+
],
|
|
59
|
+
)
|
|
60
|
+
def test_deployment_status_to_human_readable(
|
|
61
|
+
status: DeploymentStatus, expected: str
|
|
62
|
+
) -> None:
|
|
63
|
+
"""Should convert deployment status to human readable format."""
|
|
64
|
+
assert DeploymentStatus.to_human_readable(status) == expected
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from unittest.mock import ANY, patch
|
|
3
|
+
|
|
4
|
+
from fastapi_cloud_cli.utils.sentry import SENTRY_DSN, init_sentry
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_init_sentry_when_logged_in(logged_in_cli: Path) -> None:
|
|
8
|
+
with patch("fastapi_cloud_cli.utils.sentry.sentry_sdk.init") as mock_init:
|
|
9
|
+
init_sentry()
|
|
10
|
+
|
|
11
|
+
mock_init.assert_called_once_with(
|
|
12
|
+
dsn=SENTRY_DSN,
|
|
13
|
+
integrations=[ANY], # TyperIntegration instance
|
|
14
|
+
send_default_pii=False,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_init_sentry_when_logged_out(logged_out_cli: Path) -> None:
|
|
19
|
+
with patch("fastapi_cloud_cli.utils.sentry.sentry_sdk.init") as mock_init:
|
|
20
|
+
init_sentry()
|
|
21
|
+
|
|
22
|
+
mock_init.assert_not_called()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.1"
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
from unittest.mock import patch
|
|
3
|
-
|
|
4
|
-
import pytest
|
|
5
|
-
import respx
|
|
6
|
-
from httpx import Response
|
|
7
|
-
from typer.testing import CliRunner
|
|
8
|
-
|
|
9
|
-
from fastapi_cloud_cli.cli import app
|
|
10
|
-
from fastapi_cloud_cli.config import settings
|
|
11
|
-
|
|
12
|
-
runner = CliRunner()
|
|
13
|
-
|
|
14
|
-
assets_path = Path(__file__).parent / "assets"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
18
|
-
def test_shows_a_message_if_something_is_wrong(respx_mock: respx.MockRouter) -> None:
|
|
19
|
-
with patch("fastapi_cloud_cli.commands.login.typer.launch") as mock_open:
|
|
20
|
-
respx_mock.post(
|
|
21
|
-
"/login/device/authorization", data={"client_id": settings.client_id}
|
|
22
|
-
).mock(return_value=Response(500))
|
|
23
|
-
|
|
24
|
-
result = runner.invoke(app, ["login"])
|
|
25
|
-
|
|
26
|
-
assert result.exit_code == 1
|
|
27
|
-
assert (
|
|
28
|
-
"Something went wrong while contacting the FastAPI Cloud server."
|
|
29
|
-
in result.output
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
assert not mock_open.called
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
36
|
-
def test_full_login(respx_mock: respx.MockRouter) -> None:
|
|
37
|
-
with patch("fastapi_cloud_cli.commands.login.typer.launch") as mock_open:
|
|
38
|
-
respx_mock.post(
|
|
39
|
-
"/login/device/authorization", data={"client_id": settings.client_id}
|
|
40
|
-
).mock(
|
|
41
|
-
return_value=Response(
|
|
42
|
-
200,
|
|
43
|
-
json={
|
|
44
|
-
"verification_uri_complete": "http://test.com",
|
|
45
|
-
"verification_uri": "http://test.com",
|
|
46
|
-
"user_code": "1234",
|
|
47
|
-
"device_code": "5678",
|
|
48
|
-
},
|
|
49
|
-
)
|
|
50
|
-
)
|
|
51
|
-
respx_mock.post(
|
|
52
|
-
"/login/device/token",
|
|
53
|
-
data={
|
|
54
|
-
"device_code": "5678",
|
|
55
|
-
"client_id": settings.client_id,
|
|
56
|
-
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
|
57
|
-
},
|
|
58
|
-
).mock(return_value=Response(200, json={"access_token": "1234"}))
|
|
59
|
-
|
|
60
|
-
result = runner.invoke(app, ["login"])
|
|
61
|
-
|
|
62
|
-
assert result.exit_code == 0
|
|
63
|
-
assert mock_open.called
|
|
64
|
-
assert mock_open.call_args.args == ("http://test.com",)
|
|
65
|
-
assert "Now you are logged in!" in result.output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/commands/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/src/fastapi_cloud_cli/commands/logout.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/broken_package/mod/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_api/api.py
RENAMED
|
File without changes
|
{fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_app/api.py
RENAMED
|
File without changes
|
{fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_app/app.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_main/api.py
RENAMED
|
File without changes
|
{fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_main/app.py
RENAMED
|
File without changes
|
{fastapi_cloud_cli-0.1.1 → fastapi_cloud_cli-0.1.2}/tests/assets/default_files/default_main/main.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|