File size: 4,104 Bytes
fb7515e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""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