-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathtest_history.py
More file actions
168 lines (149 loc) · 5.55 KB
/
test_history.py
File metadata and controls
168 lines (149 loc) · 5.55 KB
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import pytest
from datetime import date, datetime
from unittest.mock import AsyncMock, patch
BASE_QUERY = {
"lat": 40.7128,
"lon": -74.0060,
"start": "2024-01-01",
"end": "2024-01-02",
"variables": ["temperature_2m"],
"radius_km": 10.0
}
class TestHistoryRoutes:
"""
Tests for /api/v1/history/ routes.
Both routes use MongoDB $near geospatial queries which mongomock does not
support! We test the cache miss path only `find_one` is patched to return
None, triggering the Open-Meteo fallback. The cache hit path (reading from
DB) requires a real MongoDB instance and is intentionally skipped.
"""
@pytest.fixture
def mock_hourly_observation(self):
"""Single hourly observation — reusable unit of hourly data."""
return {
"timestamp": datetime(2024, 1, 1, 12, 0, 0).isoformat(),
"values": {
"temperature_2m": 25.5,
"humidity_2m": 60.0,
"wind_speed_2m": 5.2
}
}
@pytest.fixture
def mock_daily_observation(self):
"""Single daily observation — reusable unit of daily data."""
return {
"date": date(2024, 1, 1).isoformat(),
"values": {
"temperature_2m_max": 28.0,
"temperature_2m_min": 15.0,
"humidity_2m_max": 70.0
}
}
@pytest.fixture
def mock_hourly_response(self, mock_hourly_observation):
return {
"location": {"lat": 40.7128, "lon": -74.0060},
"data": [mock_hourly_observation],
"source": "openmeteo"
}
@pytest.fixture
def mock_daily_response(self, mock_daily_observation):
"""
Shaped like DailyResponse.
Same principle as mock_hourly_response.
"""
return {
"location": {"lat": 40.7128, "lon": -74.0060},
"data": [mock_daily_observation],
"source": "openmeteo"
}
@pytest.mark.anyio
async def test_get_hourly_history_cache_miss_fetches_from_openmeteo(
self, async_client, auth_headers, mock_hourly_response
):
mock_provider = AsyncMock()
mock_provider.get_hourly_history.return_value = \
mock_hourly_response["data"]
with patch(
"src.api.api_v1.endpoints.history.HourlyHistory.find_one",
new_callable=AsyncMock,
return_value=None # cache miss
), patch(
"src.api.api_v1.endpoints.history.WeatherClientFactory.get_provider",
return_value=mock_provider
):
response = await async_client.post(
"/api/v1/history/hourly/",
json=BASE_QUERY,
headers=auth_headers,
)
assert response.status_code == 200
data = response.json()
assert data["source"] == "openmeteo"
assert data["location"] == {"lat": BASE_QUERY["lat"], "lon": BASE_QUERY["lon"]}
mock_provider.get_hourly_history.assert_called_once_with(
BASE_QUERY["lat"],
BASE_QUERY["lon"],
date.fromisoformat(BASE_QUERY["start"]),
date.fromisoformat(BASE_QUERY["end"]),
BASE_QUERY["variables"],
)
@pytest.mark.anyio
async def test_get_hourly_history_returns_403_without_auth(
self, async_client
):
response = await async_client.post(
"/api/v1/history/hourly/", json=BASE_QUERY
)
assert response.status_code == 403
@pytest.mark.skip(
reason="Cache hit path uses $near geospatial query via find_one and "
"find_many — not supported by mongomock. Would require a real MongoDB."
)
async def test_get_hourly_history_cache_hit_returns_db_data(self):
pass
@pytest.mark.anyio
async def test_get_daily_history_cache_miss_fetches_from_openmeteo(
self, async_client, auth_headers, mock_daily_response
):
mock_provider = AsyncMock()
mock_provider.get_daily_history.return_value = \
mock_daily_response["data"]
with patch(
"src.api.api_v1.endpoints.history.DailyHistory.find_one",
new_callable=AsyncMock,
return_value=None # cache miss
), patch(
"src.api.api_v1.endpoints.history.WeatherClientFactory.get_provider",
return_value=mock_provider
):
response = await async_client.post(
"/api/v1/history/daily/",
json=BASE_QUERY,
headers=auth_headers,
)
assert response.status_code == 200
data = response.json()
assert data["source"] == "openmeteo"
assert data["location"] == {"lat": BASE_QUERY["lat"], "lon": BASE_QUERY["lon"]}
mock_provider.get_daily_history.assert_called_once_with(
BASE_QUERY["lat"],
BASE_QUERY["lon"],
date.fromisoformat(BASE_QUERY["start"]),
date.fromisoformat(BASE_QUERY["end"]),
BASE_QUERY["variables"],
)
@pytest.mark.anyio
async def test_get_daily_history_returns_403_without_auth(
self, async_client
):
response = await async_client.post(
"/api/v1/history/daily/", json=BASE_QUERY
)
assert response.status_code == 403
@pytest.mark.skip(
reason="Cache hit path uses $near geospatial query via find_one and "
"find_many — not supported by mongomock. Would require a real MongoDB."
)
async def test_get_daily_history_cache_hit_returns_db_data(self):
pass