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
14 changes: 9 additions & 5 deletions src/fastapi_cloud_cli/commands/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from enum import Enum
from itertools import cycle
from pathlib import Path
from textwrap import dedent
from typing import Any, Dict, List, Optional, Union

import fastar
Expand Down Expand Up @@ -388,12 +389,15 @@ def _wait_for_deployment(

last_message_changed_at = time.monotonic()

except (BuildLogError, TooManyRetriesError) as e:
logger.error("Build log streaming failed: %s", e)
toolkit.print_line()
toolkit.print(
f"⚠️ Unable to stream build logs. Check the dashboard for status: [link={deployment.dashboard_url}]{deployment.dashboard_url}[/link]"
except (BuildLogError, TooManyRetriesError, TimeoutError) as e:
progress.set_error(
dedent(f"""
[error]Build log streaming failed: {e}[/]

Unable to stream build logs. Check the dashboard for status: [link={deployment.dashboard_url}]{deployment.dashboard_url}[/link]
""").strip()
)

raise typer.Exit(1) from e


Expand Down
3 changes: 1 addition & 2 deletions src/fastapi_cloud_cli/utils/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,7 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> Generator[T, None, None]:
for attempt_number in range(total_attempts):
if time.monotonic() - start > timeout.total_seconds():
raise TimeoutError(
"Build log streaming timed out after %ds",
timeout.total_seconds(),
f"Build log streaming timed out after {timeout.total_seconds():.0f}s"
)

with attempt(attempt_number):
Expand Down
26 changes: 14 additions & 12 deletions tests/test_cli_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from fastapi_cloud_cli.cli import app
from fastapi_cloud_cli.config import Settings
from fastapi_cloud_cli.utils.api import BuildLogError, TooManyRetriesError
from tests.conftest import ConfiguredApp
from tests.utils import Keys, build_logs_response, changing_dir

Expand Down Expand Up @@ -771,11 +772,14 @@ def test_shows_no_apps_found_message_when_team_has_no_apps(
)


@pytest.mark.parametrize(
"error",
[BuildLogError, TooManyRetriesError, TimeoutError],
)
@pytest.mark.respx(base_url=settings.base_api_url)
def test_handles_build_log_streaming_error(
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
def test_shows_error_message_on_build_exception(
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter, error: Exception
) -> None:
"""Test that BuildLogError is caught and shows dashboard link (lines 384, 387-392)."""
app_data = _get_random_app()
team_data = _get_random_team()
app_id = app_data["id"]
Expand All @@ -802,11 +806,10 @@ def test_handles_build_log_streaming_error(
return_value=Response(200)
)

respx_mock.get(f"/deployments/{deployment_data['id']}/build-logs").mock(
return_value=Response(422, text="Error")
)

with changing_dir(tmp_path):
with changing_dir(tmp_path), patch(
"fastapi_cloud_cli.utils.api.APIClient.stream_build_logs",
side_effect=error,
):
result = runner.invoke(app, ["deploy"])

assert result.exit_code == 1
Expand All @@ -815,10 +818,8 @@ def test_handles_build_log_streaming_error(


@pytest.mark.respx(base_url=settings.base_api_url)
def test_shows_error_message_when_build_log_streaming_fails(
logged_in_cli: None,
tmp_path: Path,
respx_mock: respx.MockRouter,
def test_shows_error_message_on_build_log_http_error(
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
) -> None:
app_data = _get_random_app()
team_data = _get_random_team()
Expand Down Expand Up @@ -853,6 +854,7 @@ def test_shows_error_message_when_build_log_streaming_fails(
with changing_dir(tmp_path), patch("time.sleep"):
result = runner.invoke(app, ["deploy"])

assert result.exit_code == 1
assert "Unable to stream build logs" in result.output
assert deployment_data["dashboard_url"] in result.output

Expand Down