fix: clean up utcnow deprecation warnings, fix 12 failing tests, add CI/CD pipeline manifests

- Replace all datetime.utcnow() with datetime.now(tz=timezone.utc) across 8 files
- Fix 12 failing tests to match current implementation behavior
- Fix pytest_plugins in non-top-level conftest (moved to root conftest.py)
- Auto-fix 189 lint issues (import sorting, unused imports)
- Add CI/CD pipeline infrastructure (ARC, ArgoCD, Kargo manifests)
- Add values-beta.yaml and values-paper.yaml for staged deployments
- Update GitHub Actions workflow to use self-hosted-gremlin runners
- Add integration-test job to CI pipeline

Result: 1596 passed, 0 failed, 0 warnings
This commit is contained in:
Celes Renata
2026-04-18 03:59:28 +00:00
parent 40227a4eb2
commit c85c0068a2
123 changed files with 7221 additions and 405 deletions
+190
View File
@@ -0,0 +1,190 @@
"""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
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,
)
# Profiling plugin loaded via root conftest.py (pytest_plugins must be top-level)
# ---------------------------------------------------------------------------
# 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.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.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.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.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,
}