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
6 changes: 6 additions & 0 deletions src/fastapi_cloud_cli/utils/config.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import os
from pathlib import Path

import typer


def get_config_folder() -> Path:
config_dir = os.getenv("FASTAPI_CLOUD_CLI_CONFIG_DIR")

if config_dir:
return Path(config_dir).expanduser()

return Path(typer.get_app_dir("fastapi-cli"))


Expand Down
42 changes: 31 additions & 11 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
import os
import sys
import tempfile
from collections.abc import Generator
from dataclasses import dataclass
from pathlib import Path
from unittest.mock import patch

import pytest
import respx
from typer import rich_utils

from fastapi_cloud_cli.config import Settings

from .utils import create_jwt_token


@pytest.fixture(autouse=True)
def isolated_config_path() -> Generator[Path, None, None]:
with tempfile.TemporaryDirectory() as tmpdir:
os.environ["FASTAPI_CLOUD_CLI_CONFIG_DIR"] = tmpdir

yield Path(tmpdir)


@pytest.fixture
def temp_auth_config(
isolated_config_path: Path, monkeypatch: pytest.MonkeyPatch
) -> Generator[Path, None, None]:
yield isolated_config_path / "auth.json"


@pytest.fixture(autouse=True)
def reset_syspath() -> Generator[None, None, None]:
initial_python_path = sys.path.copy()
Expand All @@ -26,6 +45,17 @@ def setup_terminal() -> None:
return


@pytest.fixture
def settings() -> Settings:
return Settings.get()


@pytest.fixture
def respx_mock(settings: Settings) -> Generator[respx.MockRouter, None, None]:
with respx.mock(base_url=settings.base_api_url) as mock_router:
yield mock_router


@pytest.fixture
def logged_in_cli(temp_auth_config: Path) -> Generator[None, None, None]:
valid_token = create_jwt_token({"sub": "test_user_12345"})
Expand Down Expand Up @@ -60,13 +90,3 @@ def configured_app(tmp_path: Path) -> ConfiguredApp:
config_path.write_text(f'{{"app_id": "{app_id}", "team_id": "{team_id}"}}')

return ConfiguredApp(app_id=app_id, team_id=team_id, path=tmp_path)


@pytest.fixture
def temp_auth_config(tmp_path: Path) -> Generator[Path, None, None]:
"""Provides a temporary auth config setup for testing file operations."""

with patch(
"fastapi_cloud_cli.utils.config.get_config_folder", return_value=tmp_path
):
yield tmp_path / "auth.json"
24 changes: 2 additions & 22 deletions tests/test_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from httpx import Response
from time_machine import TimeMachineFixture

from fastapi_cloud_cli.config import Settings
from fastapi_cloud_cli.utils.api import (
STREAM_LOGS_MAX_RETRIES,
APIClient,
Expand All @@ -17,8 +16,6 @@
)
from tests.utils import build_logs_response

settings = Settings.get()


@pytest.fixture
def client() -> httpx.Client:
Expand All @@ -31,15 +28,11 @@ def deployment_id() -> str:
return "test-deployment-123"


api_mock = respx.mock(base_url=settings.base_api_url)


@pytest.fixture
def logs_route(deployment_id: str) -> respx.Route:
return api_mock.get(f"/deployments/{deployment_id}/build-logs")
def logs_route(respx_mock: respx.MockRouter, deployment_id: str) -> respx.Route:
return respx_mock.get(f"/deployments/{deployment_id}/build-logs")


@api_mock
def test_stream_build_logs_successful(
logs_route: respx.Route,
client: APIClient,
Expand Down Expand Up @@ -69,7 +62,6 @@ def test_stream_build_logs_successful(
assert logs[2].type == "complete"


@api_mock
def test_stream_build_logs_failed(
logs_route: respx.Route, client: APIClient, deployment_id: str
) -> None:
Expand All @@ -91,7 +83,6 @@ def test_stream_build_logs_failed(


@pytest.mark.parametrize("terminal_type", ["complete", "failed"])
@api_mock
def test_stream_build_logs_stop_after_terminal_state(
logs_route: respx.Route,
client: APIClient,
Expand All @@ -116,7 +107,6 @@ def test_stream_build_logs_stop_after_terminal_state(
assert logs[1].type == terminal_type


@api_mock
def test_stream_build_logs_internal_messages_are_skipped(
logs_route: respx.Route,
client: APIClient,
Expand All @@ -140,7 +130,6 @@ def test_stream_build_logs_internal_messages_are_skipped(
assert logs[1].type == "complete"


@api_mock
def test_stream_build_logs_malformed_json_is_skipped(
logs_route: respx.Route, client: APIClient, deployment_id: str
) -> None:
Expand All @@ -161,7 +150,6 @@ def test_stream_build_logs_malformed_json_is_skipped(
assert logs[1].type == "complete"


@api_mock
def test_stream_build_logs_unknown_log_type_is_skipped(
logs_route: respx.Route, client: APIClient, deployment_id: str
) -> None:
Expand All @@ -188,7 +176,6 @@ def test_stream_build_logs_unknown_log_type_is_skipped(
"network_error",
[httpx.NetworkError, httpx.TimeoutException, httpx.RemoteProtocolError],
)
@api_mock
def test_stream_build_logs_network_error_retry(
logs_route: respx.Route,
client: APIClient,
Expand Down Expand Up @@ -216,7 +203,6 @@ def test_stream_build_logs_network_error_retry(
assert logs[0].message == "Success after retry"


@api_mock
def test_stream_build_logs_server_error_retry(
logs_route: respx.Route, client: APIClient, deployment_id: str
) -> None:
Expand All @@ -237,7 +223,6 @@ def test_stream_build_logs_server_error_retry(
assert logs[0].type == "complete"


@api_mock
def test_stream_build_logs_client_error_raises_immediately(
logs_route: respx.Route, client: APIClient, deployment_id: str
) -> None:
Expand All @@ -247,7 +232,6 @@ def test_stream_build_logs_client_error_raises_immediately(
list(client.stream_build_logs(deployment_id))


@api_mock
def test_stream_build_logs_max_retries_exceeded(
logs_route: respx.Route, client: APIClient, deployment_id: str
) -> None:
Expand All @@ -261,7 +245,6 @@ def test_stream_build_logs_max_retries_exceeded(
list(client.stream_build_logs(deployment_id))


@api_mock
def test_stream_build_logs_empty_lines_are_skipped(
logs_route: respx.Route, client: APIClient, deployment_id: str
) -> None:
Expand All @@ -284,7 +267,6 @@ def test_stream_build_logs_empty_lines_are_skipped(
assert logs[1].type == "complete"


@respx.mock(base_url=settings.base_api_url)
def test_stream_build_logs_continue_after_timeout(
respx_mock: respx.MockRouter,
client: APIClient,
Expand Down Expand Up @@ -328,7 +310,6 @@ def test_stream_build_logs_continue_after_timeout(
assert next(logs).type == "complete"


@api_mock
def test_stream_build_logs_connection_closed_without_complete_failed_or_timeout(
logs_route: respx.Route, client: APIClient, deployment_id: str
) -> None:
Expand All @@ -348,7 +329,6 @@ def test_stream_build_logs_connection_closed_without_complete_failed_or_timeout(
next(logs)


@api_mock
def test_stream_build_logs_retry_timeout(
logs_route: respx.Route,
client: APIClient,
Expand Down
Loading