mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-23 09:06:29 +00:00
Apply store reload suggestion automatically on connectivity change (#6004)
* Apply store reload suggestion automatically on connectivity change * Use sys_bus not coresys.bus Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
953f7d01d7
commit
806bd9f52c
@ -3,6 +3,7 @@
|
||||
from abc import ABC, abstractmethod
|
||||
import logging
|
||||
|
||||
from ...const import BusEvent
|
||||
from ...coresys import CoreSys, CoreSysAttributes
|
||||
from ...exceptions import ResolutionFixupError
|
||||
from ..const import ContextType, IssueType, SuggestionType
|
||||
@ -66,6 +67,11 @@ class FixupBase(ABC, CoreSysAttributes):
|
||||
"""Return if a fixup can be apply as auto fix."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def bus_event(self) -> BusEvent | None:
|
||||
"""Return the BusEvent that triggers this fixup, or None if not event-based."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def all_suggestions(self) -> list[Suggestion]:
|
||||
"""List of all suggestions which when applied run this fixup."""
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
import logging
|
||||
|
||||
from ...const import BusEvent
|
||||
from ...coresys import CoreSys
|
||||
from ...exceptions import (
|
||||
ResolutionFixupError,
|
||||
@ -68,3 +69,8 @@ class FixupStoreExecuteReload(FixupBase):
|
||||
def auto(self) -> bool:
|
||||
"""Return if a fixup can be apply as auto fix."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def bus_event(self) -> BusEvent | None:
|
||||
"""Return the BusEvent that triggers this fixup, or None if not event-based."""
|
||||
return BusEvent.SUPERVISOR_CONNECTIVITY_CHANGE
|
||||
|
@ -5,6 +5,7 @@ from typing import Any
|
||||
|
||||
import attr
|
||||
|
||||
from ..bus import EventListener
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..exceptions import ResolutionError, ResolutionNotFound
|
||||
from ..homeassistant.const import WSEvent
|
||||
@ -46,6 +47,9 @@ class ResolutionManager(FileConfiguration, CoreSysAttributes):
|
||||
self._unsupported: list[UnsupportedReason] = []
|
||||
self._unhealthy: list[UnhealthyReason] = []
|
||||
|
||||
# Map suggestion UUID to event listeners (list)
|
||||
self._suggestion_listeners: dict[str, list[EventListener]] = {}
|
||||
|
||||
async def load_modules(self):
|
||||
"""Load resolution evaluation, check and fixup modules."""
|
||||
|
||||
@ -105,6 +109,19 @@ class ResolutionManager(FileConfiguration, CoreSysAttributes):
|
||||
)
|
||||
self._suggestions.append(suggestion)
|
||||
|
||||
# Register event listeners if fixups have a bus_event
|
||||
listeners: list[EventListener] = []
|
||||
for fixup in self.fixup.fixes_for_suggestion(suggestion):
|
||||
if fixup.auto and fixup.bus_event:
|
||||
|
||||
def event_callback(reference, fixup=fixup):
|
||||
return fixup(suggestion)
|
||||
|
||||
listener = self.sys_bus.register_event(fixup.bus_event, event_callback)
|
||||
listeners.append(listener)
|
||||
if listeners:
|
||||
self._suggestion_listeners[suggestion.uuid] = listeners
|
||||
|
||||
# Event on suggestion added to issue
|
||||
for issue in self.issues_for_suggestion(suggestion):
|
||||
self.sys_homeassistant.websocket.supervisor_event(
|
||||
@ -233,6 +250,11 @@ class ResolutionManager(FileConfiguration, CoreSysAttributes):
|
||||
)
|
||||
self._suggestions.remove(suggestion)
|
||||
|
||||
# Remove event listeners if present
|
||||
listeners = self._suggestion_listeners.pop(suggestion.uuid, [])
|
||||
for listener in listeners:
|
||||
self.sys_bus.remove_listener(listener)
|
||||
|
||||
# Event on suggestion removed from issues
|
||||
for issue in self.issues_for_suggestion(suggestion):
|
||||
self.sys_homeassistant.websocket.supervisor_event(
|
||||
|
@ -1,9 +1,14 @@
|
||||
"""Test evaluation base."""
|
||||
|
||||
# pylint: disable=import-error,protected-access
|
||||
import asyncio
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from supervisor.const import BusEvent
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.exceptions import ResolutionFixupError
|
||||
from supervisor.resolution.const import ContextType, IssueType, SuggestionType
|
||||
from supervisor.resolution.data import Issue, Suggestion
|
||||
from supervisor.resolution.fixups.store_execute_reload import FixupStoreExecuteReload
|
||||
@ -32,3 +37,94 @@ async def test_fixup(coresys: CoreSys, supervisor_internet):
|
||||
assert mock_repositorie.update.called
|
||||
assert len(coresys.resolution.suggestions) == 0
|
||||
assert len(coresys.resolution.issues) == 0
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("supervisor_internet")
|
||||
async def test_store_execute_reload_runs_on_connectivity_true(coresys: CoreSys):
|
||||
"""Test fixup runs when connectivity goes from false to true."""
|
||||
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
|
||||
coresys.supervisor.connectivity = False
|
||||
await asyncio.sleep(0)
|
||||
|
||||
mock_repository = AsyncMock()
|
||||
coresys.store.repositories["test_store"] = mock_repository
|
||||
coresys.resolution.add_issue(
|
||||
Issue(
|
||||
IssueType.FATAL_ERROR,
|
||||
ContextType.STORE,
|
||||
reference="test_store",
|
||||
),
|
||||
suggestions=[SuggestionType.EXECUTE_RELOAD],
|
||||
)
|
||||
|
||||
with patch.object(coresys.store, "reload") as mock_reload:
|
||||
# Fire event with connectivity True
|
||||
coresys.supervisor.connectivity = True
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
mock_repository.load.assert_called_once()
|
||||
mock_reload.assert_awaited_once_with(mock_repository)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("supervisor_internet")
|
||||
async def test_store_execute_reload_does_not_run_on_connectivity_false(
|
||||
coresys: CoreSys,
|
||||
):
|
||||
"""Test fixup does not run when connectivity goes from true to false."""
|
||||
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
|
||||
coresys.supervisor.connectivity = True
|
||||
await asyncio.sleep(0)
|
||||
|
||||
mock_repository = AsyncMock()
|
||||
coresys.store.repositories["test_store"] = mock_repository
|
||||
coresys.resolution.add_issue(
|
||||
Issue(
|
||||
IssueType.FATAL_ERROR,
|
||||
ContextType.STORE,
|
||||
reference="test_store",
|
||||
),
|
||||
suggestions=[SuggestionType.EXECUTE_RELOAD],
|
||||
)
|
||||
|
||||
# Fire event with connectivity True
|
||||
coresys.supervisor.connectivity = False
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
mock_repository.load.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("supervisor_internet")
|
||||
async def test_store_execute_reload_dismiss_suggestion_removes_listener(
|
||||
coresys: CoreSys,
|
||||
):
|
||||
"""Test fixup does not run on event if suggestion has been dismissed."""
|
||||
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
|
||||
coresys.supervisor.connectivity = True
|
||||
await asyncio.sleep(0)
|
||||
|
||||
mock_repository = AsyncMock()
|
||||
coresys.store.repositories["test_store"] = mock_repository
|
||||
coresys.resolution.add_issue(
|
||||
issue := Issue(
|
||||
IssueType.FATAL_ERROR,
|
||||
ContextType.STORE,
|
||||
reference="test_store",
|
||||
),
|
||||
suggestions=[SuggestionType.EXECUTE_RELOAD],
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
FixupStoreExecuteReload, "process_fixup", side_effect=ResolutionFixupError
|
||||
) as mock_fixup:
|
||||
# Fire event with issue there to trigger fixup
|
||||
coresys.bus.fire_event(BusEvent.SUPERVISOR_CONNECTIVITY_CHANGE, True)
|
||||
await asyncio.sleep(0.1)
|
||||
mock_fixup.assert_called_once()
|
||||
|
||||
# Remove issue and suggestion and re-fire to see listener is gone
|
||||
mock_fixup.reset_mock()
|
||||
coresys.resolution.dismiss_issue(issue)
|
||||
|
||||
coresys.bus.fire_event(BusEvent.SUPERVISOR_CONNECTIVITY_CHANGE, True)
|
||||
await asyncio.sleep(0.1)
|
||||
mock_fixup.assert_not_called()
|
||||
|
Loading…
x
Reference in New Issue
Block a user