From 28731159acf115c05cda9ff2b14516cbb685568d Mon Sep 17 00:00:00 2001 From: Conor Brady Date: Tue, 1 Oct 2024 11:54:15 -0700 Subject: [PATCH] Morecast: Adds instructive error message for failed forecast publishes (#3973) --- api/app/routers/morecast_v2.py | 4 +- .../morecast_v2/test_morecast_v2_endpoint.py | 53 ++++++++++++++++++- api/app/wildfire_one/wfwx_post_api.py | 10 +++- 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/api/app/routers/morecast_v2.py b/api/app/routers/morecast_v2.py index cfca4c49d..ceceb8fbd 100644 --- a/api/app/routers/morecast_v2.py +++ b/api/app/routers/morecast_v2.py @@ -29,7 +29,7 @@ from app.utils.time import get_hour_20_from_date, get_utc_now from app.weather_models.fetch.predictions import fetch_latest_model_run_predictions_by_station_code_and_date_range from app.wildfire_one.wfwx_api import get_auth_header, get_dailies_for_stations_and_date, get_daily_determinates_for_stations_and_date, get_wfwx_stations_from_station_codes -from app.wildfire_one.wfwx_post_api import post_forecasts +from app.wildfire_one.wfwx_post_api import WF1_HTTP_ERROR, post_forecasts from app.utils.redis import clear_cache_matching @@ -120,7 +120,7 @@ async def save_forecasts(forecasts: MoreCastForecastRequest, response: Response, clear_cache_matching(station_id) except Exception as exc: logger.error("Encountered error posting forecast data to WF1 API", exc_info=exc) - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Error submitting forecast(s) to WF1") + raise WF1_HTTP_ERROR with get_write_session_scope() as db_session: save_all_forecasts(db_session, forecasts_to_save) diff --git a/api/app/tests/morecast_v2/test_morecast_v2_endpoint.py b/api/app/tests/morecast_v2/test_morecast_v2_endpoint.py index 5ee9336ef..704ad1a0b 100644 --- a/api/app/tests/morecast_v2/test_morecast_v2_endpoint.py +++ b/api/app/tests/morecast_v2/test_morecast_v2_endpoint.py @@ -1,3 +1,4 @@ +import aiohttp from fastapi.testclient import TestClient from httpx import AsyncClient import pytest @@ -94,6 +95,56 @@ async def mock_get_auth_header(_): assert response.status_code == 201 +@pytest.mark.anyio +def test_post_forecast_authorized_error(client: TestClient, monkeypatch: pytest.MonkeyPatch): + """Allowed to post station changes with correct role""" + + def mock_admin_role_function(*_, **__): + return MockJWTDecodeWithRole("morecast2_write_forecast") + + monkeypatch.setattr(decode_fn, mock_admin_role_function) + + async def mock_format_as_wf1_post_forecasts(client_session, forecasts_to_save, username, headers): + return [] + + monkeypatch.setattr(app.routers.morecast_v2, "format_as_wf1_post_forecasts", mock_format_as_wf1_post_forecasts) + + class MockResponse: + status = 500 + + async def text(self): + return "Bad Request" + + class MockClientSession: + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + pass + + async def post(self, url): + return MockResponse() + + # Use monkeypatch to replace the ClientSession with our mock class + monkeypatch.setattr(aiohttp, "ClientSession", lambda: MockClientSession()) + + async def mock_get_auth_header(_): + return dict() + + monkeypatch.setattr(app.routers.morecast_v2, "get_auth_header", mock_get_auth_header) + + response = client.post(morecast_v2_post_url, json=forecast.model_dump()) + assert response.status_code == 500 + assert ( + response.json()["detail"] + == """ + Error submitting forecasts to WF1, please retry. + All your forecast inputs have been saved as a draft on your browser and can be submitted at a later time. + If the problem persists, use the following link to verify the status of the WF1 service: https://wfapps.nrs.gov.bc.ca/pub/wfwx-fireweather-web/stations + """ + ) + + def test_post_forecasts_by_date_range_unauthorized(client: TestClient): """forecast role required for persisting a forecast""" response = client.post(morecast_v2_post_by_date_range_url, json=[]) @@ -108,7 +159,7 @@ def mock_admin_role_function(*_, **__): monkeypatch.setattr(decode_fn, mock_admin_role_function) - response = client.post(morecast_v2_post_by_date_range_url, json=stations.dict()) + response = client.post(morecast_v2_post_by_date_range_url, json=stations.model_dump()) assert response.status_code == 200 diff --git a/api/app/wildfire_one/wfwx_post_api.py b/api/app/wildfire_one/wfwx_post_api.py index 70506bd41..9e2f1ef32 100644 --- a/api/app/wildfire_one/wfwx_post_api.py +++ b/api/app/wildfire_one/wfwx_post_api.py @@ -11,6 +11,14 @@ logger = logging.getLogger(__name__) WF1_FORECAST_POST_URL = f"{config.get('WFWX_BASE_URL')}/v1/dailies/daily-bulk" +WF1_HTTP_ERROR = HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=""" + Error submitting forecasts to WF1, please retry. + All your forecast inputs have been saved as a draft on your browser and can be submitted at a later time. + If the problem persists, use the following link to verify the status of the WF1 service: https://wfapps.nrs.gov.bc.ca/pub/wfwx-fireweather-web/stations + """, +) async def post_forecasts(session: ClientSession, forecasts: List[WF1PostForecast]): @@ -25,4 +33,4 @@ async def post_forecasts(session: ClientSession, forecasts: List[WF1PostForecast logger.info("submitted forecasts to wf1 %s.", response_json) else: logger.error(f"error submitting forecasts to wf1 {response_json}") - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Error submitting forecast(s) to WF1") + raise WF1_HTTP_ERROR