Files
stonks-oracle/tests/integration/test_trading_extended.py
T
Celes Renata 898f89926d feat: beta API integration test suite — 85 new tests across 6 modules
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.
2026-04-20 02:34:19 +00:00

245 lines
9.0 KiB
Python

"""Integration tests for Trading Engine — extended coverage.
Validates configuration round-trips, pause/resume lifecycle, metrics
consistency, notification config, decision filtering, and override
order validation against the live sandbox.
Uses the ``trading_client`` fixture from conftest.py.
"""
import pytest
pytestmark = pytest.mark.asyncio
# ---------------------------------------------------------------------------
# 1 Config Round-Trip
# ---------------------------------------------------------------------------
class TestTradingConfigRoundTrip:
"""PUT config → GET status — verify risk_tier reflected."""
async def test_config_round_trip(self, trading_client):
"""PUT config → GET status — verify risk_tier reflected."""
# Set to aggressive
resp = await trading_client.put(
"/api/trading/config", json={"risk_tier": "aggressive"}
)
assert resp.status_code == 200
# Verify in status
status_resp = await trading_client.get("/api/trading/status")
assert status_resp.status_code == 200
assert status_resp.json()["risk_tier"] == "aggressive"
# Restore to moderate
await trading_client.put(
"/api/trading/config", json={"risk_tier": "moderate"}
)
# ---------------------------------------------------------------------------
# 2 Pause/Resume Round-Trip
# ---------------------------------------------------------------------------
class TestTradingPauseResumeRoundTrip:
"""POST pause → GET status → POST resume → GET status."""
async def test_pause_resume_round_trip(self, trading_client):
"""POST pause → GET status → POST resume → GET status."""
# Pause
resp = await trading_client.post("/api/trading/pause")
assert resp.status_code == 200
# Verify paused
status_resp = await trading_client.get("/api/trading/status")
assert status_resp.status_code == 200
assert status_resp.json()["paused"] is True
# Resume
resp2 = await trading_client.post("/api/trading/resume")
assert resp2.status_code == 200
# Verify resumed
status_resp2 = await trading_client.get("/api/trading/status")
assert status_resp2.status_code == 200
assert status_resp2.json()["paused"] is False
# ---------------------------------------------------------------------------
# 3 Metrics Consistency
# ---------------------------------------------------------------------------
class TestTradingMetricsConsistency:
"""GET /api/trading/metrics — total ≈ active + reserve + unrealized."""
async def test_metrics_consistency(self, trading_client):
"""GET /api/trading/metrics — total ≈ active + reserve + unrealized."""
resp = await trading_client.get("/api/trading/metrics")
assert resp.status_code == 200
data = resp.json()
total = data["total_portfolio_value"]
active = data["active_pool"]
reserve = data["reserve_pool"]
unrealized = data["unrealized_pnl"]
# Allow tolerance for rounding
assert abs(total - (active + reserve + unrealized)) < 1.0
# ---------------------------------------------------------------------------
# 4 Metrics History
# ---------------------------------------------------------------------------
class TestTradingMetricsHistoryExtended:
"""GET /api/trading/metrics/history — returns portfolio snapshots."""
async def test_metrics_history_snapshots(self, trading_client):
"""GET /api/trading/metrics/history — returns portfolio snapshots."""
resp = await trading_client.get("/api/trading/metrics/history")
assert resp.status_code == 200
data = resp.json()
assert isinstance(data, list)
if len(data) >= 1:
snap = data[0]
assert "portfolio_value" in snap
assert "snapshot_date" in snap
# ---------------------------------------------------------------------------
# 5 Notification Config Round-Trip
# ---------------------------------------------------------------------------
class TestTradingNotificationRoundTrip:
"""PUT → GET notification config round-trip."""
async def test_notification_config_round_trip(self, trading_client):
"""PUT → GET notification config round-trip."""
# Update phone number (which drives sms_enabled)
resp = await trading_client.put(
"/api/trading/notifications/config",
json={"phone_number": "+15551234567"},
)
assert resp.status_code == 200
# Verify GET returns config structure
get_resp = await trading_client.get("/api/trading/notifications/config")
assert get_resp.status_code == 200
data = get_resp.json()
assert "sms_enabled" in data
assert "email_enabled" in data
assert isinstance(data["sms_enabled"], bool)
assert isinstance(data["email_enabled"], bool)
# ---------------------------------------------------------------------------
# 6 Notification History
# ---------------------------------------------------------------------------
class TestTradingNotificationHistory:
"""GET /api/trading/notifications/history — returns list."""
async def test_notification_history(self, trading_client):
"""GET /api/trading/notifications/history — returns list."""
resp = await trading_client.get("/api/trading/notifications/history")
assert resp.status_code == 200
data = resp.json()
assert isinstance(data, list)
# ---------------------------------------------------------------------------
# 7 Decision Filtering
# ---------------------------------------------------------------------------
class TestTradingDecisionFiltering:
"""GET /api/trading/decisions with various filters."""
async def test_decisions_filter_by_ticker(self, trading_client):
"""GET /api/trading/decisions?ticker=AAPL — only AAPL decisions."""
resp = await trading_client.get(
"/api/trading/decisions", params={"ticker": "AAPL"}
)
assert resp.status_code == 200
data = resp.json()
assert isinstance(data, list)
for d in data:
assert d["ticker"] == "AAPL"
async def test_decisions_filter_by_limit(self, trading_client):
"""GET /api/trading/decisions?limit=1 — at most 1 decision."""
resp = await trading_client.get(
"/api/trading/decisions", params={"limit": "1"}
)
assert resp.status_code == 200
data = resp.json()
assert isinstance(data, list)
assert len(data) <= 1
async def test_decisions_filter_by_decision_type(self, trading_client):
"""GET /api/trading/decisions?decision=execute — only execute decisions."""
resp = await trading_client.get(
"/api/trading/decisions", params={"decision": "execute"}
)
assert resp.status_code == 200
data = resp.json()
assert isinstance(data, list)
for d in data:
assert d["decision"] == "execute"
# ---------------------------------------------------------------------------
# 8 Override Order Validation
# ---------------------------------------------------------------------------
class TestTradingOverrideValidation:
"""POST /api/trading/override/order — validation edge cases."""
async def test_override_invalid_ticker(self, trading_client):
"""POST /api/trading/override/order — invalid ticker returns 422."""
payload = {
"ticker": "AAPL123", # contains digits — should be rejected
"side": "buy",
"quantity": 1.0,
"order_type": "market",
}
resp = await trading_client.post(
"/api/trading/override/order", json=payload
)
assert resp.status_code == 422
async def test_override_zero_quantity(self, trading_client):
"""POST /api/trading/override/order — zero quantity returns 422."""
payload = {
"ticker": "AAPL",
"side": "buy",
"quantity": 0,
"order_type": "market",
}
resp = await trading_client.post(
"/api/trading/override/order", json=payload
)
assert resp.status_code == 422
async def test_override_valid_order(self, trading_client):
"""POST /api/trading/override/order — valid order returns 202 or structured error."""
payload = {
"ticker": "AAPL",
"side": "buy",
"quantity": 1.0,
"order_type": "market",
}
resp = await trading_client.post(
"/api/trading/override/order", json=payload
)
assert resp.status_code in (200, 202, 400, 422, 503)
data = resp.json()
if resp.status_code == 202:
assert "job_id" in data
assert data["status"] == "queued"
assert data["ticker"] == "AAPL"
assert data["side"] == "buy"
assert data["quantity"] == 1.0
else:
assert isinstance(data, dict)