forked from jeroenterheerdt/HAsmartirrigation
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_weather_records_proof.py
More file actions
275 lines (215 loc) · 9.23 KB
/
test_weather_records_proof.py
File metadata and controls
275 lines (215 loc) · 9.23 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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
#!/usr/bin/env python3
"""
Test to prove that weather records retrieval is working correctly after the datetime parsing fix.
This test demonstrates that:
1. Mixed datetime types (strings, datetime objects, None) can be sorted safely
2. Weather records are returned in the correct format for frontend display
3. The websocket_get_weather_records function works with real data scenarios
"""
import asyncio
import datetime
import os
import sys
from unittest.mock import AsyncMock, Mock
from dateutil import parser as dateutil_parser
# Add the custom component path to sys.path
sys.path.insert(
0, os.path.join(os.path.dirname(__file__), "custom_components", "smart_irrigation")
)
import const
from websockets import _safe_parse_datetime, websocket_get_weather_records
async def test_safe_parse_datetime():
"""Test the _safe_parse_datetime function with various input types."""
print("Testing _safe_parse_datetime function...")
# Test with datetime object
dt = datetime.datetime(2023, 12, 1, 12, 0, 0)
result = _safe_parse_datetime(dt)
assert isinstance(result, datetime.datetime)
print(f"✓ Datetime object: {dt} -> {result}")
# Test with timezone-aware datetime
dt_tz = datetime.datetime(2023, 12, 1, 12, 0, 0, tzinfo=datetime.timezone.utc)
result = _safe_parse_datetime(dt_tz)
assert isinstance(result, datetime.datetime)
assert result.tzinfo is None # Should be converted to naive UTC
print(f"✓ Timezone-aware datetime: {dt_tz} -> {result}")
# Test with ISO string
iso_string = "2023-12-01T12:00:00Z"
result = _safe_parse_datetime(iso_string)
assert isinstance(result, datetime.datetime)
print(f"✓ ISO string: {iso_string} -> {result}")
# Test with ISO string without timezone
iso_string_naive = "2023-12-01T12:00:00"
result = _safe_parse_datetime(iso_string_naive)
assert isinstance(result, datetime.datetime)
print(f"✓ ISO string (naive): {iso_string_naive} -> {result}")
# Test with invalid string
invalid_string = "not-a-datetime"
result = _safe_parse_datetime(invalid_string)
assert result == datetime.datetime.min
print(f"✓ Invalid string: {invalid_string} -> {result}")
# Test with None
result = _safe_parse_datetime(None)
assert result == datetime.datetime.min
print(f"✓ None value -> {result}")
print("✓ All _safe_parse_datetime tests passed!\n")
def create_mock_hass_with_weather_data():
"""Create a mock Home Assistant instance with test weather data."""
# Create sample weather data with mixed datetime types
sample_mapping_data = [
{
const.RETRIEVED_AT: datetime.datetime(
2023, 12, 1, 12, 0, 0
), # datetime object
const.MAPPING_TEMPERATURE: 20.5,
const.MAPPING_HUMIDITY: 65.0,
const.MAPPING_PRECIPITATION: 0.0,
},
{
const.RETRIEVED_AT: "2023-12-01T11:30:00Z", # ISO string with timezone
const.MAPPING_TEMPERATURE: 19.8,
const.MAPPING_HUMIDITY: 67.2,
const.MAPPING_PRECIPITATION: 0.1,
},
{
const.RETRIEVED_AT: "2023-12-01T11:00:00", # ISO string without timezone
const.MAPPING_TEMPERATURE: 19.2,
const.MAPPING_HUMIDITY: 70.1,
const.MAPPING_PRECIPITATION: 0.5,
},
{
const.RETRIEVED_AT: None, # None value (should go to end)
const.MAPPING_TEMPERATURE: 18.5,
const.MAPPING_HUMIDITY: 72.0,
const.MAPPING_PRECIPITATION: 1.0,
},
{
const.RETRIEVED_AT: "invalid-datetime", # Invalid string (should go to end)
const.MAPPING_TEMPERATURE: 17.8,
const.MAPPING_HUMIDITY: 75.5,
const.MAPPING_PRECIPITATION: 1.5,
},
]
# Create mock mapping
mock_mapping = {
const.MAPPING_DATA: sample_mapping_data,
}
# Create mock store
mock_store = Mock()
mock_store.get_mapping.return_value = mock_mapping
# Create mock coordinator
mock_coordinator = Mock()
mock_coordinator.store = mock_store
# Create mock hass
mock_hass = Mock()
mock_hass.data = {const.DOMAIN: {"coordinator": mock_coordinator}}
# Create mock connection
mock_connection = Mock()
mock_connection.send_result = Mock()
return mock_hass, mock_connection
async def test_websocket_get_weather_records():
"""Test the websocket_get_weather_records function with mixed datetime types."""
print("Testing websocket_get_weather_records function...")
mock_hass, mock_connection = create_mock_hass_with_weather_data()
# Create test message
msg = {"id": 123, "mapping_id": "1", "limit": 10}
# Call the function
await websocket_get_weather_records(mock_hass, mock_connection, msg)
# Verify that send_result was called
assert mock_connection.send_result.called
call_args = mock_connection.send_result.call_args
# Extract the results
message_id = call_args[0][0]
weather_records = call_args[0][1]
assert message_id == 123
assert isinstance(weather_records, list)
print(f"✓ Returned {len(weather_records)} weather records")
# Verify the records are properly formatted and sorted
valid_records = [r for r in weather_records if r.get("temperature") is not None]
print(f"✓ {len(valid_records)} records have valid weather data")
# Check that records with valid timestamps come first
timestamps = [r.get("timestamp") for r in weather_records]
valid_timestamps = [t for t in timestamps if t not in [None, ""]]
print(f"✓ {len(valid_timestamps)} records have valid timestamps")
# Print sample records to show the data format
print("\nSample weather records returned:")
for i, record in enumerate(weather_records[:3]):
print(
f" Record {i+1}: temp={record.get('temperature')}°C, "
f"humidity={record.get('humidity')}%, "
f"precipitation={record.get('precipitation')}mm, "
f"timestamp={record.get('timestamp')}"
)
print("✓ websocket_get_weather_records test passed!\n")
return weather_records
async def test_sorting_order():
"""Test that weather records are properly sorted by retrieval time."""
print("Testing weather records sorting order...")
mock_hass, mock_connection = create_mock_hass_with_weather_data()
msg = {"id": 456, "mapping_id": "1", "limit": 10}
await websocket_get_weather_records(mock_hass, mock_connection, msg)
call_args = mock_connection.send_result.call_args
weather_records = call_args[0][1]
# Check that records with valid timestamps are at the beginning
timestamps = [r.get("timestamp") for r in weather_records]
# Find the first record with None/invalid timestamp
first_invalid_index = None
for i, ts in enumerate(timestamps):
if ts is None or ts == "":
first_invalid_index = i
break
if first_invalid_index is not None:
valid_timestamps = timestamps[:first_invalid_index]
print(f"✓ First {first_invalid_index} records have valid timestamps")
print(f"✓ Records with invalid timestamps are at the end")
else:
valid_timestamps = timestamps
print(f"✓ All {len(timestamps)} records have valid timestamps")
# Parse and compare the valid timestamps to ensure they're in descending order
if len(valid_timestamps) >= 2:
parsed_timestamps = []
for ts_str in valid_timestamps:
try:
if ts_str:
parsed_ts = dateutil_parser.isoparse(ts_str)
parsed_timestamps.append(parsed_ts)
except:
pass
# Check if sorted in descending order (most recent first)
for i in range(len(parsed_timestamps) - 1):
assert (
parsed_timestamps[i] >= parsed_timestamps[i + 1]
), f"Timestamps not in descending order: {parsed_timestamps[i]} < {parsed_timestamps[i + 1]}"
print(
f"✓ {len(parsed_timestamps)} timestamps are properly sorted (most recent first)"
)
print("✓ Sorting order test passed!\n")
async def run_all_tests():
"""Run all tests to prove weather records functionality is working."""
print("=" * 60)
print("PROOF: Weather Records Functionality Working After Fix")
print("=" * 60)
print()
try:
# Test the core datetime parsing function
await test_safe_parse_datetime()
# Test the websocket function with mixed data types
weather_records = await test_websocket_get_weather_records()
# Test the sorting functionality
await test_sorting_order()
print("=" * 60)
print("✅ ALL TESTS PASSED!")
print("✅ Weather records retrieval is working correctly")
print("✅ Mixed datetime types are handled safely")
print("✅ Data is properly sorted for frontend display")
print("✅ Frontend can now display weather data in zones and mappings")
print("=" * 60)
return True
except Exception as e:
print(f"❌ TEST FAILED: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == "__main__":
success = asyncio.run(run_all_tests())
if not success:
sys.exit(1)