Coverage for backend / tests / conftest.py: 100%
73 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-06-19 09:10 +0000
« prev ^ index » next coverage.py v7.14.0, created at 2026-06-19 09:10 +0000
1import os
2from collections.abc import Iterator
3from contextlib import asynccontextmanager
4from typing import Any
5from unittest.mock import AsyncMock, patch
7import pytest
8from fastapi.testclient import TestClient
9import stamina
11from app.config import get_settings
12from app.dependencies.redis_client import get_redis_client
13from app.main import app
15TEST_API_KEY = "test-api-key"
18@pytest.fixture(scope="module", autouse=True)
19def _use_test_settings() -> None:
20 """Env vars must be set before the lifespan runs."""
21 os.environ["API_KEY"] = TEST_API_KEY
22 os.environ["AWS_ACCESS_KEY_ID"] = "fake-access-key"
23 os.environ["AWS_SECRET_ACCESS_KEY"] = "fake-secret-key"
24 os.environ["AWS_BUCKET"] = "test-bucket"
25 os.environ["AWS_REGION"] = "us-east-1"
26 os.environ["AWS_UPLOAD_ROLE_ARN"] = "arn:aws:iam::123456789012:role/fake"
27 os.environ["REDIS_URL"] = "redis://localhost:6379/0"
28 os.environ["GITHUB_TOKEN"] = "fake-github-token"
29 get_settings.cache_clear()
32@pytest.fixture
33def api_key() -> str:
34 return TEST_API_KEY
37@pytest.fixture(scope="module")
38def mock_s3_client() -> AsyncMock:
39 """Mock for aiobotocore S3 client (AWSStorage._client)."""
40 return AsyncMock()
43@pytest.fixture(scope="module")
44def mock_sts_client() -> AsyncMock:
45 """Mock for aiobotocore STS client used by AWSStorage.create_upload_session."""
46 return AsyncMock()
49@pytest.fixture(scope="module", autouse=True)
50def _patch_aiobotocore(
51 mock_s3_client: AsyncMock, mock_sts_client: AsyncMock
52) -> Iterator[None]:
53 """Patch aiobotocore so the lifespan-created AWSStorage gets mocked clients."""
55 @asynccontextmanager
56 async def _s3_ctx():
57 yield mock_s3_client
59 @asynccontextmanager
60 async def _sts_ctx():
61 yield mock_sts_client
63 def fake_create_client(self, service, **kwargs):
64 if service == "s3":
65 return _s3_ctx()
66 if service == "sts":
67 return _sts_ctx()
68 raise ValueError( # pragma: no cover
69 f"Unexpected AWS service in tests: {service}"
70 )
72 with patch("aiobotocore.session.AioSession.create_client", fake_create_client):
73 yield
76@pytest.fixture(autouse=True)
77def _reset_aws_mocks(mock_s3_client: AsyncMock, mock_sts_client: AsyncMock) -> None:
78 """Clear mock state between tests."""
79 mock_s3_client.reset_mock(return_value=True, side_effect=True)
80 mock_sts_client.reset_mock(return_value=True, side_effect=True)
83@pytest.fixture
84def mock_redis() -> AsyncMock:
85 return AsyncMock()
88@pytest.fixture(scope="module")
89def client(_patch_aiobotocore: None) -> Iterator[TestClient]:
90 """Lifespan runs once per module; aiobotocore is already patched."""
91 with TestClient(app) as tc:
92 yield tc
95@pytest.fixture(name="override_redis", autouse=True)
96def _override_redis(mock_redis: AsyncMock) -> Iterator[None]:
97 app.dependency_overrides[get_redis_client] = lambda: mock_redis
98 yield
99 app.dependency_overrides.clear()
102@pytest.fixture
103def disable_redis(
104 override_redis: Any, monkeypatch: pytest.MonkeyPatch
105) -> Iterator[None]:
106 """Disable Redis for a test."""
107 old_dep = app.dependency_overrides.get(get_redis_client)
108 app.dependency_overrides[get_redis_client] = lambda: None
109 yield
110 if old_dep is not None:
111 app.dependency_overrides[get_redis_client] = old_dep
114@pytest.fixture(autouse=True, scope="session")
115def set_stamina_testing():
116 stamina.set_testing(True, attempts=10, cap=True)