Files
supervisor/tests/resolution/fixup/test_app_execute_start.py
Stefan AgnerandClaude Fable 5 b9f0ff7c02 Drop ResolutionFixupError, let fixup failures bubble
Per review: the generic wrapper existed for a time when suggestions
were only applied by autofix and the one job was separating fixup
failures from real bugs for Sentry. The suggestion API is in regular
use now and the wrapper actively hurts it — well-defined errors from
the underlying operations were caught and replaced with a generic
message.

Remove the exception type entirely and let the original errors reach
the caller. The autofix loop and the bus-event fixup path treat any
HassioError as an environmental/config failure: log and continue
without Sentry capture (the raise site reports to Sentry where
warranted); everything else is still captured. Direct raises in the
data disk fixups become HassOSDataDiskError, the could-not-start check
in the app start fixup becomes AppsError. ResolutionFixupJobError now
derives from ResolutionError and JobException.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 23:31:53 +02:00

121 lines
3.9 KiB
Python

"""Test fixup app execute start."""
from unittest.mock import patch
import pytest
from supervisor.apps.app import App
from supervisor.const import AppState
from supervisor.coresys import CoreSys
from supervisor.docker.app import DockerApp
from supervisor.exceptions import AppsError, AppUnknownError, DockerError
from supervisor.resolution.const import ContextType, SuggestionType
from supervisor.resolution.data import Suggestion
from supervisor.resolution.fixups.app_execute_start import FixupAppExecuteStart
from tests.apps.test_manager import BOOT_FAIL_ISSUE
from tests.common import force_app_state
EXECUTE_START_SUGGESTION = Suggestion(
SuggestionType.EXECUTE_START, ContextType.ADDON, reference="local_ssh"
)
@pytest.mark.parametrize(
"state", [AppState.STARTED, AppState.STARTUP, AppState.STOPPED]
)
@pytest.mark.usefixtures("path_extern")
async def test_fixup(coresys: CoreSys, install_app_ssh: App, state: AppState):
"""Test fixup starts app."""
force_app_state(install_app_ssh, AppState.UNKNOWN)
app_execute_start = FixupAppExecuteStart(coresys)
assert app_execute_start.auto is False
async def mock_start(*args, **kwargs):
force_app_state(install_app_ssh, state)
coresys.resolution.add_issue(
BOOT_FAIL_ISSUE,
suggestions=[SuggestionType.EXECUTE_START],
)
with (
patch.object(DockerApp, "run") as run,
patch.object(App, "_wait_for_startup", new=mock_start),
patch.object(App, "write_options"),
):
await app_execute_start()
run.assert_called_once()
assert not coresys.resolution.issues
assert not coresys.resolution.suggestions
@pytest.mark.usefixtures("path_extern")
async def test_fixup_start_error(coresys: CoreSys, install_app_ssh: App):
"""Test fixup fails on start app failure."""
force_app_state(install_app_ssh, AppState.UNKNOWN)
app_execute_start = FixupAppExecuteStart(coresys)
coresys.resolution.add_issue(
BOOT_FAIL_ISSUE,
suggestions=[SuggestionType.EXECUTE_START],
)
with (
patch.object(DockerApp, "run", side_effect=DockerError) as run,
patch.object(App, "write_options"),
):
with pytest.raises(AppUnknownError):
await app_execute_start()
run.assert_called_once()
assert BOOT_FAIL_ISSUE in coresys.resolution.issues
assert EXECUTE_START_SUGGESTION in coresys.resolution.suggestions
@pytest.mark.parametrize("state", [AppState.ERROR, AppState.UNKNOWN])
@pytest.mark.usefixtures("path_extern")
async def test_fixup_wait_start_failure(
coresys: CoreSys, install_app_ssh: App, state: AppState
):
"""Test fixup fails if app does not complete startup."""
force_app_state(install_app_ssh, AppState.UNKNOWN)
app_execute_start = FixupAppExecuteStart(coresys)
async def mock_start(*args, **kwargs):
force_app_state(install_app_ssh, state)
coresys.resolution.add_issue(
BOOT_FAIL_ISSUE,
suggestions=[SuggestionType.EXECUTE_START],
)
with (
patch.object(DockerApp, "run") as run,
patch.object(App, "_wait_for_startup", new=mock_start),
patch.object(App, "write_options"),
):
with pytest.raises(AppsError):
await app_execute_start()
run.assert_called_once()
assert BOOT_FAIL_ISSUE in coresys.resolution.issues
assert EXECUTE_START_SUGGESTION in coresys.resolution.suggestions
async def test_fixup_no_app(coresys: CoreSys):
"""Test fixup dismisses if app is missing."""
app_execute_start = FixupAppExecuteStart(coresys)
coresys.resolution.add_issue(
BOOT_FAIL_ISSUE,
suggestions=[SuggestionType.EXECUTE_START],
)
with (
patch.object(DockerApp, "run") as run,
patch.object(App, "write_options"),
):
await app_execute_start()
run.assert_not_called()
assert not coresys.resolution.issues
assert not coresys.resolution.suggestions