DealDocumentScreening/tests/integration/conftest.py

143 lines
4.2 KiB
Python

"""Integration-test fixtures using the running Docker Compose infrastructure.
Run `docker compose up -d` before executing integration tests. Migrations run
once per session; a service token is seeded so adapter-style endpoints can
authenticate.
"""
from __future__ import annotations
import os
import subprocess
import sys
from collections.abc import Iterator
from pathlib import Path
from typing import TYPE_CHECKING
import httpx
import pytest
import pytest_asyncio
from asgi_lifespan import LifespanManager
from sqlalchemy import text
from contract_check.api.app import create_app
from contract_check.core.config import get_settings
from contract_check.core.db.session import create_session_factory
from contract_check.core.tokens import hash_token
if TYPE_CHECKING:
pass
pytestmark = pytest.mark.integration
_BOT_SERVICE_TOKEN = "it-test-bot-token"
_DB_URL = "postgresql+asyncpg://contract_check:contract_check@localhost:15432/contract_check"
_AMQP_URL = "amqp://contract_check:contract_check@localhost:5672//"
_S3_URL = "http://localhost:9000"
def _set_env() -> None:
env_vars = {
"DATABASE_URL": _DB_URL,
"RABBITMQ_URL": _AMQP_URL,
"S3_ENDPOINT_URL": _S3_URL,
"S3_ACCESS_KEY": "contract_check",
"S3_SECRET_KEY": "contract_check",
"S3_BUCKET": "contract-check-docs",
"REDIS_URL": "redis://localhost:17379/0",
"OLLAMA_HOST": "http://localhost",
"OLLAMA_API_KEY": "test",
"JWT_SECRET": "it-test-jwt-secret-not-for-production",
"JWT_ALGORITHM": "HS256",
"JWT_ACCESS_TTL_MINUTES": "1440",
"TELEGRAM_BOT_TOKEN": "it-test-bot-token:it-test-secret",
}
for k, v in env_vars.items():
os.environ[k] = v
@pytest.fixture(scope="session")
def infra() -> Iterator[dict[str, str]]:
_set_env()
get_settings.cache_clear()
repo_root = Path(__file__).resolve().parents[2]
subprocess.run(
[sys.executable, "-m", "alembic", "-c", str(repo_root / "alembic.ini"), "upgrade", "head"],
cwd=str(repo_root),
env={**os.environ},
check=True,
capture_output=False,
)
factory = create_session_factory()
async def _seed() -> None:
async with factory() as session:
await session.execute(
text(
"INSERT INTO service_tokens (name, token_hash, adapter) "
"VALUES (:name, :hash, 'bot') "
"ON CONFLICT (name) DO UPDATE SET "
" token_hash = EXCLUDED.token_hash, revoked = FALSE"
),
{"name": "bot-test", "hash": hash_token(_BOT_SERVICE_TOKEN)},
)
await session.commit()
import asyncio
asyncio.run(_seed())
yield {
"database_url": _DB_URL,
"rabbitmq_url": _AMQP_URL,
"s3_endpoint_url": _S3_URL,
"s3_access_key": "contract_check",
"s3_secret_key": "contract_check",
"token": _BOT_SERVICE_TOKEN,
}
get_settings.cache_clear()
@pytest_asyncio.fixture
async def client(infra: dict[str, str]) -> httpx.AsyncClient:
get_settings.cache_clear()
_set_env()
get_settings()
app = create_app()
async with httpx.AsyncClient(
transport=httpx.ASGITransport(app=app), base_url="http://test"
) as c:
async with LifespanManager(app):
yield c
get_settings.cache_clear()
@pytest.fixture
def auth_header(infra: dict[str, str]) -> dict[str, str]:
return {"Authorization": f"Bearer {infra['token']}"}
async def user_token(client: httpx.AsyncClient, infra: dict[str, str], telegram_id: int) -> str:
"""Call /auth/telegram/bot and return a user JWT for the given telegram_id."""
r = await client.post(
"/api/v1/auth/telegram/bot",
json={"telegram_id": telegram_id},
headers={"Authorization": f"Bearer {infra['token']}"},
)
assert r.status_code == 200, f"auth failed: {r.status_code} {r.text}"
return r.json()["access_token"]
@pytest.fixture
async def db_session():
_set_env()
get_settings.cache_clear()
factory = create_session_factory()
async with factory() as session:
yield session
await session.close()
get_settings.cache_clear()