blackopsrepl/portfolio-optimization-python
1
1"""
2Tests for solver configuration functionality.
3
4Tests the create_solver_config factory function and dynamic termination time.
5"""
6import pytest
7from portfolio_optimization.solver import create_solver_config
8from portfolio_optimization.domain import SolverConfigModel
9
10
11class TestCreateSolverConfig:
12 """Tests for the create_solver_config factory function."""
13
14 def test_default_termination(self):
15 """Default solver should terminate after 30 seconds."""
16 config = create_solver_config()
17 assert config.termination_config.spent_limit.seconds == 30
18
19 def test_custom_termination_60s(self):
20 """Custom termination time of 60 seconds should be respected."""
21 config = create_solver_config(termination_seconds=60)
22 assert config.termination_config.spent_limit.seconds == 60
23
24 def test_custom_termination_10s(self):
25 """Minimum termination time of 10 seconds should work."""
26 config = create_solver_config(termination_seconds=10)
27 assert config.termination_config.spent_limit.seconds == 10
28
29 def test_custom_termination_300s(self):
30 """Maximum termination time of 300 seconds (5 min) should work."""
31 config = create_solver_config(termination_seconds=300)
32 assert config.termination_config.spent_limit.seconds == 300
33
34 def test_solver_config_has_correct_solution_class(self):
35 """Solver config should reference PortfolioOptimizationPlan."""
36 from portfolio_optimization.domain import PortfolioOptimizationPlan
37 config = create_solver_config()
38 assert config.solution_class == PortfolioOptimizationPlan
39
40 def test_solver_config_has_correct_entity_class(self):
41 """Solver config should include StockSelection entity."""
42 from portfolio_optimization.domain import StockSelection
43 config = create_solver_config()
44 assert StockSelection in config.entity_class_list
45
46
47class TestSolverConfigModel:
48 """Tests for the SolverConfigModel Pydantic model."""
49
50 def test_default_values(self):
51 """SolverConfigModel should have default termination of 30 seconds."""
52 model = SolverConfigModel()
53 assert model.termination_seconds == 30
54
55 def test_custom_termination(self):
56 """SolverConfigModel should accept custom termination."""
57 model = SolverConfigModel(termination_seconds=60)
58 assert model.termination_seconds == 60
59
60 def test_alias_serialization(self):
61 """SolverConfigModel should serialize with camelCase alias."""
62 model = SolverConfigModel(termination_seconds=45)
63 data = model.model_dump(by_alias=True)
64 assert "terminationSeconds" in data
65 assert data["terminationSeconds"] == 45
66
67 def test_alias_deserialization(self):
68 """SolverConfigModel should deserialize from camelCase."""
69 model = SolverConfigModel.model_validate({"terminationSeconds": 90})
70 assert model.termination_seconds == 90
71
72 def test_minimum_validation(self):
73 """SolverConfigModel should reject termination < 10 seconds."""
74 with pytest.raises(ValueError):
75 SolverConfigModel(termination_seconds=5)
76
77 def test_maximum_validation(self):
78 """SolverConfigModel should reject termination > 300 seconds."""
79 with pytest.raises(ValueError):
80 SolverConfigModel(termination_seconds=400)
81
82 def test_boundary_min(self):
83 """SolverConfigModel should accept exactly 10 seconds."""
84 model = SolverConfigModel(termination_seconds=10)
85 assert model.termination_seconds == 10
86
87 def test_boundary_max(self):
88 """SolverConfigModel should accept exactly 300 seconds."""
89 model = SolverConfigModel(termination_seconds=300)
90 assert model.termination_seconds == 300
91 