alpha-arena/tests/test_api.py
oho 774f8b3f61 Initial commit: Alpha Arena - Polymarket Autonomous Trading Harness
A comprehensive autonomous trading system for Polymarket prediction markets
featuring multi-LLM provider support, a native macOS menu bar app, and a
web-based control dashboard.

Key features:
- Multi-agent trading system (Research, Risk, Execution, Reflection agents)
- LLM provider flexibility (Anthropic, OpenAI, Google, xAI, Local models)
- Automatic provider fallback chain for resilience
- Native Swift/SwiftUI macOS menu bar application
- FastAPI web dashboard with real-time WebSocket updates
- Risk management with kill switch
- Technical indicators and market analysis
2026-01-12 23:04:58 +01:00

423 lines
16 KiB
Python

"""
API Tests for Alpha Arena Web Application.
Tests all REST API endpoints for correct behavior, error handling,
and response formats.
"""
import pytest
from decimal import Decimal
pytestmark = pytest.mark.api
class TestHealthEndpoints:
"""Test system health and status endpoints."""
@pytest.mark.asyncio
async def test_health_check(self, async_client):
"""GET /api/system/health should return healthy status."""
response = await async_client.get("/api/system/health")
assert response.status_code == 200
data = response.json()
assert data["status"] == "healthy"
# API returns trading_active and websocket_clients
assert "trading_active" in data or "timestamp" in data
@pytest.mark.asyncio
async def test_providers_status(self, async_client):
"""GET /api/system/providers should return provider status."""
response = await async_client.get("/api/system/providers")
assert response.status_code == 200
data = response.json()
# API returns provider statuses directly
assert isinstance(data, dict)
# Should have some known providers
assert any(key in data for key in ["anthropic", "openai", "polymarket", "providers"])
class TestTradingEndpoints:
"""Test trading control endpoints."""
@pytest.mark.asyncio
async def test_trading_status(self, async_client):
"""GET /api/trading/status should return current status."""
response = await async_client.get("/api/trading/status")
assert response.status_code == 200
data = response.json()
# API uses "active" instead of "is_running"
assert "active" in data or "is_running" in data
assert "mode" in data or "kill_switch" in data
@pytest.mark.asyncio
async def test_start_trading_simulation(self, async_client):
"""POST /api/trading/start should start trading loop."""
response = await async_client.post(
"/api/trading/start",
json={"mode": "simulation", "loop_interval": 60}
)
# May succeed or fail depending on state, but should return valid response
assert response.status_code in [200, 400]
data = response.json()
assert "status" in data or "detail" in data or "message" in data
@pytest.mark.asyncio
async def test_stop_trading(self, async_client):
"""POST /api/trading/stop should stop trading loop."""
response = await async_client.post("/api/trading/stop")
# May succeed or fail depending on state
assert response.status_code in [200, 400]
@pytest.mark.asyncio
async def test_kill_switch_toggle(self, async_client):
"""POST /api/trading/kill-switch should toggle kill switch."""
# API expects a Form field "enabled"
response = await async_client.post(
"/api/trading/kill-switch",
data={"enabled": "true"}
)
assert response.status_code == 200
data = response.json()
assert "kill_switch" in data or "enabled" in data
@pytest.mark.asyncio
async def test_start_trading_with_mode(self, async_client):
"""POST /api/trading/start with mode parameter."""
response = await async_client.post(
"/api/trading/start",
json={"mode": "simulation"}
)
# Should accept valid modes
assert response.status_code in [200, 400]
class TestPositionsEndpoints:
"""Test positions and orders endpoints."""
@pytest.mark.asyncio
async def test_get_positions(self, async_client):
"""GET /api/positions should return positions list."""
response = await async_client.get("/api/positions")
assert response.status_code == 200
data = response.json()
assert "positions" in data
assert isinstance(data["positions"], list)
@pytest.mark.asyncio
async def test_get_orders(self, async_client):
"""GET /api/orders should return orders list."""
response = await async_client.get("/api/orders")
assert response.status_code == 200
data = response.json()
assert "orders" in data
assert isinstance(data["orders"], list)
@pytest.mark.asyncio
async def test_close_position_not_found(self, async_client):
"""POST /api/positions/{id}/close for non-existent position."""
response = await async_client.post("/api/positions/nonexistent_123/close")
# API may return 200 with error in body, or 404/400
assert response.status_code in [200, 404, 400]
@pytest.mark.asyncio
async def test_cancel_order_not_found(self, async_client):
"""POST /api/orders/{id}/cancel for non-existent order."""
response = await async_client.post("/api/orders/nonexistent_123/cancel")
# API may return 200 with error in body, or 404/400
assert response.status_code in [200, 404, 400]
class TestMarketsEndpoints:
"""Test markets browsing endpoints."""
@pytest.mark.asyncio
async def test_get_markets(self, async_client):
"""GET /api/markets should return markets list."""
response = await async_client.get("/api/markets")
assert response.status_code == 200
data = response.json()
assert "markets" in data
assert isinstance(data["markets"], list)
@pytest.mark.asyncio
async def test_get_markets_with_filters(self, async_client):
"""GET /api/markets with query params should filter results."""
response = await async_client.get(
"/api/markets",
params={"category": "politics", "limit": 10}
)
assert response.status_code == 200
data = response.json()
assert "markets" in data
@pytest.mark.asyncio
async def test_get_market_detail(self, async_client):
"""GET /api/markets/{id} should return market details."""
# First get a market ID
markets_response = await async_client.get("/api/markets?limit=1")
markets = markets_response.json().get("markets", [])
if markets:
market_id = markets[0].get("id", "test")
response = await async_client.get(f"/api/markets/{market_id}")
assert response.status_code in [200, 404]
@pytest.mark.asyncio
async def test_get_arbitrage_opportunities(self, async_client):
"""GET /api/arbitrage should return arbitrage opportunities."""
response = await async_client.get("/api/arbitrage")
assert response.status_code == 200
data = response.json()
assert "opportunities" in data
assert isinstance(data["opportunities"], list)
class TestWalletAnalysisEndpoints:
"""Test wallet analysis endpoints."""
@pytest.mark.asyncio
async def test_analyze_wallet_with_address(self, async_client):
"""POST /api/wallet/analyze with address."""
response = await async_client.post(
"/api/wallet/analyze",
json={"address": "invalid_address"}
)
# API may accept any string and return analysis or error
assert response.status_code in [200, 400, 422, 500]
@pytest.mark.asyncio
async def test_analyze_wallet_valid_format(self, async_client):
"""POST /api/wallet/analyze with valid address format."""
response = await async_client.post(
"/api/wallet/analyze",
json={"address": "0x1234567890123456789012345678901234567890"}
)
# May fail due to no data, but should accept the format
assert response.status_code in [200, 404, 500]
class TestAgentsEndpoints:
"""Test agent monitoring endpoints."""
@pytest.mark.asyncio
async def test_get_agents(self, async_client):
"""GET /api/agents should return agents status."""
response = await async_client.get("/api/agents")
assert response.status_code == 200
data = response.json()
assert "agents" in data
assert isinstance(data["agents"], list)
@pytest.mark.asyncio
async def test_get_agent_history(self, async_client):
"""GET /api/agents/{name}/history should return agent history."""
response = await async_client.get("/api/agents/research/history")
assert response.status_code == 200
data = response.json()
assert "history" in data
class TestRiskEndpoints:
"""Test risk management endpoints."""
@pytest.mark.asyncio
async def test_get_risk_status(self, async_client):
"""GET /api/risk/status should return risk status."""
response = await async_client.get("/api/risk/status")
assert response.status_code == 200
data = response.json()
assert "daily_pnl" in data or "status" in data
@pytest.mark.asyncio
async def test_update_risk_settings(self, async_client):
"""POST /api/risk/settings should update risk settings."""
response = await async_client.post(
"/api/risk/settings",
json={
"max_position_size_usdc": 150,
"daily_loss_limit_usdc": 75
}
)
assert response.status_code in [200, 400]
class TestConfigEndpoints:
"""Test configuration endpoints."""
@pytest.mark.asyncio
async def test_get_config(self, async_client):
"""GET /api/config should return current configuration."""
response = await async_client.get("/api/config")
assert response.status_code == 200
data = response.json()
# Should have some config structure
assert isinstance(data, dict)
@pytest.mark.asyncio
async def test_update_config(self, async_client):
"""POST /api/config should update configuration."""
response = await async_client.post(
"/api/config",
json={"loop_interval_seconds": 120}
)
assert response.status_code in [200, 400]
@pytest.mark.asyncio
async def test_get_llm_config(self, async_client):
"""GET /api/config/llm should return LLM configuration."""
response = await async_client.get("/api/config/llm")
assert response.status_code == 200
class TestLogsEndpoints:
"""Test logs and metrics endpoints."""
@pytest.mark.asyncio
async def test_get_decision_logs(self, async_client):
"""GET /api/logs/decisions should return decision logs."""
response = await async_client.get("/api/logs/decisions")
assert response.status_code == 200
data = response.json()
assert "logs" in data
assert isinstance(data["logs"], list)
@pytest.mark.asyncio
async def test_get_decision_logs_with_limit(self, async_client):
"""GET /api/logs/decisions with limit param."""
response = await async_client.get("/api/logs/decisions?limit=5")
assert response.status_code == 200
data = response.json()
assert len(data.get("logs", [])) <= 5
@pytest.mark.asyncio
async def test_get_trade_history(self, async_client):
"""GET /api/logs/trades should return trade history."""
response = await async_client.get("/api/logs/trades")
assert response.status_code == 200
data = response.json()
assert "trades" in data
@pytest.mark.asyncio
async def test_get_performance_metrics(self, async_client):
"""GET /api/metrics/performance should return metrics."""
response = await async_client.get("/api/metrics/performance")
assert response.status_code == 200
data = response.json()
# Should have performance metrics
assert isinstance(data, dict)
@pytest.mark.asyncio
async def test_get_equity_curve(self, async_client):
"""GET /api/metrics/equity-curve should return equity data."""
response = await async_client.get("/api/metrics/equity-curve")
assert response.status_code == 200
data = response.json()
assert "data" in data or "equity" in data or isinstance(data, list)
class TestPageRendering:
"""Test that HTML pages render correctly."""
@pytest.mark.asyncio
async def test_dashboard_page(self, async_client):
"""GET / should render dashboard page."""
response = await async_client.get("/")
assert response.status_code == 200
assert "text/html" in response.headers.get("content-type", "")
@pytest.mark.asyncio
async def test_trading_page(self, async_client):
"""GET /trading should render trading page."""
response = await async_client.get("/trading")
assert response.status_code == 200
assert "text/html" in response.headers.get("content-type", "")
@pytest.mark.asyncio
async def test_positions_page(self, async_client):
"""GET /positions should render positions page."""
response = await async_client.get("/positions")
assert response.status_code == 200
assert "text/html" in response.headers.get("content-type", "")
@pytest.mark.asyncio
async def test_markets_page(self, async_client):
"""GET /markets should render markets page."""
response = await async_client.get("/markets")
assert response.status_code == 200
assert "text/html" in response.headers.get("content-type", "")
@pytest.mark.asyncio
async def test_wallet_analysis_page(self, async_client):
"""GET /wallet-analysis should render wallet analysis page."""
response = await async_client.get("/wallet-analysis")
assert response.status_code == 200
assert "text/html" in response.headers.get("content-type", "")
@pytest.mark.asyncio
async def test_agents_page(self, async_client):
"""GET /agents should render agents page."""
response = await async_client.get("/agents")
assert response.status_code == 200
assert "text/html" in response.headers.get("content-type", "")
@pytest.mark.asyncio
async def test_risk_page(self, async_client):
"""GET /risk should render risk page."""
response = await async_client.get("/risk")
assert response.status_code == 200
assert "text/html" in response.headers.get("content-type", "")
@pytest.mark.asyncio
async def test_config_page(self, async_client):
"""GET /config should render config page."""
response = await async_client.get("/config")
assert response.status_code == 200
assert "text/html" in response.headers.get("content-type", "")
@pytest.mark.asyncio
async def test_logs_page(self, async_client):
"""GET /logs should render logs page."""
response = await async_client.get("/logs")
assert response.status_code == 200
assert "text/html" in response.headers.get("content-type", "")
@pytest.mark.asyncio
async def test_help_page(self, async_client):
"""GET /help should render help page."""
response = await async_client.get("/help")
assert response.status_code == 200
assert "text/html" in response.headers.get("content-type", "")
class TestErrorHandling:
"""Test error handling and edge cases."""
@pytest.mark.asyncio
async def test_404_for_unknown_route(self, async_client):
"""Unknown routes should return 404."""
response = await async_client.get("/api/nonexistent/endpoint")
assert response.status_code == 404
@pytest.mark.asyncio
async def test_405_for_wrong_method(self, async_client):
"""Wrong HTTP method should return 405."""
response = await async_client.delete("/api/trading/status")
assert response.status_code == 405
@pytest.mark.asyncio
async def test_422_for_invalid_json(self, async_client):
"""Invalid JSON should return 422."""
response = await async_client.post(
"/api/trading/start",
content="not valid json",
headers={"Content-Type": "application/json"}
)
assert response.status_code == 422
@pytest.mark.asyncio
async def test_cors_headers_present(self, async_client):
"""CORS headers should be present for API routes."""
response = await async_client.options("/api/trading/status")
# FastAPI may or may not have CORS middleware configured
# This test documents expected behavior
assert response.status_code in [200, 405]