diff --git a/src/fastapi_cloud_cli/commands/deploy.py b/src/fastapi_cloud_cli/commands/deploy.py index 123a4d2..694e574 100644 --- a/src/fastapi_cloud_cli/commands/deploy.py +++ b/src/fastapi_cloud_cli/commands/deploy.py @@ -20,6 +20,7 @@ from rich_toolkit.menu import Option from typing_extensions import Annotated +from fastapi_cloud_cli.commands.login import login from fastapi_cloud_cli.utils.api import APIClient from fastapi_cloud_cli.utils.apps import AppConfig, get_app_config, write_app_config from fastapi_cloud_cli.utils.auth import is_logged_in @@ -550,10 +551,33 @@ def deploy( with get_rich_toolkit() as toolkit: if not is_logged_in(): - logger.debug("User not logged in, showing waitlist form") - _waitlist_form(toolkit) + logger.debug("User not logged in, prompting for login or waitlist") - raise typer.Exit(1) + toolkit.print_title("Welcome to FastAPI Cloud!", tag="FastAPI") + toolkit.print_line() + + toolkit.print( + "You need to be logged in to deploy to FastAPI Cloud.", + tag="info", + ) + toolkit.print_line() + + choice = toolkit.ask( + "What would you like to do?", + tag="auth", + options=[ + Option({"name": "Login to my existing account", "value": "login"}), + Option({"name": "Join the waiting list", "value": "waitlist"}), + ], + ) + + toolkit.print_line() + + if choice == "login": + login() + else: + _waitlist_form(toolkit) + raise typer.Exit(1) toolkit.print_title("Starting deployment", tag="FastAPI") toolkit.print_line() diff --git a/tests/test_cli_deploy.py b/tests/test_cli_deploy.py index 4a09ecb..712a011 100644 --- a/tests/test_cli_deploy.py +++ b/tests/test_cli_deploy.py @@ -60,10 +60,63 @@ def _get_random_deployment( @pytest.mark.respx(base_url=settings.base_api_url) -def test_shows_waitlist_form_when_not_logged_in( +def test_chooses_login_option_when_not_logged_in( logged_out_cli: None, tmp_path: Path, respx_mock: respx.MockRouter ) -> None: - steps = [*"some@example.com", Keys.ENTER, Keys.RIGHT_ARROW, Keys.ENTER, Keys.ENTER] + steps = [Keys.ENTER] + + respx_mock.post( + "/login/device/authorization", data={"client_id": settings.client_id} + ).mock( + return_value=Response( + 200, + json={ + "verification_uri_complete": "http://test.com", + "verification_uri": "http://test.com", + "user_code": "1234", + "device_code": "5678", + }, + ) + ) + respx_mock.post( + "/login/device/token", + data={ + "device_code": "5678", + "client_id": settings.client_id, + "grant_type": "urn:ietf:params:oauth:grant-type:device_code", + }, + ).mock(return_value=Response(200, json={"access_token": "test_token_1234"})) + + with changing_dir(tmp_path), patch( + "rich_toolkit.container.getchar" + ) as mock_getchar, patch( + "fastapi_cloud_cli.commands.login.typer.launch" + ) as mock_launch: + mock_getchar.side_effect = steps + + result = runner.invoke(app, ["deploy"]) + + assert "Welcome to FastAPI Cloud!" in result.output + assert "What would you like to do?" in result.output + assert "Login to my existing account" in result.output + assert "Join the waiting list" in result.output + assert "Now you are logged in!" in result.output + assert mock_launch.called + + +@pytest.mark.respx(base_url=settings.base_api_url) +def test_chooses_waitlist_option_when_not_logged_in( + logged_out_cli: None, tmp_path: Path, respx_mock: respx.MockRouter +) -> None: + steps = [ + Keys.DOWN_ARROW, + Keys.ENTER, + *"some@example.com", + Keys.ENTER, + Keys.RIGHT_ARROW, + Keys.ENTER, + Keys.ENTER, + ] respx_mock.post( "/users/waiting-list", @@ -87,6 +140,10 @@ def test_shows_waitlist_form_when_not_logged_in( result = runner.invoke(app, ["deploy"]) assert result.exit_code == 1 + assert "Welcome to FastAPI Cloud!" in result.output + assert "What would you like to do?" in result.output + assert "Login to my existing account" in result.output + assert "Join the waiting list" in result.output assert "We're currently in private beta" in result.output assert "Let's go! Thanks for your interest in FastAPI Cloud! 🚀" in result.output @@ -96,6 +153,8 @@ def test_shows_waitlist_form_when_not_logged_in_longer_flow( logged_out_cli: None, tmp_path: Path, respx_mock: respx.MockRouter ) -> None: steps = [ + Keys.DOWN_ARROW, # Select "Join the waiting list" + Keys.ENTER, *"some@example.com", Keys.ENTER, Keys.ENTER, @@ -767,6 +826,8 @@ def test_shows_error_for_invalid_waitlist_form_data( logged_out_cli: None, tmp_path: Path, respx_mock: respx.MockRouter ) -> None: steps = [ + Keys.DOWN_ARROW, # Select "Join the waiting list" + Keys.ENTER, *"test@example.com", Keys.ENTER, Keys.ENTER, # Choose to provide more information diff --git a/tests/utils.py b/tests/utils.py index c5d0968..74aa97b 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -16,6 +16,7 @@ def changing_dir(directory: Union[str, Path]) -> Generator[None, None, None]: class Keys: RIGHT_ARROW = "\x1b[C" + DOWN_ARROW = "\x1b[B" ENTER = "\r" CTRL_C = "\x03" TAB = "\t"