Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/fastapi_cloud_cli/commands/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@

from fastapi_cloud_cli.config import Settings
from fastapi_cloud_cli.utils.api import APIClient
from fastapi_cloud_cli.utils.auth import AuthConfig, write_auth_config
from fastapi_cloud_cli.utils.auth import (
AuthConfig,
get_auth_token,
is_logged_in,
is_token_expired,
write_auth_config,
)
from fastapi_cloud_cli.utils.cli import get_rich_toolkit, handle_http_errors

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -76,6 +82,20 @@ def login() -> Any:
"""
Login to FastAPI Cloud. πŸš€
"""
token = get_auth_token()
if token is not None and is_token_expired(token):
with get_rich_toolkit(minimal=True) as toolkit:
toolkit.print("Your session has expired. Logging in again...")
toolkit.print_line()

if is_logged_in():
with get_rich_toolkit(minimal=True) as toolkit:
toolkit.print("You are already logged in.")
toolkit.print(
"Run [bold]fastapi logout[/bold] first if you want to switch accounts."
)
return

with get_rich_toolkit() as toolkit, APIClient() as client:
toolkit.print_title("Login to FastAPI Cloud", tag="FastAPI")

Expand Down
53 changes: 53 additions & 0 deletions tests/test_cli_login.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import time
from pathlib import Path
from unittest.mock import patch

Expand All @@ -9,6 +10,7 @@

from fastapi_cloud_cli.cli import app
from fastapi_cloud_cli.config import Settings
from tests.utils import create_jwt_token

runner = CliRunner()
settings = Settings.get()
Expand Down Expand Up @@ -162,3 +164,54 @@ def test_fetch_access_token_handles_500_error(respx_mock: respx.MockRouter) -> N
with APIClient() as client:
with pytest.raises(httpx.HTTPStatusError):
_fetch_access_token(client, "test_device_code", 5)


@pytest.mark.respx(base_url=settings.base_api_url)
def test_notify_already_logged_in_user(
respx_mock: respx.MockRouter, logged_in_cli: None
) -> None:
result = runner.invoke(app, ["login"])

assert result.exit_code == 0
assert "You are already logged in." in result.output
assert "Run fastapi logout first if you want to switch accounts." in result.output


@pytest.mark.respx(base_url=settings.base_api_url)
def test_notify_expired_token_user(
respx_mock: respx.MockRouter, temp_auth_config: Path
) -> None:
past_exp = int(time.time()) - 3600
expired_token = create_jwt_token({"sub": "test_user_12345", "exp": past_exp})

temp_auth_config.write_text(f'{{"access_token": "{expired_token}"}}')

with patch("fastapi_cloud_cli.commands.login.typer.launch") as mock_open:
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": "new_token_1234"}))

result = runner.invoke(app, ["login"])

assert result.exit_code == 0
assert "Your session has expired. Logging in again..." in result.output
assert "Now you are logged in!" in result.output
assert mock_open.called