mirror of
https://github.com/home-assistant/supervisor.git
synced 2026-08-20 13:18:21 +01:00
Applying a suggestion whose fixup failed reported success: the fixup base swallowed ResolutionFixupError, the API returned OK, and the repair flow in Home Assistant completed and removed the repair from the UI while the issue persisted in Supervisor. Let the error propagate from the fixup instead. The autofix loop already handles and logs per-fixup errors, bus-event triggered fixups now get the same treatment in the event callback, and a user-applied suggestion surfaces the failure as an API error so clients can show that the fix did not apply. ResolutionFixupError becomes an APIError with a translatable error key so it is reported as a client-visible error instead of an unexpected one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
550 lines
19 KiB
Python
550 lines
19 KiB
Python
"""Test Resolution API."""
|
|
|
|
import asyncio
|
|
from http import HTTPStatus
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
from aiohttp.test_utils import TestClient
|
|
import pytest
|
|
|
|
from supervisor.const import (
|
|
ATTR_ISSUES,
|
|
ATTR_SUGGESTIONS,
|
|
ATTR_UNHEALTHY,
|
|
ATTR_UNSUPPORTED,
|
|
CoreState,
|
|
FeatureFlag,
|
|
)
|
|
from supervisor.coresys import CoreSys
|
|
from supervisor.exceptions import ResolutionError, ResolutionFixupError
|
|
from supervisor.homeassistant.const import WSType
|
|
from supervisor.resolution.const import (
|
|
ContextType,
|
|
IssueType,
|
|
SuggestionType,
|
|
UnhealthyReason,
|
|
UnsupportedReason,
|
|
)
|
|
from supervisor.resolution.data import Issue, Suggestion
|
|
|
|
|
|
async def test_api_resolution_base(
|
|
coresys: CoreSys, api_client_with_prefix: tuple[TestClient, str]
|
|
):
|
|
"""Test resolution manager api."""
|
|
api_client, prefix = api_client_with_prefix
|
|
coresys.resolution.add_unsupported_reason(UnsupportedReason.OS)
|
|
coresys.resolution.add_suggestion(
|
|
Suggestion(SuggestionType.CLEAR_FULL_BACKUP, ContextType.SYSTEM)
|
|
)
|
|
coresys.resolution.create_issue(IssueType.FREE_SPACE, ContextType.SYSTEM)
|
|
|
|
resp = await api_client.get(f"{prefix}/resolution/info")
|
|
result = await resp.json()
|
|
assert UnsupportedReason.OS in result["data"][ATTR_UNSUPPORTED]
|
|
assert (
|
|
result["data"][ATTR_SUGGESTIONS][-1]["type"] == SuggestionType.CLEAR_FULL_BACKUP
|
|
)
|
|
assert result["data"][ATTR_ISSUES][-1]["type"] == IssueType.FREE_SPACE
|
|
|
|
|
|
async def test_api_resolution_dismiss_suggestion(
|
|
coresys: CoreSys, api_client_with_prefix: tuple[TestClient, str]
|
|
):
|
|
"""Test resolution manager dismiss suggestion api."""
|
|
api_client, prefix = api_client_with_prefix
|
|
coresys.resolution.add_suggestion(
|
|
clear_backup := Suggestion(SuggestionType.CLEAR_FULL_BACKUP, ContextType.SYSTEM)
|
|
)
|
|
|
|
assert coresys.resolution.suggestions[-1].type == SuggestionType.CLEAR_FULL_BACKUP
|
|
await api_client.delete(f"{prefix}/resolution/suggestion/{clear_backup.uuid}")
|
|
assert clear_backup not in coresys.resolution.suggestions
|
|
|
|
|
|
async def test_api_resolution_apply_suggestion(
|
|
coresys: CoreSys, api_client_with_prefix: tuple[TestClient, str]
|
|
):
|
|
"""Test resolution manager suggestion apply api."""
|
|
api_client, prefix = api_client_with_prefix
|
|
coresys.resolution.add_suggestion(
|
|
clear_backup := Suggestion(SuggestionType.CLEAR_FULL_BACKUP, ContextType.SYSTEM)
|
|
)
|
|
coresys.resolution.add_suggestion(
|
|
create_backup := Suggestion(
|
|
SuggestionType.CREATE_FULL_BACKUP, ContextType.SYSTEM
|
|
)
|
|
)
|
|
|
|
mock_backups = AsyncMock()
|
|
mock_health = AsyncMock()
|
|
coresys.backups.do_backup_full = mock_backups
|
|
coresys.resolution.healthcheck = mock_health
|
|
|
|
await api_client.post(f"{prefix}/resolution/suggestion/{clear_backup.uuid}")
|
|
await api_client.post(f"{prefix}/resolution/suggestion/{create_backup.uuid}")
|
|
|
|
assert clear_backup not in coresys.resolution.suggestions
|
|
assert create_backup not in coresys.resolution.suggestions
|
|
|
|
assert mock_backups.called
|
|
assert mock_health.called
|
|
|
|
with pytest.raises(ResolutionError):
|
|
await coresys.resolution.apply_suggestion(clear_backup)
|
|
|
|
|
|
async def test_api_resolution_apply_suggestion_fixup_error(
|
|
coresys: CoreSys, api_client_with_prefix: tuple[TestClient, str]
|
|
):
|
|
"""Test a failing fixup surfaces as API error and keeps issue and suggestion."""
|
|
api_client, prefix = api_client_with_prefix
|
|
coresys.resolution.add_issue(
|
|
issue := Issue(IssueType.MOUNT_FAILED, ContextType.MOUNT, reference="test"),
|
|
suggestions=[SuggestionType.EXECUTE_RELOAD],
|
|
)
|
|
suggestion = coresys.resolution.suggestions[-1]
|
|
|
|
with patch(
|
|
"supervisor.resolution.fixups.mount_execute_reload.FixupMountExecuteReload.process_fixup",
|
|
side_effect=ResolutionFixupError("Test fixup failure"),
|
|
):
|
|
resp = await api_client.post(
|
|
f"{prefix}/resolution/suggestion/{suggestion.uuid}"
|
|
)
|
|
|
|
assert resp.status == 400
|
|
body = await resp.json()
|
|
assert body["message"] == "Test fixup failure"
|
|
|
|
assert issue in coresys.resolution.issues
|
|
assert suggestion in coresys.resolution.suggestions
|
|
|
|
|
|
async def test_api_resolution_dismiss_issue(
|
|
coresys: CoreSys, api_client_with_prefix: tuple[TestClient, str]
|
|
):
|
|
"""Test resolution manager issue apply api."""
|
|
api_client, prefix = api_client_with_prefix
|
|
coresys.resolution.add_issue(
|
|
updated_failed := Issue(IssueType.UPDATE_FAILED, ContextType.SYSTEM)
|
|
)
|
|
|
|
assert coresys.resolution.issues[-1].type == IssueType.UPDATE_FAILED
|
|
await api_client.delete(f"{prefix}/resolution/issue/{updated_failed.uuid}")
|
|
assert updated_failed not in coresys.resolution.issues
|
|
|
|
|
|
async def test_api_resolution_unhealthy(
|
|
coresys: CoreSys, api_client_with_prefix: tuple[TestClient, str]
|
|
):
|
|
"""Test resolution manager api."""
|
|
api_client, prefix = api_client_with_prefix
|
|
coresys.resolution.add_unhealthy_reason(UnhealthyReason.DOCKER)
|
|
|
|
resp = await api_client.get(f"{prefix}/resolution/info")
|
|
result = await resp.json()
|
|
assert result["data"][ATTR_UNHEALTHY][-1] == UnhealthyReason.DOCKER
|
|
|
|
|
|
async def test_api_resolution_check_options(
|
|
coresys: CoreSys, api_client_with_prefix: tuple[TestClient, str]
|
|
):
|
|
"""Test client API with checks options."""
|
|
api_client, prefix = api_client_with_prefix
|
|
free_space = coresys.resolution.check.get("free_space")
|
|
|
|
assert free_space.enabled
|
|
await api_client.post(
|
|
f"{prefix}/resolution/check/{free_space.slug}/options", json={"enabled": False}
|
|
)
|
|
assert not free_space.enabled
|
|
|
|
await api_client.post(
|
|
f"{prefix}/resolution/check/{free_space.slug}/options", json={"enabled": True}
|
|
)
|
|
assert free_space.enabled
|
|
|
|
|
|
async def test_api_resolution_check_run(
|
|
coresys: CoreSys, api_client_with_prefix: tuple[TestClient, str]
|
|
):
|
|
"""Test client API with run check."""
|
|
api_client, prefix = api_client_with_prefix
|
|
await coresys.core.set_state(CoreState.RUNNING)
|
|
free_space = coresys.resolution.check.get("free_space")
|
|
|
|
free_space.run_check = AsyncMock()
|
|
|
|
await api_client.post(f"{prefix}/resolution/check/{free_space.slug}/run")
|
|
|
|
assert free_space.run_check.called
|
|
|
|
|
|
async def test_api_resolution_suggestions_for_issue(
|
|
coresys: CoreSys, api_client_with_prefix: tuple[TestClient, str]
|
|
):
|
|
"""Test getting suggestions that fix an issue."""
|
|
api_client, prefix = api_client_with_prefix
|
|
coresys.resolution.add_issue(
|
|
corrupt_repo := Issue(IssueType.CORRUPT_REPOSITORY, ContextType.STORE, "repo_1")
|
|
)
|
|
|
|
resp = await api_client.get(
|
|
f"{prefix}/resolution/issue/{corrupt_repo.uuid}/suggestions"
|
|
)
|
|
result = await resp.json()
|
|
|
|
assert result["data"]["suggestions"] == []
|
|
|
|
coresys.resolution.add_suggestion(
|
|
execute_reset := Suggestion(
|
|
SuggestionType.EXECUTE_RESET, ContextType.STORE, "repo_1"
|
|
)
|
|
)
|
|
coresys.resolution.add_suggestion(
|
|
execute_remove := Suggestion(
|
|
SuggestionType.EXECUTE_REMOVE, ContextType.STORE, "repo_1"
|
|
)
|
|
)
|
|
|
|
resp = await api_client.get(
|
|
f"{prefix}/resolution/issue/{corrupt_repo.uuid}/suggestions"
|
|
)
|
|
result = await resp.json()
|
|
|
|
suggestion = [
|
|
su for su in result["data"]["suggestions"] if su["uuid"] == execute_reset.uuid
|
|
]
|
|
assert len(suggestion) == 1
|
|
assert suggestion[0]["auto"] is True
|
|
|
|
suggestion = [
|
|
su for su in result["data"]["suggestions"] if su["uuid"] == execute_remove.uuid
|
|
]
|
|
assert len(suggestion) == 1
|
|
assert suggestion[0]["auto"] is False
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("method", "url"),
|
|
[("delete", "/resolution/issue/bad"), ("get", "/resolution/issue/bad/suggestions")],
|
|
)
|
|
async def test_issue_not_found(
|
|
api_client_with_prefix: tuple[TestClient, str], method: str, url: str
|
|
):
|
|
"""Test issue not found error."""
|
|
api_client, prefix = api_client_with_prefix
|
|
resp = await api_client.request(method, f"{prefix}{url}")
|
|
assert resp.status == 404
|
|
body = await resp.json()
|
|
assert body["message"] == "Issue bad does not exist"
|
|
assert body["error_key"] == "resolution_issue_not_found_error"
|
|
assert body["extra_fields"] == {"uuid": "bad"}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("method", "url"),
|
|
[("delete", "/resolution/suggestion/bad"), ("post", "/resolution/suggestion/bad")],
|
|
)
|
|
async def test_suggestion_not_found(
|
|
api_client_with_prefix: tuple[TestClient, str], method: str, url: str
|
|
):
|
|
"""Test suggestion not found error."""
|
|
api_client, prefix = api_client_with_prefix
|
|
resp = await api_client.request(method, f"{prefix}{url}")
|
|
assert resp.status == 404
|
|
body = await resp.json()
|
|
assert body["message"] == "Suggestion bad does not exist"
|
|
assert body["error_key"] == "resolution_suggestion_not_found_error"
|
|
assert body["extra_fields"] == {"uuid": "bad"}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("method", "url"),
|
|
[("post", "/resolution/check/bad/options"), ("post", "/resolution/check/bad/run")],
|
|
)
|
|
async def test_check_not_found(
|
|
api_client_with_prefix: tuple[TestClient, str], method: str, url: str
|
|
):
|
|
"""Test check not found error."""
|
|
api_client, prefix = api_client_with_prefix
|
|
resp = await api_client.request(method, f"{prefix}{url}")
|
|
assert resp.status == HTTPStatus.NOT_FOUND
|
|
body = await resp.json()
|
|
assert body["message"] == "Check 'bad' does not exist"
|
|
assert body["error_key"] == "resolution_check_not_found_error"
|
|
assert body["extra_fields"] == {"check": "bad"}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("issue_type", "legacy_issue_type"),
|
|
[
|
|
(IssueType.DEPRECATED_APP, "deprecated_addon"),
|
|
(IssueType.DEPRECATED_ARCH_APP, "deprecated_arch_addon"),
|
|
(IssueType.DETACHED_APP_MISSING, "detached_addon_missing"),
|
|
(IssueType.DETACHED_APP_REMOVED, "detached_addon_removed"),
|
|
],
|
|
)
|
|
async def test_api_resolution_info_v1_uses_legacy_names(
|
|
coresys: CoreSys, api_client: TestClient, issue_type: str, legacy_issue_type: str
|
|
):
|
|
"""Test v1 resolution info uses legacy issue type and check slug names."""
|
|
coresys.resolution.add_issue(Issue(issue_type, ContextType.ADDON, reference="test"))
|
|
|
|
resp = await api_client.get("/resolution/info")
|
|
result = await resp.json()
|
|
|
|
# V1 should return legacy issue type name
|
|
issue_types = [issue["type"] for issue in result["data"][ATTR_ISSUES]]
|
|
assert legacy_issue_type in issue_types
|
|
assert issue_type not in issue_types
|
|
|
|
# V1 should return legacy check slugs
|
|
check_slugs = [check["slug"] for check in result["data"]["checks"]]
|
|
assert "addon_pwned" in check_slugs
|
|
assert "deprecated_addon" in check_slugs
|
|
assert "deprecated_arch_addon" in check_slugs
|
|
assert "detached_addon_missing" in check_slugs
|
|
assert "detached_addon_removed" in check_slugs
|
|
# Should NOT have new names
|
|
assert "app_pwned" not in check_slugs
|
|
assert "deprecated_app" not in check_slugs
|
|
assert "deprecated_arch_app" not in check_slugs
|
|
assert "detached_app_missing" not in check_slugs
|
|
assert "detached_app_removed" not in check_slugs
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"issue_type",
|
|
[
|
|
IssueType.DEPRECATED_APP,
|
|
IssueType.DEPRECATED_ARCH_APP,
|
|
IssueType.DETACHED_APP_MISSING,
|
|
IssueType.DETACHED_APP_REMOVED,
|
|
],
|
|
)
|
|
async def test_api_resolution_info_v2_uses_new_names(
|
|
coresys: CoreSys, api_client_v2: TestClient, issue_type: str
|
|
):
|
|
"""Test v2 resolution info uses new issue type and check slug names."""
|
|
coresys.resolution.add_issue(Issue(issue_type, ContextType.ADDON, reference="test"))
|
|
|
|
resp = await api_client_v2.get("/v2/resolution/info")
|
|
result = await resp.json()
|
|
|
|
# V2 should return new issue type name
|
|
issue_types = [issue["type"] for issue in result["data"][ATTR_ISSUES]]
|
|
assert issue_type in issue_types
|
|
|
|
# V2 should return new check slugs
|
|
check_slugs = [check["slug"] for check in result["data"]["checks"]]
|
|
assert "app_pwned" in check_slugs
|
|
assert "deprecated_app" in check_slugs
|
|
assert "deprecated_arch_app" in check_slugs
|
|
assert "detached_app_missing" in check_slugs
|
|
assert "detached_app_removed" in check_slugs
|
|
# Should NOT have legacy names
|
|
assert "addon_pwned" not in check_slugs
|
|
assert "deprecated_addon" not in check_slugs
|
|
assert "deprecated_arch_addon" not in check_slugs
|
|
assert "detached_addon_missing" not in check_slugs
|
|
assert "detached_addon_removed" not in check_slugs
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("issue_type", "legacy_issue_type"),
|
|
[
|
|
(IssueType.DEPRECATED_APP, "deprecated_addon"),
|
|
(IssueType.DEPRECATED_ARCH_APP, "deprecated_arch_addon"),
|
|
(IssueType.DETACHED_APP_MISSING, "detached_addon_missing"),
|
|
(IssueType.DETACHED_APP_REMOVED, "detached_addon_removed"),
|
|
],
|
|
)
|
|
async def test_ws_resolution_issue_events_legacy_compat(
|
|
coresys: CoreSys,
|
|
ha_ws_client: AsyncMock,
|
|
issue_type: str,
|
|
legacy_issue_type: str,
|
|
):
|
|
"""Test WS issue events use legacy names when SUPERVISOR_WEBSOCKET_V2_API is disabled."""
|
|
# Default: SUPERVISOR_WEBSOCKET_V2_API is disabled
|
|
coresys.resolution.add_issue(Issue(issue_type, ContextType.ADDON, reference="test"))
|
|
await asyncio.sleep(0)
|
|
|
|
ws_events = [
|
|
call.args[0]
|
|
for call in ha_ws_client.async_send_command.call_args_list
|
|
if call.args[0].get("type") == WSType.SUPERVISOR_EVENT
|
|
and call.args[0].get("data", {}).get("event") == "issue_changed"
|
|
]
|
|
assert len(ws_events) == 1
|
|
assert ws_events[0]["data"]["data"]["type"] == legacy_issue_type
|
|
|
|
# After dismissing, the issue_removed event should also use legacy name
|
|
ha_ws_client.async_send_command.reset_mock()
|
|
issue = coresys.resolution.issues[0]
|
|
coresys.resolution.dismiss_issue(issue)
|
|
await asyncio.sleep(0)
|
|
|
|
ws_events = [
|
|
call.args[0]
|
|
for call in ha_ws_client.async_send_command.call_args_list
|
|
if call.args[0].get("type") == WSType.SUPERVISOR_EVENT
|
|
and call.args[0].get("data", {}).get("event") == "issue_removed"
|
|
]
|
|
assert len(ws_events) == 1
|
|
assert ws_events[0]["data"]["data"]["type"] == legacy_issue_type
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"issue_type",
|
|
[
|
|
IssueType.DEPRECATED_APP,
|
|
IssueType.DEPRECATED_ARCH_APP,
|
|
IssueType.DETACHED_APP_MISSING,
|
|
IssueType.DETACHED_APP_REMOVED,
|
|
],
|
|
)
|
|
async def test_ws_resolution_issue_events_v2(
|
|
coresys: CoreSys, ha_ws_client: AsyncMock, issue_type: str
|
|
):
|
|
"""Test WS issue events use new names when SUPERVISOR_WEBSOCKET_V2_API is enabled."""
|
|
coresys.config.set_feature_flag(FeatureFlag.SUPERVISOR_WEBSOCKET_V2_API, True)
|
|
|
|
coresys.resolution.add_issue(Issue(issue_type, ContextType.ADDON, reference="test"))
|
|
await asyncio.sleep(0)
|
|
|
|
ws_events = [
|
|
call.args[0]
|
|
for call in ha_ws_client.async_send_command.call_args_list
|
|
if call.args[0].get("type") == WSType.SUPERVISOR_EVENT
|
|
and call.args[0].get("data", {}).get("event") == "issue_changed"
|
|
]
|
|
assert len(ws_events) == 1
|
|
assert ws_events[0]["data"]["data"]["type"] == issue_type
|
|
|
|
# After dismissing, the issue_removed event should also use new name
|
|
ha_ws_client.async_send_command.reset_mock()
|
|
issue = coresys.resolution.issues[0]
|
|
coresys.resolution.dismiss_issue(issue)
|
|
await asyncio.sleep(0)
|
|
|
|
ws_events = [
|
|
call.args[0]
|
|
for call in ha_ws_client.async_send_command.call_args_list
|
|
if call.args[0].get("type") == WSType.SUPERVISOR_EVENT
|
|
and call.args[0].get("data", {}).get("event") == "issue_removed"
|
|
]
|
|
assert len(ws_events) == 1
|
|
assert ws_events[0]["data"]["data"]["type"] == issue_type
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("check_slug", "legacy_slug"),
|
|
[
|
|
("app_pwned", "addon_pwned"),
|
|
("deprecated_app", "deprecated_addon"),
|
|
("deprecated_arch_app", "deprecated_arch_addon"),
|
|
("detached_app_missing", "detached_addon_missing"),
|
|
("detached_app_removed", "detached_addon_removed"),
|
|
],
|
|
)
|
|
async def test_api_resolution_check_run_v1_accepts_legacy_names(
|
|
api_client: TestClient,
|
|
check_slug: str,
|
|
legacy_slug: str,
|
|
):
|
|
"""Test v1 check run endpoint translates legacy check slugs to new ones."""
|
|
# V1 should accept legacy slug and translate it to new slug
|
|
resp = await api_client.post(f"/resolution/check/{legacy_slug}/run")
|
|
assert resp.status == 200
|
|
|
|
# V1 should also accept new slug directly
|
|
resp = await api_client.post(f"/resolution/check/{check_slug}/run")
|
|
assert resp.status == 200
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("check_slug", "legacy_slug"),
|
|
[
|
|
("app_pwned", "addon_pwned"),
|
|
("deprecated_app", "deprecated_addon"),
|
|
("deprecated_arch_app", "deprecated_arch_addon"),
|
|
("detached_app_missing", "detached_addon_missing"),
|
|
("detached_app_removed", "detached_addon_removed"),
|
|
],
|
|
)
|
|
async def test_api_resolution_check_run_v2_accepts_new_names(
|
|
api_client_v2: TestClient, check_slug: str, legacy_slug: str
|
|
):
|
|
"""Test v2 check run endpoint accepts new check slugs and rejects legacy ones."""
|
|
# V2 should accept new slug
|
|
resp = await api_client_v2.post(f"/v2/resolution/check/{check_slug}/run")
|
|
assert resp.status == 200
|
|
|
|
# V2 should NOT accept legacy slug
|
|
resp = await api_client_v2.post(f"/v2/resolution/check/{legacy_slug}/run")
|
|
assert resp.status == HTTPStatus.NOT_FOUND
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("check_slug", "legacy_slug"),
|
|
[
|
|
("app_pwned", "addon_pwned"),
|
|
("deprecated_app", "deprecated_addon"),
|
|
("deprecated_arch_app", "deprecated_arch_addon"),
|
|
("detached_app_missing", "detached_addon_missing"),
|
|
("detached_app_removed", "detached_addon_removed"),
|
|
],
|
|
)
|
|
async def test_api_resolution_check_options_v1_accepts_legacy_names(
|
|
api_client: TestClient,
|
|
check_slug: str,
|
|
legacy_slug: str,
|
|
):
|
|
"""Test v1 check options endpoint translates legacy check slugs to new ones."""
|
|
# V1 should accept legacy slug and translate it to new slug
|
|
resp = await api_client.post(
|
|
f"/resolution/check/{legacy_slug}/options", json={"enabled": False}
|
|
)
|
|
assert resp.status == 200
|
|
|
|
# V1 should also accept new slug directly
|
|
resp = await api_client.post(
|
|
f"/resolution/check/{check_slug}/options", json={"enabled": True}
|
|
)
|
|
assert resp.status == 200
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("check_slug", "legacy_slug"),
|
|
[
|
|
("app_pwned", "addon_pwned"),
|
|
("deprecated_app", "deprecated_addon"),
|
|
("deprecated_arch_app", "deprecated_arch_addon"),
|
|
("detached_app_missing", "detached_addon_missing"),
|
|
("detached_app_removed", "detached_addon_removed"),
|
|
],
|
|
)
|
|
async def test_api_resolution_check_options_v2_accepts_new_names(
|
|
api_client_v2: TestClient, check_slug: str, legacy_slug: str
|
|
):
|
|
"""Test v2 check options endpoint accepts new check slugs and rejects legacy ones."""
|
|
# V2 should accept new slug
|
|
resp = await api_client_v2.post(
|
|
f"/v2/resolution/check/{check_slug}/options", json={"enabled": False}
|
|
)
|
|
assert resp.status == 200
|
|
|
|
resp = await api_client_v2.post(
|
|
f"/v2/resolution/check/{check_slug}/options", json={"enabled": True}
|
|
)
|
|
assert resp.status == 200
|
|
|
|
# V2 should NOT accept legacy slug
|
|
resp = await api_client_v2.post(
|
|
f"/v2/resolution/check/{legacy_slug}/options", json={"enabled": False}
|
|
)
|
|
assert resp.status == HTTPStatus.NOT_FOUND
|