-
-
Notifications
You must be signed in to change notification settings - Fork 71
Expand file tree
/
Copy pathtest_comprehensive.py
More file actions
288 lines (235 loc) · 10.5 KB
/
test_comprehensive.py
File metadata and controls
288 lines (235 loc) · 10.5 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
276
277
278
279
280
281
282
283
284
285
286
287
288
#!/usr/bin/env python3
"""
Final comprehensive test to verify the latitude/elevation fix works completely.
This tests the exact scenario described in the problem statement.
"""
import asyncio
import os
import sys
from unittest.mock import AsyncMock, MagicMock
# Add the custom components to the path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "custom_components"))
async def test_original_error_scenario():
"""Test the exact scenario from the problem statement."""
print("=== Testing Original Error Scenario ===")
print(
"Before fix: Should have gotten AttributeError: 'SmartIrrigationCoordinator' object has no attribute '_latitude'"
)
print("After fix: Should work without errors")
try:
from smart_irrigation import SmartIrrigationCoordinator
# Create realistic mocks
mock_hass = MagicMock()
mock_hass.config.as_dict.return_value = {
"latitude": 37.7749, # San Francisco
"longitude": -122.4194,
"elevation": 16,
}
mock_hass.config.units.is_metric = True
mock_hass.data = {"smart_irrigation": {"use_weather_service": False}}
mock_hass.loop = asyncio.get_event_loop()
mock_hass.async_create_task = asyncio.create_task
mock_hass.config.language = "en"
mock_hass.bus.fire = MagicMock()
mock_entry = MagicMock()
mock_entry.unique_id = "test_smart_irrigation"
mock_entry.data = {}
mock_entry.options = {}
mock_store = MagicMock()
mock_store.get_config.return_value = {
"autocalcenabled": False,
"autoupdateenabled": False,
"autoclearenabled": False,
"starteventfiredtoday": False,
}
# Create test zone data
test_zone = {
"id": 1,
"name": "Garden Zone",
"size": 25.0, # m²
"throughput": 8.0, # l/m
"state": "automatic",
"mapping": 1,
"module": 1,
"multiplier": 1.0,
"maximum_bucket": 50.0,
"drainage_rate": 0.1,
}
mock_store.async_get_zones = AsyncMock(return_value=[test_zone])
mock_store.get_zone.return_value = test_zone
test_module = {
"id": 1,
"name": "PyETO",
"description": "Penman-Monteith ET calculation",
"config": {"forecast_days": 0},
}
mock_store.get_module.return_value = test_module
# Create coordinator - this is where the original error occurred
print("Creating SmartIrrigationCoordinator...")
coordinator = SmartIrrigationCoordinator(
mock_hass, None, mock_entry, mock_store
)
print("✓ Coordinator created successfully")
# Verify attributes are properly initialized
print(f"Coordinator._latitude: {coordinator._latitude}")
print(f"Coordinator._elevation: {coordinator._elevation}")
assert hasattr(coordinator, "_latitude"), "Missing _latitude attribute"
assert hasattr(coordinator, "_elevation"), "Missing _elevation attribute"
assert coordinator._latitude == 37.7749
assert coordinator._elevation == 16
print("✓ Latitude and elevation properly initialized")
# Test the specific method that was failing
print("Testing _generate_monthly_climate_data (method that was failing)...")
monthly_data = coordinator._generate_monthly_climate_data()
assert len(monthly_data) == 12
print("✓ Monthly climate data generation successful")
# Test seasonal variation to ensure calculation is working correctly
winter_month = monthly_data[0] # January
summer_month = monthly_data[6] # July
print(f"January avg temp: {winter_month['avg_temp']:.1f}°C")
print(f"July avg temp: {summer_month['avg_temp']:.1f}°C")
assert summer_month["avg_temp"] > winter_month["avg_temp"]
print("✓ Seasonal variation correct")
# Mock PyETO module for calendar testing
mock_pyeto = MagicMock()
mock_pyeto.name = "PyETO"
mock_pyeto.calculate_et_for_day.return_value = -3.2 # Realistic daily deficit
coordinator.getModuleInstanceByID = AsyncMock(return_value=mock_pyeto)
# Test full calendar generation
print("Testing full watering calendar generation...")
calendar_data = await coordinator.async_generate_watering_calendar()
assert len(calendar_data) > 0, "No calendar data generated"
assert 1 in calendar_data, "Zone 1 missing from calendar"
zone_calendar = calendar_data[1]
assert zone_calendar["zone_name"] == "Garden Zone"
assert len(zone_calendar["monthly_estimates"]) == 12
print("✓ Full calendar generation successful")
# Test with service call
print("Testing calendar generation service call...")
mock_call = MagicMock()
mock_call.data = {"zone_id": 1}
await coordinator.handle_generate_watering_calendar(mock_call)
# Verify event was fired
mock_hass.bus.fire.assert_called()
event_calls = mock_hass.bus.fire.call_args_list
success_events = [
call for call in event_calls if "watering_calendar_generated" in str(call)
]
assert len(success_events) > 0, "Success event not fired"
print("✓ Service call completed and success event fired")
return True
except Exception as e:
print(f"❌ Test failed: {type(e).__name__}: {e}")
import traceback
traceback.print_exc()
return False
async def test_without_ha_config():
"""Test that defaults work when HA config has no latitude/elevation."""
print("\n=== Testing Default Values (No HA Config) ===")
try:
from smart_irrigation import SmartIrrigationCoordinator
# Mock HA with empty config (common scenario)
mock_hass = MagicMock()
mock_hass.config.as_dict.return_value = {} # No coordinates configured
mock_hass.config.units.is_metric = True
mock_hass.data = {"smart_irrigation": {"use_weather_service": False}}
mock_hass.loop = asyncio.get_event_loop()
mock_hass.async_create_task = asyncio.create_task
mock_entry = MagicMock()
mock_entry.unique_id = "test_no_coords"
mock_entry.data = {}
mock_entry.options = {}
mock_store = MagicMock()
mock_store.get_config.return_value = {
"autocalcenabled": False,
"autoupdateenabled": False,
"autoclearenabled": False,
"starteventfiredtoday": False,
}
print("Creating coordinator with no coordinates configured...")
coordinator = SmartIrrigationCoordinator(
mock_hass, None, mock_entry, mock_store
)
# Should use defaults
assert (
coordinator._latitude == 45.0
), f"Expected 45.0, got {coordinator._latitude}"
assert coordinator._elevation == 0, f"Expected 0, got {coordinator._elevation}"
print("✓ Default values applied correctly")
# Should still work for calendar generation
monthly_data = coordinator._generate_monthly_climate_data()
assert len(monthly_data) == 12
print("✓ Calendar generation works with defaults")
return True
except Exception as e:
print(f"❌ Default test failed: {type(e).__name__}: {e}")
return False
async def test_config_from_entry():
"""Test getting latitude/elevation from config entry instead of HA config."""
print("\n=== Testing Config from Entry Data ===")
try:
from homeassistant.const import CONF_ELEVATION, CONF_LATITUDE
from smart_irrigation import SmartIrrigationCoordinator
# Mock HA with empty config
mock_hass = MagicMock()
mock_hass.config.as_dict.return_value = {} # No HA config
mock_hass.config.units.is_metric = True
mock_hass.data = {"smart_irrigation": {"use_weather_service": False}}
mock_hass.loop = asyncio.get_event_loop()
mock_hass.async_create_task = asyncio.create_task
# But provide coordinates in entry data
mock_entry = MagicMock()
mock_entry.unique_id = "test_entry_coords"
mock_entry.data = {CONF_LATITUDE: 51.5074, CONF_ELEVATION: 35} # London
mock_entry.options = {}
mock_store = MagicMock()
mock_store.get_config.return_value = {
"autocalcenabled": False,
"autoupdateenabled": False,
"autoclearenabled": False,
"starteventfiredtoday": False,
}
print("Creating coordinator with coordinates in entry data...")
coordinator = SmartIrrigationCoordinator(
mock_hass, None, mock_entry, mock_store
)
# Should use entry data
assert (
coordinator._latitude == 51.5074
), f"Expected 51.5074, got {coordinator._latitude}"
assert (
coordinator._elevation == 35
), f"Expected 35, got {coordinator._elevation}"
print("✓ Entry data values used correctly")
# Test calendar generation
monthly_data = coordinator._generate_monthly_climate_data()
assert len(monthly_data) == 12
print("✓ Calendar works with entry data coordinates")
return True
except Exception as e:
print(f"❌ Entry data test failed: {type(e).__name__}: {e}")
return False
if __name__ == "__main__":
async def run_comprehensive_tests():
print("🔬 Comprehensive test of latitude/elevation fix")
print("=" * 60)
test1 = await test_original_error_scenario()
test2 = await test_without_ha_config()
test3 = await test_config_from_entry()
print("\n" + "=" * 60)
if test1 and test2 and test3:
print("🎉 SUCCESS: All comprehensive tests passed!")
print(
"The AttributeError: 'SmartIrrigationCoordinator' object has no attribute '_latitude' has been fixed!"
)
print("\n✅ Calendar generation now works in all scenarios:")
print(" - With latitude/elevation in Home Assistant config")
print(" - With latitude/elevation in config entry data")
print(" - With no configuration (using sensible defaults)")
print(" - Proper warning messages when using defaults")
return True
else:
print("❌ FAILURE: Some tests failed")
return False
success = asyncio.run(run_comprehensive_tests())
sys.exit(0 if success else 1)