Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,19 @@ COPY ./backend/uv.lock /app/
COPY ./backend/packages/wps-api/pyproject.toml /app/packages/wps-api/
COPY ./backend/packages/wps-shared/pyproject.toml /app/packages/wps-shared/
COPY ./backend/packages/wps-shared/src /app/packages/wps-shared/src
COPY ./backend/packages/wps-wf1/pyproject.toml /app/packages/wps-wf1/
COPY ./backend/packages/wps-wf1/src /app/packages/wps-wf1/src

# Switch to root to set file permissions
USER 0

# Set configuration files to read-only for security
RUN chmod 444 /app/pyproject.toml /app/uv.lock \
/app/packages/wps-api/pyproject.toml \
/app/packages/wps-shared/pyproject.toml
RUN chmod -R a-w /app/packages/wps-shared/src
/app/packages/wps-shared/pyproject.toml \
/app/packages/wps-wf1/pyproject.toml
RUN chmod -R a-w /app/packages/wps-shared/src \
/app/packages/wps-wf1/src

# Switch back to non-root user
USER $USERNAME
Expand Down Expand Up @@ -77,6 +81,7 @@ WORKDIR /app
COPY --from=builder /app/pyproject.toml /app/
COPY --from=builder /app/packages/wps-api/pyproject.toml /app/packages/wps-api/
COPY --from=builder /app/packages/wps-shared/pyproject.toml /app/packages/wps-shared/
COPY --from=builder /app/packages/wps-wf1/pyproject.toml /app/packages/wps-wf1/

# Switch back to our non-root user
USER $USERNAME
Expand All @@ -96,8 +101,9 @@ COPY ./backend/packages/wps-api/alembic.ini /app
COPY ./backend/packages/wps-api/prestart.sh /app
COPY ./backend/packages/wps-api/start.sh /app

# Make uv happy by copying wps_shared
# Make uv happy by copying wps_shared and wps_wf1
COPY ./backend/packages/wps-shared/src /app/packages/wps-shared/src
COPY ./backend/packages/wps-wf1/src /app/packages/wps-wf1/src

# Copy installed Python packages
COPY --from=builder /app/.venv /app/.venv
Expand All @@ -115,7 +121,7 @@ ENV VIRTUAL_ENV="/app/.venv"
# root user please
USER 0
# Remove write permissions from copied configuration and source files for security
RUN chmod -R a-w /app/pyproject.toml /app/packages/wps-api/pyproject.toml /app/advisory /app/libs /app/alembic /app/alembic.ini /app/prestart.sh /app/start.sh /app/packages/wps-shared/src
RUN chmod -R a-w /app/pyproject.toml /app/packages/wps-api/pyproject.toml /app/advisory /app/libs /app/alembic /app/alembic.ini /app/prestart.sh /app/start.sh /app/packages/wps-shared/src /app/packages/wps-wf1/src
# We don't know what user uv is going to run as, so we give everyone write access directories
# in the app folder. We need write access for .pyc files to be created. .pyc files are good,
# they speed up python.
Expand Down
12 changes: 9 additions & 3 deletions Dockerfile.jobs
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,19 @@ COPY ./backend/uv.lock /app/
COPY ./backend/packages/wps-jobs/pyproject.toml /app/packages/wps-jobs/
COPY ./backend/packages/wps-shared/pyproject.toml /app/packages/wps-shared/
COPY ./backend/packages/wps-shared/src /app/packages/wps-shared/src
COPY ./backend/packages/wps-wf1/pyproject.toml /app/packages/wps-wf1/
COPY ./backend/packages/wps-wf1/src /app/packages/wps-wf1/src

# Switch to root to set file permissions
USER 0

# Set configuration files to read-only for security
RUN chmod 444 /app/pyproject.toml /app/uv.lock \
/app/packages/wps-jobs/pyproject.toml \
/app/packages/wps-shared/pyproject.toml
RUN chmod -R a-w /app/packages/wps-shared/src
/app/packages/wps-shared/pyproject.toml \
/app/packages/wps-wf1/pyproject.toml
RUN chmod -R a-w /app/packages/wps-shared/src \
/app/packages/wps-wf1/src

# Switch back to non-root user
USER $USERNAME
Expand Down Expand Up @@ -75,13 +79,15 @@ WORKDIR /app
COPY --from=builder /app/pyproject.toml /app/
COPY --from=builder /app/packages/wps-jobs/pyproject.toml /app/packages/wps-jobs/
COPY --from=builder /app/packages/wps-shared/pyproject.toml /app/packages/wps-shared/
COPY --from=builder /app/packages/wps-wf1/pyproject.toml /app/packages/wps-wf1/

# Switch back to our non-root user
USER $USERNAME

# Copy the jobs from src layout:
COPY ./backend/packages/wps-jobs/src /app
COPY ./backend/packages/wps-shared/src /app/packages/wps-shared/src
COPY ./backend/packages/wps-wf1/src /app/packages/wps-wf1/src

# Copy installed Python packages
COPY --from=builder /app/.venv /app/.venv
Expand All @@ -96,7 +102,7 @@ USER 0
# Create writable data directory for library caches (e.g., herbie BallTree)
RUN mkdir -p /data && chmod 777 /data
# Remove write permissions from copied configuration and source files for security
RUN chmod -R a-w /app/pyproject.toml /app/packages/wps-jobs/pyproject.toml /app/weather_model_jobs /app/packages/wps-shared/src
RUN chmod -R a-w /app/pyproject.toml /app/packages/wps-jobs/pyproject.toml /app/weather_model_jobs /app/packages/wps-shared/src /app/packages/wps-wf1/src
# We don't know what user uv is going to run as, so we give everyone write access directories
# in the app folder. We need write access for .pyc files to be created. .pyc files are good,
# they speed up python.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import asyncio
import json
import math
from collections import namedtuple
Expand All @@ -10,10 +9,8 @@
from aiohttp import ClientSession
from app.tests import get_complete_filename
from fastapi.testclient import TestClient

from wps_shared.db.models.auto_spatial_advisory import (
AdvisoryHFIWindSpeed,
AdvisoryTPIStats,
RunParameters,
SFMSFuelType,
TPIClassEnum,
Expand All @@ -24,7 +21,6 @@
FireZoneHFIStats,
HFIStatsResponse,
HfiThreshold,
LatestSFMSRunParameterRangeResponse,
SFMSRunParameter,
)
from wps_shared.tests.common import default_mock_client_get
Expand Down Expand Up @@ -281,6 +277,7 @@ def client():
yield test_client


@patch("app.routers.fba.get_auth_header", mock_get_auth_header)
@pytest.mark.usefixtures("mock_jwt_decode")
@pytest.mark.parametrize(
"status, expected_fire_centers", [(200, "test_fba_endpoint_fire_centers.json")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
import math
import os
import logging
from unittest.mock import MagicMock
import pytest
from pytest_mock import MockerFixture
from wps_wf1.wfwx_client import WfwxClient
from wps_shared.db.models.observations import HourlyActual
from app.tests.jobs.job_fixtures import mock_wfwx_stations, mock_wfwx_response
from wps_shared.utils.time import get_utc_now
Expand All @@ -19,9 +21,13 @@ def mock_hourly_actuals(mocker: MockerFixture):
""" Mocks out hourly actuals as async result """
wfwx_hourlies = mock_wfwx_response()
future_wfwx_stations = mock_wfwx_stations()
mock_wfwx_client = MagicMock()
mock_wfwx_client.fetch_paged_response_generator = iter(wfwx_hourlies)
mocker.patch("wps_shared.wildfire_one.wfwx_api.wfwx_station_list_mapper", return_value=future_wfwx_stations)
mocker.patch("wps_shared.wildfire_one.wfwx_api.get_hourly_actuals_all_stations", return_value=wfwx_hourlies)
mocker.patch("wps_shared.wildfire_one.wildfire_fetchers.fetch_paged_response_generator", return_value=iter(wfwx_hourlies))
mocker.patch(
"wps_shared.wildfire_one.wfwx_api.create_wps_wf1_client", return_value=mock_wfwx_client
)


def test_hourly_actuals_job(monkeypatch, mocker: MockerFixture, mock_hourly_actuals):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
""" Unit tests for the fireweather noon forecats job """
import os
import logging
from unittest.mock import MagicMock
import pytest
from pytest_mock import MockerFixture
from app.jobs import noon_forecasts
Expand All @@ -15,10 +16,14 @@ def mock_noon_forecasts(mocker: MockerFixture):
""" Mocks out noon forecasts as async result """
wfwx_hourlies = mock_wfwx_response()
future_wfwx_stations = mock_wfwx_stations()
mock_wfwx_client = MagicMock()
mock_wfwx_client.fetch_paged_response_generator = iter(wfwx_hourlies)

mocker.patch("wps_shared.wildfire_one.wfwx_api.wfwx_station_list_mapper", return_value=future_wfwx_stations)
mocker.patch("wps_shared.wildfire_one.wfwx_api.get_noon_forecasts_all_stations", return_value=wfwx_hourlies)
mocker.patch("wps_shared.wildfire_one.wildfire_fetchers.fetch_paged_response_generator", return_value=iter(wfwx_hourlies))
mocker.patch(
"wps_shared.wildfire_one.wfwx_api.create_wps_wf1_client", return_value=mock_wfwx_client
)


def test_noon_forecasts_bot(monkeypatch, mocker: MockerFixture, mock_noon_forecasts):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from unittest.mock import Mock, patch
import pytest
from math import isclose
import app
from wps_shared.db.models.morecast_v2 import MorecastForecastRecord
from app.morecast_v2.forecasts import (
actual_exists,
Expand Down Expand Up @@ -242,8 +243,13 @@ def test_construct_wf1_forecast_update():
@pytest.mark.anyio
@patch("aiohttp.ClientSession.get")
@patch("app.morecast_v2.forecasts.get_forecasts_for_stations_by_date_range", return_value=[station_1_daily_from_wf1])
async def test_construct_wf1_forecasts_new(_, mock_get):
async def test_construct_wf1_forecasts_new(_, mock_get, monkeypatch: pytest.MonkeyPatch):
async def mock_get_auth_header(_):
return {}

monkeypatch.setattr(app.morecast_v2.forecasts, "get_no_cache_auth_header", mock_get_auth_header)
result = await construct_wf1_forecasts(mock_get, [morecast_input_1, morecast_input_2], wfwx_weather_stations, "user")

assert len(result) == 2
# existing forecast
assert_wf1_forecast(result[0], morecast_input_1, station_1_daily_from_wf1.forecast_id, station_1_daily_from_wf1.created_by, station_1_url, "1")
Expand Down
4 changes: 4 additions & 0 deletions backend/packages/wps-shared/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies = [
"asyncpg>=0.30.0,<1",
"redis>=7.0.0,<8",
"gdal==3.9.2",
"wps-wf1",
]

[project.optional-dependencies]
Expand All @@ -35,3 +36,6 @@ build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/wps_shared"]

[tool.uv.sources]
wps-wf1 = { workspace = true }
10 changes: 8 additions & 2 deletions backend/packages/wps-shared/src/wps_shared/tests/common.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Mock modules/classes"""

import json
import logging
import os
import json
from typing import Optional
from contextlib import asynccontextmanager
from typing import Optional

from wps_shared.auth import ASA_TEST_IDIR_GUID
from wps_shared.tests.fixtures.loader import FixtureFinder

Expand Down Expand Up @@ -109,6 +110,11 @@ async def json(self) -> dict:
"""Return json response"""
return self._json

def raise_for_status(self) -> None:
"""Mimic aiohttp.ClientResponse.raise_for_status()."""
if 400 <= self.status:
raise Exception(f"HTTP {self.status}")


class DefaultMockAioSession:
"""Mock aiobotocore.session.AioSession"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@
"get": {
"{'query': 'weatherTimestamp==1618862400000', 'page': 0, 'size': '1000'}": {
"None": "wfwx/v1/dailies/rsql__query_weatherTimestamp==1618862400000_page_0_size_1000.json"
},
"{'query': 'weatherTimestamp==1618862400000', 'page': 0, 'size': 1000}": {
"None": "wfwx/v1/dailies/rsql__query_weatherTimestamp==1618862400000_page_0_size_1000.json"
}
}
},
Expand Down
Loading
Loading