Team Ai
Apppublic

blackopsrepl/portfolio-optimization-python

sourceHugging Faceapache-2.0updated 10mo agoView on Hugging Face
1likes
test_rest_api.py137 linesDownload Raw Back to tests
1"""
2Tests for REST API endpoints.
3
4Tests that configuration is properly received and applied.
5"""
6import pytest
7from fastapi.testclient import TestClient
8from portfolio_optimization.rest_api import app
9from portfolio_optimization.domain import (
10    PortfolioOptimizationPlanModel,
11    StockSelectionModel,
12    SolverConfigModel,
13)
14
15
16@pytest.fixture
17def client():
18    """Create a test client for the FastAPI app."""
19    return TestClient(app)
20
21
22class TestDemoDataEndpoints:
23    """Tests for demo data endpoints."""
24
25    def test_list_demo_data(self, client):
26        """GET /demo-data should return available datasets."""
27        response = client.get("/demo-data")
28        assert response.status_code == 200
29        data = response.json()
30        assert "SMALL" in data
31        assert "LARGE" in data
32
33    def test_get_small_demo_data(self, client):
34        """GET /demo-data/SMALL should return 25 stocks."""
35        response = client.get("/demo-data/SMALL")
36        assert response.status_code == 200
37        data = response.json()
38        assert "stocks" in data
39        assert len(data["stocks"]) == 25
40
41    def test_get_large_demo_data(self, client):
42        """GET /demo-data/LARGE should return 51 stocks."""
43        response = client.get("/demo-data/LARGE")
44        assert response.status_code == 200
45        data = response.json()
46        assert "stocks" in data
47        assert len(data["stocks"]) == 51
48
49
50class TestSolverConfigEndpoints:
51    """Tests for solver configuration handling."""
52
53    def test_plan_model_accepts_solver_config(self):
54        """PortfolioOptimizationPlanModel should accept solverConfig."""
55        model = PortfolioOptimizationPlanModel(
56            stocks=[
57                StockSelectionModel(
58                    stockId="AAPL",
59                    stockName="Apple",
60                    sector="Technology",
61                    predictedReturn=0.12,
62                    selected=None
63                )
64            ],
65            targetPositionCount=20,
66            maxSectorPercentage=0.25,
67            solverConfig=SolverConfigModel(terminationSeconds=60)
68        )
69        assert model.solver_config is not None
70        assert model.solver_config.termination_seconds == 60
71
72    def test_plan_model_serializes_solver_config(self):
73        """solverConfig should serialize with camelCase aliases."""
74        model = PortfolioOptimizationPlanModel(
75            stocks=[],
76            solverConfig=SolverConfigModel(terminationSeconds=90)
77        )
78        data = model.model_dump(by_alias=True)
79        assert "solverConfig" in data
80        assert data["solverConfig"]["terminationSeconds"] == 90
81
82    def test_plan_model_deserializes_solver_config(self):
83        """solverConfig should deserialize from JSON."""
84        json_data = {
85            "stocks": [
86                {
87                    "stockId": "AAPL",
88                    "stockName": "Apple",
89                    "sector": "Technology",
90                    "predictedReturn": 0.12,
91                    "selected": None
92                }
93            ],
94            "targetPositionCount": 15,
95            "maxSectorPercentage": 0.30,
96            "solverConfig": {
97                "terminationSeconds": 120
98            }
99        }
100        model = PortfolioOptimizationPlanModel.model_validate(json_data)
101        assert model.target_position_count == 15
102        assert model.max_sector_percentage == 0.30
103        assert model.solver_config is not None
104        assert model.solver_config.termination_seconds == 120
105
106    def test_plan_without_solver_config(self):
107        """Plan should work without solverConfig (uses defaults)."""
108        json_data = {
109            "stocks": [],
110            "targetPositionCount": 20,
111            "maxSectorPercentage": 0.25
112        }
113        model = PortfolioOptimizationPlanModel.model_validate(json_data)
114        assert model.solver_config is None  # None is OK, will use default 30s
115
116    def test_post_portfolio_with_solver_config(self, client):
117        """POST /portfolios should accept solverConfig in request body."""
118        # First get demo data
119        demo_response = client.get("/demo-data/SMALL")
120        plan_data = demo_response.json()
121
122        # Add solver config
123        plan_data["solverConfig"] = {
124            "terminationSeconds": 10  # Use short time for test
125        }
126
127        # Submit for solving
128        response = client.post("/portfolios", json=plan_data)
129        assert response.status_code == 200
130        job_id = response.json()
131        assert job_id is not None
132        assert len(job_id) > 0
133
134        # Stop solving immediately (we just want to verify config was accepted)
135        stop_response = client.delete(f"/portfolios/{job_id}")
136        assert stop_response.status_code == 200
137