from __future__ import annotations import pytest from fastapi.testclient import TestClient from cloud_api.app import create_app from cloud.control_config import CloudConfigurationError, CloudControlConfig def test_create_app_returns_independent_cloud_application() -> None: app = create_app() assert app.title == "Device Cloud API" assert callable(create_app) paths = set(app.openapi()["paths"]) assert "/v1/tasks" in paths assert "/internal/v1/hosts/{host_id}/heartbeat" in paths def test_cloud_application_owns_database_lifecycle() -> None: events: list[str] = [] class FakeDatabase: repository = object() def close(self) -> None: events.append("closed") fake_database = FakeDatabase() app = create_app( config=CloudControlConfig(database_url="sqlite:///:memory:"), database_factory=lambda _config: fake_database, # type: ignore[arg-type,return-value] ) with TestClient(app): assert app.state.database is fake_database assert ( app.state.cloud_services.pool.store is app.state.cloud_services.repository ) assert events == [] assert events == ["closed"] def test_repository_proxy_is_available_only_during_lifespan() -> None: class FakeRepository: def health_check(self) -> None: return None class FakeDatabase: repository = FakeRepository() def close(self) -> None: return None app = create_app( config=CloudControlConfig(database_url="sqlite:///:memory:"), database_factory=lambda _config: FakeDatabase(), # type: ignore[arg-type,return-value] ) with TestClient(app): app.state.cloud_services.repository.health_check() with pytest.raises(RuntimeError, match="outside app lifespan"): app.state.cloud_services.repository.health_check() def test_production_app_rejects_missing_credentials() -> None: with pytest.raises(CloudConfigurationError, match="credential"): create_app( config=CloudControlConfig( environment="production", database_url="postgresql://db/cloud", ) )