-
Notifications
You must be signed in to change notification settings - Fork 653
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create issue for detached addons (#5084)
* Create issue for detached addons * Separate issues into missing and removed
- Loading branch information
Showing
5 changed files
with
273 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
"""Helpers to check for detached addons due to repo misisng.""" | ||
|
||
from ...const import CoreState | ||
from ...coresys import CoreSys | ||
from ..const import ContextType, IssueType | ||
from .base import CheckBase | ||
|
||
|
||
def setup(coresys: CoreSys) -> CheckBase: | ||
"""Check setup function.""" | ||
return CheckDetachedAddonMissing(coresys) | ||
|
||
|
||
class CheckDetachedAddonMissing(CheckBase): | ||
"""CheckDetachedAddonMissing class for check.""" | ||
|
||
async def run_check(self) -> None: | ||
"""Run check if not affected by issue.""" | ||
for addon in self.sys_addons.installed: | ||
if ( | ||
addon.is_detached | ||
and addon.repository not in self.sys_store.repositories | ||
): | ||
self.sys_resolution.create_issue( | ||
IssueType.DETACHED_ADDON_MISSING, | ||
ContextType.ADDON, | ||
reference=addon.slug, | ||
) | ||
|
||
async def approve_check(self, reference: str | None = None) -> bool: | ||
"""Approve check if it is affected by issue.""" | ||
return ( | ||
addon := self.sys_addons.get(reference, local_only=True) | ||
) and addon.is_detached | ||
|
||
@property | ||
def issue(self) -> IssueType: | ||
"""Return a IssueType enum.""" | ||
return IssueType.DETACHED_ADDON_MISSING | ||
|
||
@property | ||
def context(self) -> ContextType: | ||
"""Return a ContextType enum.""" | ||
return ContextType.ADDON | ||
|
||
@property | ||
def states(self) -> list[CoreState]: | ||
"""Return a list of valid states when this check can run.""" | ||
return [CoreState.SETUP] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
"""Helpers to check for detached addons due to removal from repo.""" | ||
|
||
from ...const import CoreState | ||
from ...coresys import CoreSys | ||
from ..const import ContextType, IssueType | ||
from .base import CheckBase | ||
|
||
|
||
def setup(coresys: CoreSys) -> CheckBase: | ||
"""Check setup function.""" | ||
return CheckDetachedAddonRemoved(coresys) | ||
|
||
|
||
class CheckDetachedAddonRemoved(CheckBase): | ||
"""CheckDetachedAddonRemoved class for check.""" | ||
|
||
async def run_check(self) -> None: | ||
"""Run check if not affected by issue.""" | ||
for addon in self.sys_addons.installed: | ||
if addon.is_detached and addon.repository in self.sys_store.repositories: | ||
self.sys_resolution.create_issue( | ||
IssueType.DETACHED_ADDON_REMOVED, | ||
ContextType.ADDON, | ||
reference=addon.slug, | ||
) | ||
|
||
async def approve_check(self, reference: str | None = None) -> bool: | ||
"""Approve check if it is affected by issue.""" | ||
return ( | ||
addon := self.sys_addons.get(reference, local_only=True) | ||
) and addon.is_detached | ||
|
||
@property | ||
def issue(self) -> IssueType: | ||
"""Return a IssueType enum.""" | ||
return IssueType.DETACHED_ADDON_REMOVED | ||
|
||
@property | ||
def context(self) -> ContextType: | ||
"""Return a ContextType enum.""" | ||
return ContextType.ADDON | ||
|
||
@property | ||
def states(self) -> list[CoreState]: | ||
"""Return a list of valid states when this check can run.""" | ||
return [CoreState.SETUP] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
tests/resolution/check/test_check_detached_addon_missing.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
"""Test check for detached addons due to repo missing.""" | ||
from unittest.mock import patch | ||
|
||
from supervisor.addons.addon import Addon | ||
from supervisor.const import CoreState | ||
from supervisor.coresys import CoreSys | ||
from supervisor.resolution.checks.detached_addon_missing import ( | ||
CheckDetachedAddonMissing, | ||
) | ||
from supervisor.resolution.const import ContextType, IssueType | ||
|
||
|
||
async def test_base(coresys: CoreSys): | ||
"""Test check basics.""" | ||
detached_addon_missing = CheckDetachedAddonMissing(coresys) | ||
assert detached_addon_missing.slug == "detached_addon_missing" | ||
assert detached_addon_missing.enabled | ||
|
||
|
||
async def test_check(coresys: CoreSys, install_addon_ssh: Addon): | ||
"""Test check for detached addons.""" | ||
detached_addon_missing = CheckDetachedAddonMissing(coresys) | ||
coresys.core.state = CoreState.SETUP | ||
|
||
await detached_addon_missing() | ||
assert len(coresys.resolution.issues) == 0 | ||
|
||
# Mock test addon was been installed from a now non-existent store | ||
install_addon_ssh.slug = "abc123_ssh" | ||
coresys.addons.data.system["abc123_ssh"] = coresys.addons.data.system["local_ssh"] | ||
coresys.addons.local["abc123_ssh"] = coresys.addons.local["local_ssh"] | ||
install_addon_ssh.data["repository"] = "abc123" | ||
|
||
await detached_addon_missing() | ||
|
||
assert len(coresys.resolution.issues) == 1 | ||
assert coresys.resolution.issues[0].type is IssueType.DETACHED_ADDON_MISSING | ||
assert coresys.resolution.issues[0].context is ContextType.ADDON | ||
assert coresys.resolution.issues[0].reference == install_addon_ssh.slug | ||
|
||
|
||
async def test_approve(coresys: CoreSys, install_addon_ssh: Addon): | ||
"""Test approve existing detached addon issues.""" | ||
detached_addon_missing = CheckDetachedAddonMissing(coresys) | ||
coresys.core.state = CoreState.SETUP | ||
|
||
assert ( | ||
await detached_addon_missing.approve_check(reference=install_addon_ssh.slug) | ||
is False | ||
) | ||
|
||
# Mock test addon was been installed from a now non-existent store | ||
install_addon_ssh.slug = "abc123_ssh" | ||
coresys.addons.data.system["abc123_ssh"] = coresys.addons.data.system["local_ssh"] | ||
coresys.addons.local["abc123_ssh"] = coresys.addons.local["local_ssh"] | ||
install_addon_ssh.data["repository"] = "abc123" | ||
|
||
assert ( | ||
await detached_addon_missing.approve_check(reference=install_addon_ssh.slug) | ||
is True | ||
) | ||
|
||
|
||
async def test_did_run(coresys: CoreSys): | ||
"""Test that the check ran as expected.""" | ||
detached_addon_missing = CheckDetachedAddonMissing(coresys) | ||
should_run = detached_addon_missing.states | ||
should_not_run = [state for state in CoreState if state not in should_run] | ||
assert should_run == [CoreState.SETUP] | ||
assert len(should_not_run) != 0 | ||
|
||
with patch.object( | ||
CheckDetachedAddonMissing, "run_check", return_value=None | ||
) as check: | ||
for state in should_run: | ||
coresys.core.state = state | ||
await detached_addon_missing() | ||
check.assert_called_once() | ||
check.reset_mock() | ||
|
||
for state in should_not_run: | ||
coresys.core.state = state | ||
await detached_addon_missing() | ||
check.assert_not_called() | ||
check.reset_mock() |
91 changes: 91 additions & 0 deletions
91
tests/resolution/check/test_check_detached_addon_removed.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
"""Test check for detached addons due to removal from repo.""" | ||
from pathlib import Path | ||
from unittest.mock import PropertyMock, patch | ||
|
||
from supervisor.addons.addon import Addon | ||
from supervisor.config import CoreConfig | ||
from supervisor.const import CoreState | ||
from supervisor.coresys import CoreSys | ||
from supervisor.resolution.checks.detached_addon_removed import ( | ||
CheckDetachedAddonRemoved, | ||
) | ||
from supervisor.resolution.const import ContextType, IssueType | ||
|
||
|
||
async def test_base(coresys: CoreSys): | ||
"""Test check basics.""" | ||
detached_addon_removed = CheckDetachedAddonRemoved(coresys) | ||
assert detached_addon_removed.slug == "detached_addon_removed" | ||
assert detached_addon_removed.enabled | ||
|
||
|
||
async def test_check( | ||
coresys: CoreSys, install_addon_ssh: Addon, tmp_supervisor_data: Path | ||
): | ||
"""Test check for detached addons.""" | ||
detached_addon_removed = CheckDetachedAddonRemoved(coresys) | ||
coresys.core.state = CoreState.SETUP | ||
|
||
await detached_addon_removed() | ||
assert len(coresys.resolution.issues) == 0 | ||
|
||
(addons_dir := tmp_supervisor_data / "addons" / "local").mkdir() | ||
with patch.object( | ||
CoreConfig, "path_addons_local", new=PropertyMock(return_value=addons_dir) | ||
): | ||
await coresys.store.load() | ||
|
||
await detached_addon_removed() | ||
|
||
assert len(coresys.resolution.issues) == 1 | ||
assert coresys.resolution.issues[0].type is IssueType.DETACHED_ADDON_REMOVED | ||
assert coresys.resolution.issues[0].context is ContextType.ADDON | ||
assert coresys.resolution.issues[0].reference == install_addon_ssh.slug | ||
|
||
|
||
async def test_approve( | ||
coresys: CoreSys, install_addon_ssh: Addon, tmp_supervisor_data: Path | ||
): | ||
"""Test approve existing detached addon issues.""" | ||
detached_addon_removed = CheckDetachedAddonRemoved(coresys) | ||
coresys.core.state = CoreState.SETUP | ||
|
||
assert ( | ||
await detached_addon_removed.approve_check(reference=install_addon_ssh.slug) | ||
is False | ||
) | ||
|
||
(addons_dir := tmp_supervisor_data / "addons" / "local").mkdir() | ||
with patch.object( | ||
CoreConfig, "path_addons_local", new=PropertyMock(return_value=addons_dir) | ||
): | ||
await coresys.store.load() | ||
|
||
assert ( | ||
await detached_addon_removed.approve_check(reference=install_addon_ssh.slug) | ||
is True | ||
) | ||
|
||
|
||
async def test_did_run(coresys: CoreSys): | ||
"""Test that the check ran as expected.""" | ||
detached_addon_removed = CheckDetachedAddonRemoved(coresys) | ||
should_run = detached_addon_removed.states | ||
should_not_run = [state for state in CoreState if state not in should_run] | ||
assert should_run == [CoreState.SETUP] | ||
assert len(should_not_run) != 0 | ||
|
||
with patch.object( | ||
CheckDetachedAddonRemoved, "run_check", return_value=None | ||
) as check: | ||
for state in should_run: | ||
coresys.core.state = state | ||
await detached_addon_removed() | ||
check.assert_called_once() | ||
check.reset_mock() | ||
|
||
for state in should_not_run: | ||
coresys.core.state = state | ||
await detached_addon_removed() | ||
check.assert_not_called() | ||
check.reset_mock() |