test: add unit test suite for config, health checks, and lifespan wiring
This commit is contained in:
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
37
tests/conftest.py
Normal file
37
tests/conftest.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from asgi_lifespan import LifespanManager
|
||||
from fastapi import FastAPI
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
|
||||
from src.config import Settings
|
||||
from src.main import create_app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def settings() -> Settings:
|
||||
return Settings(
|
||||
postgres={"host": "127.0.0.1", "port": 1},
|
||||
minio={"endpoint": "127.0.0.1:1"},
|
||||
ingestion={"timeout_seconds": 1.0},
|
||||
qdrant={"url": "http://127.0.0.1:1"},
|
||||
app={"readiness_check_timeout_seconds": 0.5},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app(settings: Settings) -> FastAPI:
|
||||
return create_app(settings)
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def client(app: FastAPI) -> AsyncIterator[AsyncClient]:
|
||||
async with (
|
||||
LifespanManager(app) as manager,
|
||||
AsyncClient(
|
||||
transport=ASGITransport(app=manager.app), base_url="http://test"
|
||||
) as async_client,
|
||||
):
|
||||
yield async_client
|
||||
0
tests/e2e/__init__.py
Normal file
0
tests/e2e/__init__.py
Normal file
5
tests/fakes.py
Normal file
5
tests/fakes.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Hand-written fakes for narrow application-owned ports.
|
||||
|
||||
No application ports exist yet (Phase 1 only wires infrastructure client
|
||||
lifecycle). Fakes are added here as ports are introduced in later phases.
|
||||
"""
|
||||
0
tests/integration/__init__.py
Normal file
0
tests/integration/__init__.py
Normal file
0
tests/integration/minio/__init__.py
Normal file
0
tests/integration/minio/__init__.py
Normal file
0
tests/integration/postgres/__init__.py
Normal file
0
tests/integration/postgres/__init__.py
Normal file
0
tests/integration/qdrant/__init__.py
Normal file
0
tests/integration/qdrant/__init__.py
Normal file
0
tests/support/__init__.py
Normal file
0
tests/support/__init__.py
Normal file
1
tests/support/assertions.py
Normal file
1
tests/support/assertions.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Custom test assertions, populated as later phases need them."""
|
||||
1
tests/support/factories.py
Normal file
1
tests/support/factories.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Object builders for test fixtures, populated as later phases need them."""
|
||||
0
tests/unit/__init__.py
Normal file
0
tests/unit/__init__.py
Normal file
0
tests/unit/agent/__init__.py
Normal file
0
tests/unit/agent/__init__.py
Normal file
0
tests/unit/application/__init__.py
Normal file
0
tests/unit/application/__init__.py
Normal file
0
tests/unit/bootstrap/__init__.py
Normal file
0
tests/unit/bootstrap/__init__.py
Normal file
19
tests/unit/bootstrap/test_lifespan.py
Normal file
19
tests/unit/bootstrap/test_lifespan.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from httpx import AsyncClient
|
||||
|
||||
from src.config import Settings
|
||||
|
||||
pytestmark = [pytest.mark.unit, pytest.mark.asyncio]
|
||||
|
||||
|
||||
async def test_lifespan_starts_and_stops_without_docker(client: AsyncClient) -> None:
|
||||
response = await client.get("/healthz")
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
async def test_lifespan_binds_resources_to_app_state(app: FastAPI, client: AsyncClient) -> None:
|
||||
await client.get("/healthz")
|
||||
resources = app.state.resources
|
||||
assert isinstance(resources.settings, Settings)
|
||||
assert resources.minio_client is not None
|
||||
27
tests/unit/test_config.py
Normal file
27
tests/unit/test_config.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from src.config import Settings
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
def test_settings_loads_defaults_without_env_file() -> None:
|
||||
settings = Settings(_env_file=None)
|
||||
|
||||
assert settings.postgres.port == 5433
|
||||
assert settings.minio.bucket == "chatbot-source-files"
|
||||
assert settings.ingestion.max_concurrency == 4
|
||||
assert settings.qdrant.url == "http://127.0.0.1:6343"
|
||||
|
||||
|
||||
def test_settings_parses_env_example() -> None:
|
||||
env_example = Path(__file__).parent.parent.parent / ".env.example"
|
||||
|
||||
settings = Settings(_env_file=env_example)
|
||||
|
||||
assert settings.postgres.host == "127.0.0.1"
|
||||
assert settings.minio.endpoint == "127.0.0.1:9100"
|
||||
assert settings.ingestion.embed_batch_size == 128
|
||||
assert settings.qdrant.api_key is None
|
||||
11
tests/unit/test_healthz.py
Normal file
11
tests/unit/test_healthz.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
|
||||
pytestmark = [pytest.mark.unit, pytest.mark.asyncio]
|
||||
|
||||
|
||||
async def test_healthz_returns_ok_status(client: AsyncClient) -> None:
|
||||
response = await client.get("/healthz")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"status": "ok"}
|
||||
17
tests/unit/test_readyz.py
Normal file
17
tests/unit/test_readyz.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
|
||||
pytestmark = [pytest.mark.unit, pytest.mark.asyncio]
|
||||
|
||||
|
||||
async def test_readyz_reports_all_dependencies_not_ready_without_docker(
|
||||
client: AsyncClient,
|
||||
) -> None:
|
||||
response = await client.get("/readyz")
|
||||
|
||||
assert response.status_code == 503
|
||||
assert response.json() == {
|
||||
"postgres": False,
|
||||
"minio": False,
|
||||
"qdrant": False,
|
||||
}
|
||||
Reference in New Issue
Block a user