|
15 | 15 | SimulationRequest, |
16 | 16 | JobSubmitResponse, |
17 | 17 | JobStatusResponse, |
| 18 | + _default_missing_state_tax_revenue_impact, |
| 19 | + _enforce_max_payload_size, |
| 20 | + _move_internal_telemetry_alias, |
| 21 | + _strip_internal_passthrough_fields, |
18 | 22 | ) |
19 | 23 | from tests.fixtures.budget_window_outputs import make_single_year_macro_output |
20 | 24 |
|
@@ -113,6 +117,15 @@ def test_ping_response_serializes_correctly(self): |
113 | 117 | class TestSimulationRequest: |
114 | 118 | """Tests for SimulationRequest model.""" |
115 | 119 |
|
| 120 | + def test_request_pre_validators_ignore_non_dict_payloads(self): |
| 121 | + """Defensive pre-validators must leave non-mapping input to Pydantic.""" |
| 122 | + |
| 123 | + value = "not-a-request-object" |
| 124 | + |
| 125 | + assert _move_internal_telemetry_alias(value) == value |
| 126 | + assert _strip_internal_passthrough_fields(value) == value |
| 127 | + assert _enforce_max_payload_size(value) == value |
| 128 | + |
116 | 129 | def test_simulation_request_requires_country(self): |
117 | 130 | """ |
118 | 131 | Given no country |
@@ -252,6 +265,13 @@ def test_simulation_request_rejects_payload_just_above_256kb(self): |
252 | 265 | with pytest.raises(ValidationError, match="too large"): |
253 | 266 | SimulationRequest(**payload) |
254 | 267 |
|
| 268 | + def test_simulation_request_size_cap_defers_non_json_serializable_payloads(self): |
| 269 | + """The size cap is best-effort; non-JSON objects fail later.""" |
| 270 | + |
| 271 | + payload = {("tuple", "key"): "not-json-serializable"} |
| 272 | + |
| 273 | + assert _enforce_max_payload_size(payload) is payload |
| 274 | + |
255 | 275 | def test_simulation_request_accepts_typed_telemetry_envelope(self): |
256 | 276 | """ |
257 | 277 | Given a telemetry envelope |
@@ -555,6 +575,34 @@ def test_budget_window_batch_submit_response_serializes_correctly(self): |
555 | 575 | class TestBudgetWindowBatchStatusResponse: |
556 | 576 | """Tests for budget-window batch status responses.""" |
557 | 577 |
|
| 578 | + def test_single_year_macro_output_budget_normalizer_defensive_branches(self): |
| 579 | + """The state-tax default only applies to object outputs with budgets.""" |
| 580 | + |
| 581 | + raw_value = "not-a-macro-output" |
| 582 | + assert _default_missing_state_tax_revenue_impact(raw_value) == raw_value |
| 583 | + |
| 584 | + no_budget = {"poverty": {}} |
| 585 | + assert _default_missing_state_tax_revenue_impact(no_budget) is no_budget |
| 586 | + |
| 587 | + non_object_budget = {"budget": "not-an-object"} |
| 588 | + assert ( |
| 589 | + _default_missing_state_tax_revenue_impact(non_object_budget) |
| 590 | + is non_object_budget |
| 591 | + ) |
| 592 | + |
| 593 | + existing_state_tax = {"budget": {"state_tax_revenue_impact": 12}} |
| 594 | + assert ( |
| 595 | + _default_missing_state_tax_revenue_impact(existing_state_tax) |
| 596 | + is existing_state_tax |
| 597 | + ) |
| 598 | + |
| 599 | + missing_state_tax = {"budget": {"tax_revenue_impact": 12}} |
| 600 | + normalized = _default_missing_state_tax_revenue_impact(missing_state_tax) |
| 601 | + |
| 602 | + assert normalized is not missing_state_tax |
| 603 | + assert missing_state_tax["budget"].get("state_tax_revenue_impact") is None |
| 604 | + assert normalized["budget"]["state_tax_revenue_impact"] == 0.0 |
| 605 | + |
558 | 606 | def test_budget_window_result_requires_years_and_outputs_by_year(self): |
559 | 607 | with pytest.raises(ValidationError): |
560 | 608 | BudgetWindowResult( |
|
0 commit comments