"""Integration tests for Risk Engine — evaluation edge cases and approval lifecycle. Validates evaluation with minimal/extreme orders, custom config overrides, and the full approval lifecycle (list → detail → review → expire) against the live sandbox with deterministic seed data. Uses the ``risk_client`` and ``seed_ids`` fixtures from conftest.py. """ import pytest pytestmark = pytest.mark.asyncio # --------------------------------------------------------------------------- # 1 Evaluation Edge Cases # --------------------------------------------------------------------------- class TestRiskEvaluationEdgeCases: """Edge-case scenarios for POST /evaluate.""" async def test_minimal_order_evaluation(self, risk_client): """POST /evaluate — minimal order with only ticker.""" payload = {"order": {"ticker": "AAPL"}} resp = await risk_client.post("/evaluate", json=payload) assert resp.status_code == 200 data = resp.json() assert "evaluation_id" in data assert "eligible" in data assert "rejection_reasons" in data async def test_order_exceeding_position_cap(self, risk_client): """POST /evaluate — order exceeding position cap returns eligible: false.""" payload = { "order": { "ticker": "AAPL", "action": "buy", "quantity": 100000, "estimated_value": 18550000.00, "confidence": 0.5, "sector": "Technology", }, } resp = await risk_client.post("/evaluate", json=payload) assert resp.status_code == 200 data = resp.json() assert data["eligible"] is False assert len(data["rejection_reasons"]) >= 1 async def test_evaluation_with_custom_config(self, risk_client): """POST /evaluate — custom config override.""" payload = { "order": { "ticker": "MSFT", "action": "buy", "quantity": 5, "estimated_value": 2050.00, "confidence": 0.8, }, "config": { "max_portfolio_heat": 0.50, "max_single_position_pct": 0.10, "max_sector_concentration": 0.50, "daily_loss_limit_pct": 0.05, }, } resp = await risk_client.post("/evaluate", json=payload) assert resp.status_code == 200 data = resp.json() assert "evaluation_id" in data assert "eligible" in data # --------------------------------------------------------------------------- # 2 Approval Lifecycle # --------------------------------------------------------------------------- class TestRiskApprovalLifecycle: """Full approval lifecycle: list → detail → review → expire.""" async def test_pending_approvals_list(self, risk_client, seed_ids): """GET /approvals/pending — list pending approvals from seed.""" resp = await risk_client.get("/approvals/pending") assert resp.status_code == 200 data = resp.json() assert isinstance(data, list) assert len(data) >= 1 async def test_approval_detail(self, risk_client, seed_ids): """GET /approvals/{id} — seeded pending approval detail.""" approval_id = seed_ids["approvals"]["PENDING"] resp = await risk_client.get(f"/approvals/{approval_id}") assert resp.status_code == 200 data = resp.json() assert data["ticker"] == "AAPL" assert data["side"] == "buy" assert "status" in data assert "expires_at" in data async def test_approval_review(self, risk_client, seed_ids): """POST /approvals/{id}/review — approve the seeded pending approval.""" approval_id = seed_ids["approvals"]["PENDING"] payload = { "approved": True, "reviewed_by": "test-operator", "review_note": "Integration test approval", } resp = await risk_client.post( f"/approvals/{approval_id}/review", json=payload, ) assert resp.status_code == 200 data = resp.json() assert data["status"] == "approved" async def test_review_nonexistent_approval(self, risk_client): """POST /approvals/{id}/review — 404 for non-existent approval.""" fake_id = "00000000-0000-4000-ffff-000000000099" payload = { "approved": True, "reviewed_by": "test-operator", "review_note": "Should fail", } resp = await risk_client.post( f"/approvals/{fake_id}/review", json=payload, ) assert resp.status_code == 404 async def test_approval_expiry(self, risk_client): """POST /approvals/expire — expire stale approvals.""" resp = await risk_client.post("/approvals/expire") assert resp.status_code == 200 data = resp.json() assert "expired" in data assert isinstance(data["expired"], int)