Download tests/test_rest_api.py from blackopsrepl/portfolio-optimization-python: direct link, hf CLI and curl.
- Browser
- Download file 4.89 kB
-
https://huggingface.co/spaces/blackopsrepl/portfolio-optimization-python/resolve/ddfa975d099b0ffffa0406e8f98ab85097029043/tests/test_rest_api.py
- Command line
-
hf download hf://spaces/blackopsrepl/portfolio-optimization-python@ddfa975d099b0ffffa0406e8f98ab85097029043/tests/test_rest_api.py
-
curl -L -o test_rest_api.py https://huggingface.co/spaces/blackopsrepl/portfolio-optimization-python/resolve/ddfa975d099b0ffffa0406e8f98ab85097029043/tests/test_rest_api.py
4.89 kB
| """ | |
| Tests for REST API endpoints. | |
| Tests that configuration is properly received and applied. | |
| """ | |
| import pytest | |
| from fastapi.testclient import TestClient | |
| from portfolio_optimization.rest_api import app | |
| from portfolio_optimization.domain import ( | |
| PortfolioOptimizationPlanModel, | |
| StockSelectionModel, | |
| SolverConfigModel, | |
| ) | |
| def client(): | |
| """Create a test client for the FastAPI app.""" | |
| return TestClient(app) | |
| class TestDemoDataEndpoints: | |
| """Tests for demo data endpoints.""" | |
| def test_list_demo_data(self, client): | |
| """GET /demo-data should return available datasets.""" | |
| response = client.get("/demo-data") | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert "SMALL" in data | |
| assert "LARGE" in data | |
| def test_get_small_demo_data(self, client): | |
| """GET /demo-data/SMALL should return 25 stocks.""" | |
| response = client.get("/demo-data/SMALL") | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert "stocks" in data | |
| assert len(data["stocks"]) == 25 | |
| def test_get_large_demo_data(self, client): | |
| """GET /demo-data/LARGE should return 51 stocks.""" | |
| response = client.get("/demo-data/LARGE") | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert "stocks" in data | |
| assert len(data["stocks"]) == 51 | |
| class TestSolverConfigEndpoints: | |
| """Tests for solver configuration handling.""" | |
| def test_plan_model_accepts_solver_config(self): | |
| """PortfolioOptimizationPlanModel should accept solverConfig.""" | |
| model = PortfolioOptimizationPlanModel( | |
| stocks=[ | |
| StockSelectionModel( | |
| stockId="AAPL", | |
| stockName="Apple", | |
| sector="Technology", | |
| predictedReturn=0.12, | |
| selected=None | |
| ) | |
| ], | |
| targetPositionCount=20, | |
| maxSectorPercentage=0.25, | |
| solverConfig=SolverConfigModel(terminationSeconds=60) | |
| ) | |
| assert model.solver_config is not None | |
| assert model.solver_config.termination_seconds == 60 | |
| def test_plan_model_serializes_solver_config(self): | |
| """solverConfig should serialize with camelCase aliases.""" | |
| model = PortfolioOptimizationPlanModel( | |
| stocks=[], | |
| solverConfig=SolverConfigModel(terminationSeconds=90) | |
| ) | |
| data = model.model_dump(by_alias=True) | |
| assert "solverConfig" in data | |
| assert data["solverConfig"]["terminationSeconds"] == 90 | |
| def test_plan_model_deserializes_solver_config(self): | |
| """solverConfig should deserialize from JSON.""" | |
| json_data = { | |
| "stocks": [ | |
| { | |
| "stockId": "AAPL", | |
| "stockName": "Apple", | |
| "sector": "Technology", | |
| "predictedReturn": 0.12, | |
| "selected": None | |
| } | |
| ], | |
| "targetPositionCount": 15, | |
| "maxSectorPercentage": 0.30, | |
| "solverConfig": { | |
| "terminationSeconds": 120 | |
| } | |
| } | |
| model = PortfolioOptimizationPlanModel.model_validate(json_data) | |
| assert model.target_position_count == 15 | |
| assert model.max_sector_percentage == 0.30 | |
| assert model.solver_config is not None | |
| assert model.solver_config.termination_seconds == 120 | |
| def test_plan_without_solver_config(self): | |
| """Plan should work without solverConfig (uses defaults).""" | |
| json_data = { | |
| "stocks": [], | |
| "targetPositionCount": 20, | |
| "maxSectorPercentage": 0.25 | |
| } | |
| model = PortfolioOptimizationPlanModel.model_validate(json_data) | |
| assert model.solver_config is None # None is OK, will use default 30s | |
| def test_post_portfolio_with_solver_config(self, client): | |
| """POST /portfolios should accept solverConfig in request body.""" | |
| # First get demo data | |
| demo_response = client.get("/demo-data/SMALL") | |
| plan_data = demo_response.json() | |
| # Add solver config | |
| plan_data["solverConfig"] = { | |
| "terminationSeconds": 10 # Use short time for test | |
| } | |
| # Submit for solving | |
| response = client.post("/portfolios", json=plan_data) | |
| assert response.status_code == 200 | |
| job_id = response.json() | |
| assert job_id is not None | |
| assert len(job_id) > 0 | |
| # Stop solving immediately (we just want to verify config was accepted) | |
| stop_response = client.delete(f"/portfolios/{job_id}") | |
| assert stop_response.status_code == 200 | |