|
1 | 1 | import json |
2 | 2 | import sys |
| 3 | +import httpx |
3 | 4 | import pytest |
4 | 5 | from unittest.mock import patch, MagicMock |
5 | 6 | from typing import Literal |
@@ -110,6 +111,19 @@ def make_mock_budget_impact_data( |
110 | 111 | } |
111 | 112 |
|
112 | 113 |
|
| 114 | +def make_http_status_error(status_code: int, payload: dict) -> httpx.HTTPStatusError: |
| 115 | + request = httpx.Request( |
| 116 | + "POST", |
| 117 | + "https://policyengine-staging--policyengine-simulation-gateway-web-app.modal.run/simulate/economy/budget-window", |
| 118 | + ) |
| 119 | + response = httpx.Response(status_code, json=payload, request=request) |
| 120 | + return httpx.HTTPStatusError( |
| 121 | + f"Client error '{status_code}'", |
| 122 | + request=request, |
| 123 | + response=response, |
| 124 | + ) |
| 125 | + |
| 126 | + |
113 | 127 | class TestEconomyService: |
114 | 128 | class TestGetEconomicImpact: |
115 | 129 | @pytest.fixture |
@@ -1010,6 +1024,59 @@ def test__given_batch_submission_fails__clears_start_claim( |
1010 | 1024 | "budget-window-cache-key", MOCK_PROCESS_ID |
1011 | 1025 | ) |
1012 | 1026 |
|
| 1027 | + def test__given_modal_rejects_batch_submission__returns_failed_result( |
| 1028 | + self, |
| 1029 | + economy_service, |
| 1030 | + base_params, |
| 1031 | + mock_simulation_api, |
| 1032 | + mock_budget_window_cache, |
| 1033 | + ): |
| 1034 | + mock_simulation_api.run_budget_window_batch.side_effect = make_http_status_error( |
| 1035 | + 400, |
| 1036 | + { |
| 1037 | + "detail": ( |
| 1038 | + "Invalid Hugging Face dataset URI: " |
| 1039 | + "'hf://policyengine/nonexistent-budget-window-test.h5@0.0.0'" |
| 1040 | + ) |
| 1041 | + }, |
| 1042 | + ) |
| 1043 | + |
| 1044 | + result = economy_service.get_budget_window_economic_impact(**base_params) |
| 1045 | + |
| 1046 | + assert result.status == ImpactStatus.ERROR |
| 1047 | + assert result.data is None |
| 1048 | + assert result.error == ( |
| 1049 | + "Invalid Hugging Face dataset URI: " |
| 1050 | + "'hf://policyengine/nonexistent-budget-window-test.h5@0.0.0'" |
| 1051 | + ) |
| 1052 | + assert result.completed_years == [] |
| 1053 | + assert result.computing_years == [] |
| 1054 | + assert result.queued_years == ["2026", "2027", "2028"] |
| 1055 | + assert result.cache_status == "miss" |
| 1056 | + mock_budget_window_cache.clear_starting_claim.assert_called_once_with( |
| 1057 | + "budget-window-cache-key", MOCK_PROCESS_ID |
| 1058 | + ) |
| 1059 | + mock_budget_window_cache.store_batch_job_id.assert_not_called() |
| 1060 | + |
| 1061 | + def test__given_modal_server_error_on_batch_submission__raises( |
| 1062 | + self, |
| 1063 | + economy_service, |
| 1064 | + base_params, |
| 1065 | + mock_simulation_api, |
| 1066 | + mock_budget_window_cache, |
| 1067 | + ): |
| 1068 | + mock_simulation_api.run_budget_window_batch.side_effect = ( |
| 1069 | + make_http_status_error(500, {"detail": "gateway unavailable"}) |
| 1070 | + ) |
| 1071 | + |
| 1072 | + with pytest.raises(httpx.HTTPStatusError): |
| 1073 | + economy_service.get_budget_window_economic_impact(**base_params) |
| 1074 | + |
| 1075 | + mock_budget_window_cache.clear_starting_claim.assert_called_once_with( |
| 1076 | + "budget-window-cache-key", MOCK_PROCESS_ID |
| 1077 | + ) |
| 1078 | + mock_budget_window_cache.store_batch_job_id.assert_not_called() |
| 1079 | + |
1013 | 1080 | def test__given_cliff_target__raises_value_error( |
1014 | 1081 | self, economy_service, base_params |
1015 | 1082 | ): |
|
0 commit comments