wavereader / tests /test_openmeteo.py
lucashudsn's picture
Worker A (v2-core): port core package + scoring v2 with offline tests
fb7515e
Raw History Blame Contribute Delete
4.1 kB
"""Offline tests for wavereader.openmeteo (cache, merge, offset contract)."""
import json
import time
from pathlib import Path
import pytest
from wavereader import openmeteo as om
FIXTURE = Path(__file__).parent / "fixtures" / "openmeteo_bells.json"
def _use_tmp_cache(monkeypatch, tmp_path):
monkeypatch.setattr(om, "CACHE_DIR", tmp_path)
# ---- Fetch contract ----
def test_marine_fields_include_swell_components():
assert "swell_wave_direction" in om.MARINE_HOURLY_FIELDS
assert "swell_wave_period" in om.MARINE_HOURLY_FIELDS
assert "swell_wave_height" in om.MARINE_HOURLY_FIELDS
def test_cache_version_bumped():
# Old-shape (v2, no swell_* fields) cache must never serve as fresh.
assert om.CACHE_VERSION == 3
# ---- Seaward offset (Worker B reuse contract) ----
@pytest.mark.parametrize(
("lat", "lon", "expected"),
[
(-28.17, 153.55, (0.0, 0.13)), # east coast β†’ east
(-33.80, 115.00, (0.0, -0.13)), # west coast β†’ west
(-35.00, 138.00, (-0.13, 0.0)), # south coast SA β†’ south
(-38.37, 144.28, (-0.13, 0.0)), # Surf Coast VIC β†’ south
(-42.90, 147.30, (-0.13, 0.0)), # Tasmania β†’ south
],
)
def test_seaward_offset_directions(lat, lon, expected):
assert om._seaward_offset(lat, lon) == pytest.approx(expected)
# ---- Merge ----
def test_build_normalized_merges_and_carries_daily():
marine = {
"hourly": {
"time": ["2026-09-01T00:00", "2026-09-01T01:00"],
"wave_height": [1.2, 1.3],
"swell_wave_height": [1.0, 1.1],
"swell_wave_direction": [180.0, 185.0],
"swell_wave_period": [12.0, 12.5],
}
}
weather = {
"hourly": {
"time": ["2026-09-01T00:00", "2026-09-01T01:00"],
"wind_speed_10m": [10.0, 40.0],
"wind_direction_10m": [0.0, 90.0],
"wind_gusts_10m": [15.0, 50.0],
},
"daily": {
"time": ["2026-09-01"],
"sunrise": ["2026-09-01T06:00"],
"sunset": ["2026-09-01T18:00"],
},
}
out = om._build_normalized(marine, weather)
assert len(out["hourly"]) == 2
assert out["hourly"][0]["swell_wave_direction"] == 180.0
assert out["hourly"][0]["wind_speed_10m"] == 10.0
assert out["daily"]["sunrise"] == ["2026-09-01T06:00"]
assert out["daily"]["sunset"] == ["2026-09-01T18:00"]
# ---- Disk cache, fully offline ----
def test_get_forecast_serves_fresh_cache_without_network(monkeypatch, tmp_path):
_use_tmp_cache(monkeypatch, tmp_path)
payload = {"hourly": [{"time": "2026-09-01T12:00", "wave_height": 1.0}], "daily": {}}
om._save_cache(-38.37, 144.28, 3, payload)
assert om.get_forecast(-38.37, 144.28, days=3) == payload
def test_get_forecast_falls_back_to_stale_cache(monkeypatch, tmp_path):
_use_tmp_cache(monkeypatch, tmp_path)
payload = {"hourly": [{"time": "2026-09-01T12:00", "wave_height": 2.0}]}
path = om._cache_path(-38.37, 144.28, 3)
tmp_path.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps({"fetched_at": 0.0, "data": payload}))
# httpx.Client raises (conftest) β†’ stale fallback, not an error.
assert om.get_forecast(-38.37, 144.28, days=3) == payload
def test_get_forecast_raises_with_no_cache_and_no_network(monkeypatch, tmp_path):
_use_tmp_cache(monkeypatch, tmp_path)
with pytest.raises(Exception):
om.get_forecast(-38.37, 144.28, days=3)
def test_cache_key_embeds_version():
assert om._cache_key(-38.37, 144.28, 3).startswith(f"v{om.CACHE_VERSION}_")
# ---- Recorded live fixture ----
def test_recorded_bells_fixture_shape():
fc = json.loads(FIXTURE.read_text())
assert len(fc["hourly"]) == 72
first = fc["hourly"][0]
for field in ("swell_wave_height", "swell_wave_direction", "swell_wave_period",
"wave_height", "wind_speed_10m", "wind_direction_10m"):
assert field in first, f"fixture missing {field}"
assert "sunrise" in fc["daily"] and "sunset" in fc["daily"]
assert len(fc["daily"]["time"]) >= 3