898f89926d
Extends integration test coverage from 108 to 193 tests for the beta gate. New test modules: - test_query_api_extended.py (33 tests): documents, evidence, macro/competitive, ops/admin, agents, analytics - test_registry_write_paths.py (16 tests): write paths, validation, duplicates, competitor/exposure CRUD - test_risk_approval_lifecycle.py (8 tests): evaluation edge cases, full approval lifecycle - test_trading_extended.py (12 tests): config round-trips, decision filtering, override validation - test_cross_service_roundtrip.py (4 tests): cross-service data consistency - test_error_handling.py (12 tests): 404s, 422s, empty states, health checks Seed script extended with watchlists, approvals, lockouts, notifications, ingestion runs, saved queries, and daily risk snapshots.
203 lines
7.1 KiB
Python
203 lines
7.1 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_APPROVAL_IDS,
|
|
SEED_BROKER_ACCOUNT_ID,
|
|
SEED_COMPANY_IDS,
|
|
SEED_DOCUMENT_IDS,
|
|
SEED_GLOBAL_EVENT_IDS,
|
|
SEED_INGESTION_RUN_IDS,
|
|
SEED_LOCKOUT_IDS,
|
|
SEED_NOTIFICATION_IDS,
|
|
SEED_ORDER_IDS,
|
|
SEED_PORTFOLIO_SNAPSHOT_ID,
|
|
SEED_POSITION_IDS,
|
|
SEED_RECOMMENDATION_IDS,
|
|
SEED_RISK_CONFIG_ID,
|
|
SEED_RISK_SNAPSHOT_IDS,
|
|
SEED_SAVED_QUERY_IDS,
|
|
SEED_TRADING_DECISION_ID,
|
|
SEED_TREND_IDS,
|
|
SEED_VARIANT_IDS,
|
|
SEED_WATCHLIST_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,
|
|
"watchlists": SEED_WATCHLIST_IDS,
|
|
"approvals": SEED_APPROVAL_IDS,
|
|
"lockouts": SEED_LOCKOUT_IDS,
|
|
"notifications": SEED_NOTIFICATION_IDS,
|
|
"ingestion_runs": SEED_INGESTION_RUN_IDS,
|
|
"saved_queries": SEED_SAVED_QUERY_IDS,
|
|
"risk_snapshots": SEED_RISK_SNAPSHOT_IDS,
|
|
}
|