Skip to content

Commit 0b8ddbc

Browse files
authored
Merge pull request #142 from ian-morgan99/copilot/fix-installation-review-check
Fix installation review scheduling when no updates available
2 parents ff70786 + 7cebc4b commit 0b8ddbc

File tree

2 files changed

+208
-9
lines changed

2 files changed

+208
-9
lines changed

ha_sentry/rootfs/app/sentry_service.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,15 @@ async def run_update_check(self):
495495

496496
total_updates = len(all_updates)
497497

498+
# Check if installation review should be run (independent of updates)
499+
if self._should_run_installation_review():
500+
logger.info("Installation review is due, will run independently")
501+
# Run installation review as separate task but track it for proper error handling
502+
# Store the task reference to avoid silent exception swallowing
503+
review_task = asyncio.create_task(self.run_installation_review())
504+
# Add done callback to log any exceptions
505+
review_task.add_done_callback(self._installation_review_done_callback)
506+
498507
if total_updates == 0:
499508
logger.info("No updates available")
500509
logger.debug("Reporting up-to-date status to Home Assistant")
@@ -519,15 +528,6 @@ async def run_update_check(self):
519528
if log_analysis:
520529
await self._report_log_analysis(ha_client, log_analysis)
521530

522-
# Check if installation review should be run
523-
if self._should_run_installation_review():
524-
logger.info("Installation review is due, will run after update check")
525-
# Run installation review as separate task but track it for proper error handling
526-
# Store the task reference to avoid silent exception swallowing
527-
review_task = asyncio.create_task(self.run_installation_review())
528-
# Add done callback to log any exceptions
529-
review_task.add_done_callback(self._installation_review_done_callback)
530-
531531
# Save machine-readable report (Feature 4) if enabled
532532
if self.config.save_reports:
533533
self._save_machine_readable_report(all_updates, analysis)
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
"""
2+
Test for installation review scheduling bug fix
3+
4+
This test verifies that the installation review check runs independently
5+
of whether updates are available, fixing the bug where the early return
6+
when no updates were found prevented installation review from running.
7+
"""
8+
import sys
9+
import os
10+
from unittest.mock import AsyncMock, patch
11+
from datetime import datetime, timedelta
12+
import asyncio
13+
14+
# Add the app directory to Python path
15+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'ha_sentry', 'rootfs', 'app'))
16+
17+
from config_manager import ConfigManager
18+
from sentry_service import SentryService
19+
20+
21+
def test_installation_review_runs_when_no_updates():
22+
"""Test that installation review check executes even when no updates are available"""
23+
24+
print("Testing installation review scheduling with no updates available...")
25+
26+
# Set up test environment
27+
os.environ['AI_ENABLED'] = 'false'
28+
os.environ['SUPERVISOR_TOKEN'] = 'test_token'
29+
os.environ['ENABLE_INSTALLATION_REVIEW'] = 'true'
30+
os.environ['INSTALLATION_REVIEW_SCHEDULE'] = 'weekly'
31+
32+
config = ConfigManager()
33+
service = SentryService(config)
34+
35+
# Set last review to 8 days ago (should trigger review for weekly schedule)
36+
service._last_installation_review = datetime.now() - timedelta(days=8)
37+
38+
# Track if _should_run_installation_review was called
39+
review_check_called = []
40+
original_should_run = service._should_run_installation_review
41+
42+
def mock_should_run():
43+
result = original_should_run()
44+
review_check_called.append(result)
45+
return result
46+
47+
service._should_run_installation_review = mock_should_run
48+
49+
# Mock the run_installation_review method to avoid actual execution
50+
service.run_installation_review = AsyncMock()
51+
52+
# Mock HomeAssistantClient to return no updates
53+
async def run_test():
54+
with patch('sentry_service.HomeAssistantClient') as mock_ha_client_class:
55+
mock_ha_client = AsyncMock()
56+
mock_ha_client.__aenter__ = AsyncMock(return_value=mock_ha_client)
57+
mock_ha_client.__aexit__ = AsyncMock()
58+
59+
# Return empty list for all update checks
60+
mock_ha_client.get_all_updates = AsyncMock(return_value=[])
61+
mock_ha_client.get_addon_updates = AsyncMock(return_value=[])
62+
mock_ha_client.get_hacs_updates = AsyncMock(return_value=[])
63+
64+
mock_ha_client_class.return_value = mock_ha_client
65+
66+
# Mock other dependencies
67+
service.log_monitor.check_logs = AsyncMock(return_value=None)
68+
69+
# Mock _report_no_updates to avoid errors
70+
service._report_no_updates = AsyncMock()
71+
72+
# Run the update check
73+
await service.run_update_check()
74+
75+
# Give async task time to be created (but not necessarily complete)
76+
await asyncio.sleep(0.1)
77+
78+
# Run the async test
79+
asyncio.run(run_test())
80+
81+
# Verify that _should_run_installation_review was called
82+
assert len(review_check_called) > 0, "Installation review check was not called"
83+
assert review_check_called[0] is True, "Installation review should have been scheduled to run"
84+
85+
# Verify that run_installation_review was actually invoked
86+
service.run_installation_review.assert_called_once()
87+
88+
print("✓ Installation review check was called when no updates were available")
89+
print(f" _should_run_installation_review returned: {review_check_called[0]}")
90+
print("✓ run_installation_review was invoked")
91+
92+
return True
93+
94+
95+
def test_installation_review_not_run_when_manual():
96+
"""Test that installation review respects 'manual' schedule setting"""
97+
98+
print("Testing installation review with 'manual' schedule...")
99+
100+
# Set up test environment
101+
os.environ['AI_ENABLED'] = 'false'
102+
os.environ['SUPERVISOR_TOKEN'] = 'test_token'
103+
os.environ['ENABLE_INSTALLATION_REVIEW'] = 'true'
104+
os.environ['INSTALLATION_REVIEW_SCHEDULE'] = 'manual'
105+
106+
config = ConfigManager()
107+
service = SentryService(config)
108+
109+
# Set last review to 8 days ago
110+
service._last_installation_review = datetime.now() - timedelta(days=8)
111+
112+
# Verify that _should_run_installation_review returns False for manual
113+
result = service._should_run_installation_review()
114+
assert result is False, "Installation review should not run automatically when schedule is 'manual'"
115+
116+
print("✓ Installation review correctly respects 'manual' schedule")
117+
print(f" _should_run_installation_review returned: {result}")
118+
119+
return True
120+
121+
122+
def test_installation_review_scheduling_logic():
123+
"""Test the scheduling logic for weekly and monthly schedules"""
124+
125+
print("Testing installation review scheduling logic...")
126+
127+
# Set up test environment
128+
os.environ['AI_ENABLED'] = 'false'
129+
os.environ['SUPERVISOR_TOKEN'] = 'test_token'
130+
os.environ['ENABLE_INSTALLATION_REVIEW'] = 'true'
131+
132+
# Test weekly schedule
133+
os.environ['INSTALLATION_REVIEW_SCHEDULE'] = 'weekly'
134+
config = ConfigManager()
135+
service = SentryService(config)
136+
137+
# First run - should trigger
138+
service._last_installation_review = None
139+
assert service._should_run_installation_review() is True, "First run should trigger review"
140+
print("✓ First run triggers review")
141+
142+
# Recent review - should not trigger
143+
service._last_installation_review = datetime.now() - timedelta(days=3)
144+
assert service._should_run_installation_review() is False, "Recent review should not trigger (weekly, 3 days ago)"
145+
print("✓ Recent review (3 days) does not trigger for weekly schedule")
146+
147+
# Old review - should trigger
148+
service._last_installation_review = datetime.now() - timedelta(days=8)
149+
assert service._should_run_installation_review() is True, "Old review should trigger (weekly, 8 days ago)"
150+
print("✓ Old review (8 days) triggers for weekly schedule")
151+
152+
# Test monthly schedule
153+
os.environ['INSTALLATION_REVIEW_SCHEDULE'] = 'monthly'
154+
config = ConfigManager()
155+
service = SentryService(config)
156+
157+
# Recent review (20 days) - should not trigger for monthly
158+
service._last_installation_review = datetime.now() - timedelta(days=20)
159+
assert service._should_run_installation_review() is False, "Recent review should not trigger (monthly, 20 days ago)"
160+
print("✓ Recent review (20 days) does not trigger for monthly schedule")
161+
162+
# Old review (31 days) - should trigger for monthly
163+
service._last_installation_review = datetime.now() - timedelta(days=31)
164+
assert service._should_run_installation_review() is True, "Old review should trigger (monthly, 31 days ago)"
165+
print("✓ Old review (31 days) triggers for monthly schedule")
166+
167+
return True
168+
169+
170+
if __name__ == '__main__':
171+
print("Running Installation Review Scheduling Tests...\n")
172+
173+
tests = [
174+
test_installation_review_runs_when_no_updates,
175+
test_installation_review_not_run_when_manual,
176+
test_installation_review_scheduling_logic
177+
]
178+
179+
passed = 0
180+
failed = 0
181+
182+
for test in tests:
183+
try:
184+
if test():
185+
passed += 1
186+
else:
187+
failed += 1
188+
except Exception as e:
189+
print(f"✗ Test failed with exception: {e}")
190+
import traceback
191+
traceback.print_exc()
192+
failed += 1
193+
print()
194+
195+
print(f"{'='*50}")
196+
print(f"Tests completed: {passed} passed, {failed} failed")
197+
print(f"{'='*50}")
198+
199+
sys.exit(0 if failed == 0 else 1)

0 commit comments

Comments
 (0)