Files
stonks-oracle/tests/integration/conftest.py
T
Celes Renata b2b8aca7c6 fix: inttest runner crash and minio bucket-init proxy issue
- Remove --profiling-output arg from runner.yaml (plugin uses default path)
- Inline profiling hooks in root conftest.py with graceful fallback
- Replace mc-based bucket-init with Python urllib (no proxy interference)
- Add explicit ProxyHandler({}) to guarantee no proxy usage in bucket-init
2026-04-19 19:15:20 +00:00

189 lines
6.7 KiB
Python

"""Shared pytest fixtures for integration tests.
Provides HTTP clients (httpx.AsyncClient) for each service, base URL
fixtures driven by environment variables, and a seed_ids dict that
re-exports every deterministic UUID from seed_sandbox.py.
When the ``profiler`` fixture is active (provided by conftest_profiling.py),
each HTTP client is wrapped in :class:`ProfiledAsyncClient` so that every
request is automatically timed and recorded.
"""
from __future__ import annotations
import os
from typing import Any
import httpx
import pytest
import pytest_asyncio
from tests.integration.profiler import EndpointProfiler
from tests.integration.seed_sandbox import (
SEED_AGENT_IDS,
SEED_BROKER_ACCOUNT_ID,
SEED_COMPANY_IDS,
SEED_DOCUMENT_IDS,
SEED_GLOBAL_EVENT_IDS,
SEED_ORDER_IDS,
SEED_PORTFOLIO_SNAPSHOT_ID,
SEED_POSITION_IDS,
SEED_RECOMMENDATION_IDS,
SEED_RISK_CONFIG_ID,
SEED_TRADING_DECISION_ID,
SEED_TREND_IDS,
SEED_VARIANT_IDS,
)
# ---------------------------------------------------------------------------
# ProfiledAsyncClient — transparent timing wrapper
# ---------------------------------------------------------------------------
class ProfiledAsyncClient:
"""Wraps :class:`httpx.AsyncClient` to record per-request timing.
Every HTTP method call (get, post, put, patch, delete, head, options)
is automatically timed via :meth:`EndpointProfiler.track` using the
pattern ``"METHOD /path"``.
Attribute access for anything not explicitly wrapped is forwarded to
the underlying client so tests can still use ``client.base_url``,
``client.headers``, etc.
"""
def __init__(self, client: httpx.AsyncClient, profiler: EndpointProfiler) -> None:
self._client = client
self._profiler = profiler
# -- Proxied HTTP methods ------------------------------------------------
async def get(self, url: str, **kwargs: Any) -> httpx.Response:
async with self._profiler.track(f"GET {url}"):
return await self._client.get(url, **kwargs)
async def post(self, url: str, **kwargs: Any) -> httpx.Response:
async with self._profiler.track(f"POST {url}"):
return await self._client.post(url, **kwargs)
async def put(self, url: str, **kwargs: Any) -> httpx.Response:
async with self._profiler.track(f"PUT {url}"):
return await self._client.put(url, **kwargs)
async def patch(self, url: str, **kwargs: Any) -> httpx.Response:
async with self._profiler.track(f"PATCH {url}"):
return await self._client.patch(url, **kwargs)
async def delete(self, url: str, **kwargs: Any) -> httpx.Response:
async with self._profiler.track(f"DELETE {url}"):
return await self._client.delete(url, **kwargs)
async def head(self, url: str, **kwargs: Any) -> httpx.Response:
async with self._profiler.track(f"HEAD {url}"):
return await self._client.head(url, **kwargs)
async def options(self, url: str, **kwargs: Any) -> httpx.Response:
async with self._profiler.track(f"OPTIONS {url}"):
return await self._client.options(url, **kwargs)
# -- Transparent attribute forwarding ------------------------------------
def __getattr__(self, name: str) -> Any:
return getattr(self._client, name)
# ---------------------------------------------------------------------------
# URL fixtures — read from env vars set by the runner Job (runner.yaml)
# ---------------------------------------------------------------------------
@pytest.fixture
def query_api_url() -> str:
"""Base URL for the Query API service."""
return os.environ.get("QUERY_API_URL", "http://localhost:8000")
@pytest.fixture
def registry_api_url() -> str:
"""Base URL for the Symbol Registry service."""
return os.environ.get("REGISTRY_API_URL", "http://localhost:8001")
@pytest.fixture
def risk_api_url() -> str:
"""Base URL for the Risk Engine service."""
return os.environ.get("RISK_API_URL", "http://localhost:8002")
@pytest.fixture
def trading_api_url() -> str:
"""Base URL for the Trading Engine service."""
return os.environ.get("TRADING_API_URL", "http://localhost:8003")
# ---------------------------------------------------------------------------
# Async HTTP client fixtures — one per service, 30 s timeout
# Wrapped with ProfiledAsyncClient for automatic timing collection.
# ---------------------------------------------------------------------------
@pytest_asyncio.fixture
async def query_client(
query_api_url: str, profiler: EndpointProfiler,
) -> ProfiledAsyncClient:
"""Profiled async HTTP client pointed at the Query API."""
async with httpx.AsyncClient(base_url=query_api_url, timeout=30.0) as client:
yield ProfiledAsyncClient(client, profiler)
@pytest_asyncio.fixture
async def registry_client(
registry_api_url: str, profiler: EndpointProfiler,
) -> ProfiledAsyncClient:
"""Profiled async HTTP client pointed at the Symbol Registry."""
async with httpx.AsyncClient(base_url=registry_api_url, timeout=30.0) as client:
yield ProfiledAsyncClient(client, profiler)
@pytest_asyncio.fixture
async def risk_client(
risk_api_url: str, profiler: EndpointProfiler,
) -> ProfiledAsyncClient:
"""Profiled async HTTP client pointed at the Risk Engine."""
async with httpx.AsyncClient(base_url=risk_api_url, timeout=30.0) as client:
yield ProfiledAsyncClient(client, profiler)
@pytest_asyncio.fixture
async def trading_client(
trading_api_url: str, profiler: EndpointProfiler,
) -> ProfiledAsyncClient:
"""Profiled async HTTP client pointed at the Trading Engine."""
async with httpx.AsyncClient(base_url=trading_api_url, timeout=30.0) as client:
yield ProfiledAsyncClient(client, profiler)
# ---------------------------------------------------------------------------
# Seed ID lookup — single dict with all deterministic IDs from seed_sandbox
# ---------------------------------------------------------------------------
@pytest.fixture
def seed_ids() -> dict:
"""All deterministic seed IDs for assertion in integration tests."""
return {
"companies": SEED_COMPANY_IDS,
"documents": SEED_DOCUMENT_IDS,
"trends": SEED_TREND_IDS,
"recommendations": SEED_RECOMMENDATION_IDS,
"orders": SEED_ORDER_IDS,
"positions": SEED_POSITION_IDS,
"global_events": SEED_GLOBAL_EVENT_IDS,
"agents": SEED_AGENT_IDS,
"variants": SEED_VARIANT_IDS,
"broker_account_id": SEED_BROKER_ACCOUNT_ID,
"trading_decision_id": SEED_TRADING_DECISION_ID,
"portfolio_snapshot_id": SEED_PORTFOLIO_SNAPSHOT_ID,
"risk_config_id": SEED_RISK_CONFIG_ID,
}