mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-09 10:16:29 +00:00
Use status 404 in more places when appropriate (#5480)
This commit is contained in:
parent
de68868788
commit
d8101ddba8
@ -106,6 +106,7 @@ from ..exceptions import (
|
|||||||
APIAddonNotInstalled,
|
APIAddonNotInstalled,
|
||||||
APIError,
|
APIError,
|
||||||
APIForbidden,
|
APIForbidden,
|
||||||
|
APINotFound,
|
||||||
PwnedError,
|
PwnedError,
|
||||||
PwnedSecret,
|
PwnedSecret,
|
||||||
)
|
)
|
||||||
@ -161,7 +162,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
|
|
||||||
addon = self.sys_addons.get(addon_slug)
|
addon = self.sys_addons.get(addon_slug)
|
||||||
if not addon:
|
if not addon:
|
||||||
raise APIError(f"Addon {addon_slug} does not exist")
|
raise APINotFound(f"Addon {addon_slug} does not exist")
|
||||||
if not isinstance(addon, Addon) or not addon.is_installed:
|
if not isinstance(addon, Addon) or not addon.is_installed:
|
||||||
raise APIAddonNotInstalled("Addon is not installed")
|
raise APIAddonNotInstalled("Addon is not installed")
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ from ..const import (
|
|||||||
AddonState,
|
AddonState,
|
||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIError, APIForbidden
|
from ..exceptions import APIForbidden, APINotFound
|
||||||
from .utils import api_process, api_validate, require_home_assistant
|
from .utils import api_process, api_validate, require_home_assistant
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
@ -36,7 +36,7 @@ class APIDiscovery(CoreSysAttributes):
|
|||||||
"""Extract discovery message from URL."""
|
"""Extract discovery message from URL."""
|
||||||
message = self.sys_discovery.get(request.match_info.get("uuid"))
|
message = self.sys_discovery.get(request.match_info.get("uuid"))
|
||||||
if not message:
|
if not message:
|
||||||
raise APIError("Discovery message not found")
|
raise APINotFound("Discovery message not found")
|
||||||
return message
|
return message
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
|
@ -16,6 +16,7 @@ from ..const import (
|
|||||||
ATTR_VERSION,
|
ATTR_VERSION,
|
||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
from ..exceptions import APINotFound
|
||||||
from .utils import api_process, api_validate
|
from .utils import api_process, api_validate
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
@ -58,6 +59,9 @@ class APIDocker(CoreSysAttributes):
|
|||||||
async def remove_registry(self, request: web.Request):
|
async def remove_registry(self, request: web.Request):
|
||||||
"""Delete a docker registry."""
|
"""Delete a docker registry."""
|
||||||
hostname = request.match_info.get(ATTR_HOSTNAME)
|
hostname = request.match_info.get(ATTR_HOSTNAME)
|
||||||
|
if hostname not in self.sys_docker.config.registries:
|
||||||
|
raise APINotFound(f"Hostname {hostname} does not exist in registries")
|
||||||
|
|
||||||
del self.sys_docker.config.registries[hostname]
|
del self.sys_docker.config.registries[hostname]
|
||||||
self.sys_docker.config.save_data()
|
self.sys_docker.config.save_data()
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ from aiohttp import web
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIError
|
from ..exceptions import APIError, APINotFound, JobNotFound
|
||||||
from ..jobs import SupervisorJob
|
from ..jobs import SupervisorJob
|
||||||
from ..jobs.const import ATTR_IGNORE_CONDITIONS, JobCondition
|
from ..jobs.const import ATTR_IGNORE_CONDITIONS, JobCondition
|
||||||
from .const import ATTR_JOBS
|
from .const import ATTR_JOBS
|
||||||
@ -23,6 +23,13 @@ SCHEMA_OPTIONS = vol.Schema(
|
|||||||
class APIJobs(CoreSysAttributes):
|
class APIJobs(CoreSysAttributes):
|
||||||
"""Handle RESTful API for OS functions."""
|
"""Handle RESTful API for OS functions."""
|
||||||
|
|
||||||
|
def _extract_job(self, request: web.Request) -> SupervisorJob:
|
||||||
|
"""Extract job from request or raise."""
|
||||||
|
try:
|
||||||
|
return self.sys_jobs.get_job(request.match_info.get("uuid"))
|
||||||
|
except JobNotFound:
|
||||||
|
raise APINotFound("Job does not exist") from None
|
||||||
|
|
||||||
def _list_jobs(self, start: SupervisorJob | None = None) -> list[dict[str, Any]]:
|
def _list_jobs(self, start: SupervisorJob | None = None) -> list[dict[str, Any]]:
|
||||||
"""Return current job tree."""
|
"""Return current job tree."""
|
||||||
jobs_by_parent: dict[str | None, list[SupervisorJob]] = {}
|
jobs_by_parent: dict[str | None, list[SupervisorJob]] = {}
|
||||||
@ -86,13 +93,13 @@ class APIJobs(CoreSysAttributes):
|
|||||||
@api_process
|
@api_process
|
||||||
async def job_info(self, request: web.Request) -> dict[str, Any]:
|
async def job_info(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Get details of a job by ID."""
|
"""Get details of a job by ID."""
|
||||||
job = self.sys_jobs.get_job(request.match_info.get("uuid"))
|
job = self._extract_job(request)
|
||||||
return self._list_jobs(job)[0]
|
return self._list_jobs(job)[0]
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def remove_job(self, request: web.Request) -> None:
|
async def remove_job(self, request: web.Request) -> None:
|
||||||
"""Remove a completed job."""
|
"""Remove a completed job."""
|
||||||
job = self.sys_jobs.get_job(request.match_info.get("uuid"))
|
job = self._extract_job(request)
|
||||||
|
|
||||||
if not job.done:
|
if not job.done:
|
||||||
raise APIError(f"Job {job.uuid} is not done!")
|
raise APIError(f"Job {job.uuid} is not done!")
|
||||||
|
@ -7,7 +7,7 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from ..const import ATTR_NAME, ATTR_STATE
|
from ..const import ATTR_NAME, ATTR_STATE
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIError
|
from ..exceptions import APIError, APINotFound
|
||||||
from ..mounts.const import ATTR_DEFAULT_BACKUP_MOUNT, MountUsage
|
from ..mounts.const import ATTR_DEFAULT_BACKUP_MOUNT, MountUsage
|
||||||
from ..mounts.mount import Mount
|
from ..mounts.mount import Mount
|
||||||
from ..mounts.validate import SCHEMA_MOUNT_CONFIG
|
from ..mounts.validate import SCHEMA_MOUNT_CONFIG
|
||||||
@ -24,6 +24,13 @@ SCHEMA_OPTIONS = vol.Schema(
|
|||||||
class APIMounts(CoreSysAttributes):
|
class APIMounts(CoreSysAttributes):
|
||||||
"""Handle REST API for mounting options."""
|
"""Handle REST API for mounting options."""
|
||||||
|
|
||||||
|
def _extract_mount(self, request: web.Request) -> Mount:
|
||||||
|
"""Extract mount from request or raise."""
|
||||||
|
name = request.match_info.get("mount")
|
||||||
|
if name not in self.sys_mounts:
|
||||||
|
raise APINotFound(f"No mount exists with name {name}")
|
||||||
|
return self.sys_mounts.get(name)
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request: web.Request) -> dict[str, Any]:
|
async def info(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return MountManager info."""
|
"""Return MountManager info."""
|
||||||
@ -85,15 +92,13 @@ class APIMounts(CoreSysAttributes):
|
|||||||
@api_process
|
@api_process
|
||||||
async def update_mount(self, request: web.Request) -> None:
|
async def update_mount(self, request: web.Request) -> None:
|
||||||
"""Update an existing mount in supervisor."""
|
"""Update an existing mount in supervisor."""
|
||||||
name = request.match_info.get("mount")
|
current = self._extract_mount(request)
|
||||||
name_schema = vol.Schema(
|
name_schema = vol.Schema(
|
||||||
{vol.Optional(ATTR_NAME, default=name): name}, extra=vol.ALLOW_EXTRA
|
{vol.Optional(ATTR_NAME, default=current.name): current.name},
|
||||||
|
extra=vol.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
body = await api_validate(vol.All(name_schema, SCHEMA_MOUNT_CONFIG), request)
|
body = await api_validate(vol.All(name_schema, SCHEMA_MOUNT_CONFIG), request)
|
||||||
|
|
||||||
if name not in self.sys_mounts:
|
|
||||||
raise APIError(f"No mount exists with name {name}")
|
|
||||||
|
|
||||||
mount = Mount.from_dict(self.coresys, body)
|
mount = Mount.from_dict(self.coresys, body)
|
||||||
await self.sys_mounts.create_mount(mount)
|
await self.sys_mounts.create_mount(mount)
|
||||||
|
|
||||||
@ -110,8 +115,8 @@ class APIMounts(CoreSysAttributes):
|
|||||||
@api_process
|
@api_process
|
||||||
async def delete_mount(self, request: web.Request) -> None:
|
async def delete_mount(self, request: web.Request) -> None:
|
||||||
"""Delete an existing mount in supervisor."""
|
"""Delete an existing mount in supervisor."""
|
||||||
name = request.match_info.get("mount")
|
current = self._extract_mount(request)
|
||||||
mount = await self.sys_mounts.remove_mount(name)
|
mount = await self.sys_mounts.remove_mount(current.name)
|
||||||
|
|
||||||
# If it was a backup mount, reload backups
|
# If it was a backup mount, reload backups
|
||||||
if mount.usage == MountUsage.BACKUP:
|
if mount.usage == MountUsage.BACKUP:
|
||||||
@ -122,9 +127,9 @@ class APIMounts(CoreSysAttributes):
|
|||||||
@api_process
|
@api_process
|
||||||
async def reload_mount(self, request: web.Request) -> None:
|
async def reload_mount(self, request: web.Request) -> None:
|
||||||
"""Reload an existing mount in supervisor."""
|
"""Reload an existing mount in supervisor."""
|
||||||
name = request.match_info.get("mount")
|
mount = self._extract_mount(request)
|
||||||
await self.sys_mounts.reload_mount(name)
|
await self.sys_mounts.reload_mount(mount.name)
|
||||||
|
|
||||||
# If it's a backup mount, reload backups
|
# If it's a backup mount, reload backups
|
||||||
if self.sys_mounts.get(name).usage == MountUsage.BACKUP:
|
if mount.usage == MountUsage.BACKUP:
|
||||||
self.sys_create_task(self.sys_backups.reload())
|
self.sys_create_task(self.sys_backups.reload())
|
||||||
|
@ -42,7 +42,7 @@ from ..const import (
|
|||||||
DOCKER_NETWORK_MASK,
|
DOCKER_NETWORK_MASK,
|
||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIError, HostNetworkNotFound
|
from ..exceptions import APIError, APINotFound, HostNetworkNotFound
|
||||||
from ..host.configuration import (
|
from ..host.configuration import (
|
||||||
AccessPoint,
|
AccessPoint,
|
||||||
Interface,
|
Interface,
|
||||||
@ -167,7 +167,7 @@ class APINetwork(CoreSysAttributes):
|
|||||||
except HostNetworkNotFound:
|
except HostNetworkNotFound:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
raise APIError(f"Interface {name} does not exist") from None
|
raise APINotFound(f"Interface {name} does not exist") from None
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request: web.Request) -> dict[str, Any]:
|
async def info(self, request: web.Request) -> dict[str, Any]:
|
||||||
|
@ -19,8 +19,9 @@ from ..const import (
|
|||||||
ATTR_UNSUPPORTED,
|
ATTR_UNSUPPORTED,
|
||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIError, ResolutionNotFound
|
from ..exceptions import APINotFound, ResolutionNotFound
|
||||||
from ..resolution.data import Suggestion
|
from ..resolution.checks.base import CheckBase
|
||||||
|
from ..resolution.data import Issue, Suggestion
|
||||||
from .utils import api_process, api_validate
|
from .utils import api_process, api_validate
|
||||||
|
|
||||||
SCHEMA_CHECK_OPTIONS = vol.Schema({vol.Optional(ATTR_ENABLED): bool})
|
SCHEMA_CHECK_OPTIONS = vol.Schema({vol.Optional(ATTR_ENABLED): bool})
|
||||||
@ -29,6 +30,29 @@ SCHEMA_CHECK_OPTIONS = vol.Schema({vol.Optional(ATTR_ENABLED): bool})
|
|||||||
class APIResoulution(CoreSysAttributes):
|
class APIResoulution(CoreSysAttributes):
|
||||||
"""Handle REST API for resoulution."""
|
"""Handle REST API for resoulution."""
|
||||||
|
|
||||||
|
def _extract_issue(self, request: web.Request) -> Issue:
|
||||||
|
"""Extract issue from request or raise."""
|
||||||
|
try:
|
||||||
|
return self.sys_resolution.get_issue(request.match_info.get("issue"))
|
||||||
|
except ResolutionNotFound:
|
||||||
|
raise APINotFound("The supplied UUID is not a valid issue") from None
|
||||||
|
|
||||||
|
def _extract_suggestion(self, request: web.Request) -> Suggestion:
|
||||||
|
"""Extract suggestion from request or raise."""
|
||||||
|
try:
|
||||||
|
return self.sys_resolution.get_suggestion(
|
||||||
|
request.match_info.get("suggestion")
|
||||||
|
)
|
||||||
|
except ResolutionNotFound:
|
||||||
|
raise APINotFound("The supplied UUID is not a valid suggestion") from None
|
||||||
|
|
||||||
|
def _extract_check(self, request: web.Request) -> CheckBase:
|
||||||
|
"""Extract check from request or raise."""
|
||||||
|
try:
|
||||||
|
return self.sys_resolution.check.get(request.match_info.get("check"))
|
||||||
|
except ResolutionNotFound:
|
||||||
|
raise APINotFound("The supplied check slug is not available") from None
|
||||||
|
|
||||||
def _generate_suggestion_information(self, suggestion: Suggestion):
|
def _generate_suggestion_information(self, suggestion: Suggestion):
|
||||||
"""Generate suggestion information for response."""
|
"""Generate suggestion information for response."""
|
||||||
resp = attr.asdict(suggestion)
|
resp = attr.asdict(suggestion)
|
||||||
@ -61,47 +85,31 @@ class APIResoulution(CoreSysAttributes):
|
|||||||
@api_process
|
@api_process
|
||||||
async def apply_suggestion(self, request: web.Request) -> None:
|
async def apply_suggestion(self, request: web.Request) -> None:
|
||||||
"""Apply suggestion."""
|
"""Apply suggestion."""
|
||||||
try:
|
suggestion = self._extract_suggestion(request)
|
||||||
suggestion = self.sys_resolution.get_suggestion(
|
|
||||||
request.match_info.get("suggestion")
|
|
||||||
)
|
|
||||||
await self.sys_resolution.apply_suggestion(suggestion)
|
await self.sys_resolution.apply_suggestion(suggestion)
|
||||||
except ResolutionNotFound:
|
|
||||||
raise APIError("The supplied UUID is not a valid suggestion") from None
|
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def dismiss_suggestion(self, request: web.Request) -> None:
|
async def dismiss_suggestion(self, request: web.Request) -> None:
|
||||||
"""Dismiss suggestion."""
|
"""Dismiss suggestion."""
|
||||||
try:
|
suggestion = self._extract_suggestion(request)
|
||||||
suggestion = self.sys_resolution.get_suggestion(
|
|
||||||
request.match_info.get("suggestion")
|
|
||||||
)
|
|
||||||
self.sys_resolution.dismiss_suggestion(suggestion)
|
self.sys_resolution.dismiss_suggestion(suggestion)
|
||||||
except ResolutionNotFound:
|
|
||||||
raise APIError("The supplied UUID is not a valid suggestion") from None
|
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def suggestions_for_issue(self, request: web.Request) -> dict[str, Any]:
|
async def suggestions_for_issue(self, request: web.Request) -> dict[str, Any]:
|
||||||
"""Return suggestions that fix an issue."""
|
"""Return suggestions that fix an issue."""
|
||||||
try:
|
issue = self._extract_issue(request)
|
||||||
issue = self.sys_resolution.get_issue(request.match_info.get("issue"))
|
|
||||||
return {
|
return {
|
||||||
ATTR_SUGGESTIONS: [
|
ATTR_SUGGESTIONS: [
|
||||||
self._generate_suggestion_information(suggestion)
|
self._generate_suggestion_information(suggestion)
|
||||||
for suggestion in self.sys_resolution.suggestions_for_issue(issue)
|
for suggestion in self.sys_resolution.suggestions_for_issue(issue)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
except ResolutionNotFound:
|
|
||||||
raise APIError("The supplied UUID is not a valid issue") from None
|
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def dismiss_issue(self, request: web.Request) -> None:
|
async def dismiss_issue(self, request: web.Request) -> None:
|
||||||
"""Dismiss issue."""
|
"""Dismiss issue."""
|
||||||
try:
|
issue = self._extract_issue(request)
|
||||||
issue = self.sys_resolution.get_issue(request.match_info.get("issue"))
|
|
||||||
self.sys_resolution.dismiss_issue(issue)
|
self.sys_resolution.dismiss_issue(issue)
|
||||||
except ResolutionNotFound:
|
|
||||||
raise APIError("The supplied UUID is not a valid issue") from None
|
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def healthcheck(self, request: web.Request) -> Awaitable[None]:
|
def healthcheck(self, request: web.Request) -> Awaitable[None]:
|
||||||
@ -112,11 +120,7 @@ class APIResoulution(CoreSysAttributes):
|
|||||||
async def options_check(self, request: web.Request) -> None:
|
async def options_check(self, request: web.Request) -> None:
|
||||||
"""Set options for check."""
|
"""Set options for check."""
|
||||||
body = await api_validate(SCHEMA_CHECK_OPTIONS, request)
|
body = await api_validate(SCHEMA_CHECK_OPTIONS, request)
|
||||||
|
check = self._extract_check(request)
|
||||||
try:
|
|
||||||
check = self.sys_resolution.check.get(request.match_info.get("check"))
|
|
||||||
except ResolutionNotFound:
|
|
||||||
raise APIError("The supplied check slug is not available") from None
|
|
||||||
|
|
||||||
# Apply options
|
# Apply options
|
||||||
if ATTR_ENABLED in body:
|
if ATTR_ENABLED in body:
|
||||||
@ -127,9 +131,5 @@ class APIResoulution(CoreSysAttributes):
|
|||||||
@api_process
|
@api_process
|
||||||
async def run_check(self, request: web.Request) -> None:
|
async def run_check(self, request: web.Request) -> None:
|
||||||
"""Execute a backend check."""
|
"""Execute a backend check."""
|
||||||
try:
|
check = self._extract_check(request)
|
||||||
check = self.sys_resolution.check.get(request.match_info.get("check"))
|
|
||||||
except ResolutionNotFound:
|
|
||||||
raise APIError("The supplied check slug is not available") from None
|
|
||||||
|
|
||||||
await check()
|
await check()
|
||||||
|
@ -9,7 +9,7 @@ from ..const import (
|
|||||||
REQUEST_FROM,
|
REQUEST_FROM,
|
||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIError, APIForbidden
|
from ..exceptions import APIError, APIForbidden, APINotFound
|
||||||
from .utils import api_process, api_validate
|
from .utils import api_process, api_validate
|
||||||
|
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ class APIServices(CoreSysAttributes):
|
|||||||
"""Return service, throw an exception if it doesn't exist."""
|
"""Return service, throw an exception if it doesn't exist."""
|
||||||
service = self.sys_services.get(request.match_info.get("service"))
|
service = self.sys_services.get(request.match_info.get("service"))
|
||||||
if not service:
|
if not service:
|
||||||
raise APIError("Service does not exist")
|
raise APINotFound("Service does not exist")
|
||||||
|
|
||||||
return service
|
return service
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ from ..const import (
|
|||||||
REQUEST_FROM,
|
REQUEST_FROM,
|
||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIError, APIForbidden
|
from ..exceptions import APIError, APIForbidden, APINotFound
|
||||||
from ..store.addon import AddonStore
|
from ..store.addon import AddonStore
|
||||||
from ..store.repository import Repository
|
from ..store.repository import Repository
|
||||||
from ..store.validate import validate_repository
|
from ..store.validate import validate_repository
|
||||||
@ -74,19 +74,17 @@ class APIStore(CoreSysAttributes):
|
|||||||
def _extract_addon(self, request: web.Request, installed=False) -> AnyAddon:
|
def _extract_addon(self, request: web.Request, installed=False) -> AnyAddon:
|
||||||
"""Return add-on, throw an exception it it doesn't exist."""
|
"""Return add-on, throw an exception it it doesn't exist."""
|
||||||
addon_slug: str = request.match_info.get("addon")
|
addon_slug: str = request.match_info.get("addon")
|
||||||
addon_version: str = request.match_info.get("version", "latest")
|
|
||||||
|
|
||||||
if installed:
|
if not (addon := self.sys_addons.get(addon_slug)):
|
||||||
addon = self.sys_addons.local.get(addon_slug)
|
raise APINotFound(f"Addon {addon_slug} does not exist")
|
||||||
if addon is None or not addon.is_installed:
|
|
||||||
|
if installed and not addon.is_installed:
|
||||||
raise APIError(f"Addon {addon_slug} is not installed")
|
raise APIError(f"Addon {addon_slug} is not installed")
|
||||||
else:
|
|
||||||
addon = self.sys_addons.store.get(addon_slug)
|
|
||||||
|
|
||||||
if not addon:
|
if not installed and addon.is_installed:
|
||||||
raise APIError(
|
if not addon.addon_store:
|
||||||
f"Addon {addon_slug} with version {addon_version} does not exist in the store"
|
raise APINotFound(f"Addon {addon_slug} does not exist in the store")
|
||||||
)
|
return addon.addon_store
|
||||||
|
|
||||||
return addon
|
return addon
|
||||||
|
|
||||||
@ -94,11 +92,12 @@ class APIStore(CoreSysAttributes):
|
|||||||
"""Return repository, throw an exception it it doesn't exist."""
|
"""Return repository, throw an exception it it doesn't exist."""
|
||||||
repository_slug: str = request.match_info.get("repository")
|
repository_slug: str = request.match_info.get("repository")
|
||||||
|
|
||||||
repository = self.sys_store.get(repository_slug)
|
if repository_slug not in self.sys_store.repositories:
|
||||||
if not repository:
|
raise APINotFound(
|
||||||
raise APIError(f"Repository {repository_slug} does not exist in the store")
|
f"Repository {repository_slug} does not exist in the store"
|
||||||
|
)
|
||||||
|
|
||||||
return repository
|
return self.sys_store.get(repository_slug)
|
||||||
|
|
||||||
def _generate_addon_information(
|
def _generate_addon_information(
|
||||||
self, addon: AddonStore, extended: bool = False
|
self, addon: AddonStore, extended: bool = False
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from unittest.mock import MagicMock, PropertyMock, patch
|
from unittest.mock import MagicMock, PropertyMock, patch
|
||||||
|
|
||||||
|
from aiohttp import ClientResponse
|
||||||
from aiohttp.test_utils import TestClient
|
from aiohttp.test_utils import TestClient
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -82,7 +83,7 @@ async def test_api_addon_logs_not_installed(api_client: TestClient):
|
|||||||
"""Test error is returned for non-existing add-on."""
|
"""Test error is returned for non-existing add-on."""
|
||||||
resp = await api_client.get("/addons/hic_sunt_leones/logs")
|
resp = await api_client.get("/addons/hic_sunt_leones/logs")
|
||||||
|
|
||||||
assert resp.status == 400
|
assert resp.status == 404
|
||||||
assert resp.content_type == "text/plain"
|
assert resp.content_type == "text/plain"
|
||||||
content = await resp.text()
|
content = await resp.text()
|
||||||
assert content == "Addon hic_sunt_leones does not exist"
|
assert content == "Addon hic_sunt_leones does not exist"
|
||||||
@ -366,3 +367,71 @@ async def test_addon_options_boot_mode_manual_only_invalid(
|
|||||||
body["message"]
|
body["message"]
|
||||||
== "Addon local_example boot option is set to manual_only so it cannot be changed"
|
== "Addon local_example boot option is set to manual_only so it cannot be changed"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_message(resp: ClientResponse, json_expected: bool) -> str:
|
||||||
|
"""Get message from response based on response type."""
|
||||||
|
if json_expected:
|
||||||
|
body = await resp.json()
|
||||||
|
return body["message"]
|
||||||
|
return await resp.text()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("method", "url", "json_expected"),
|
||||||
|
[
|
||||||
|
("get", "/addons/bad/info", True),
|
||||||
|
("post", "/addons/bad/uninstall", True),
|
||||||
|
("post", "/addons/bad/start", True),
|
||||||
|
("post", "/addons/bad/stop", True),
|
||||||
|
("post", "/addons/bad/restart", True),
|
||||||
|
("post", "/addons/bad/options", True),
|
||||||
|
("post", "/addons/bad/sys_options", True),
|
||||||
|
("post", "/addons/bad/options/validate", True),
|
||||||
|
("post", "/addons/bad/rebuild", True),
|
||||||
|
("post", "/addons/bad/stdin", True),
|
||||||
|
("post", "/addons/bad/security", True),
|
||||||
|
("get", "/addons/bad/stats", True),
|
||||||
|
("get", "/addons/bad/logs", False),
|
||||||
|
("get", "/addons/bad/logs/follow", False),
|
||||||
|
("get", "/addons/bad/logs/boots/1", False),
|
||||||
|
("get", "/addons/bad/logs/boots/1/follow", False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_addon_not_found(
|
||||||
|
api_client: TestClient, method: str, url: str, json_expected: bool
|
||||||
|
):
|
||||||
|
"""Test addon not found error."""
|
||||||
|
resp = await api_client.request(method, url)
|
||||||
|
assert resp.status == 404
|
||||||
|
assert await get_message(resp, json_expected) == "Addon bad does not exist"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("method", "url", "json_expected"),
|
||||||
|
[
|
||||||
|
("post", "/addons/local_ssh/uninstall", True),
|
||||||
|
("post", "/addons/local_ssh/start", True),
|
||||||
|
("post", "/addons/local_ssh/stop", True),
|
||||||
|
("post", "/addons/local_ssh/restart", True),
|
||||||
|
("post", "/addons/local_ssh/options", True),
|
||||||
|
("post", "/addons/local_ssh/sys_options", True),
|
||||||
|
("post", "/addons/local_ssh/options/validate", True),
|
||||||
|
("post", "/addons/local_ssh/rebuild", True),
|
||||||
|
("post", "/addons/local_ssh/stdin", True),
|
||||||
|
("post", "/addons/local_ssh/security", True),
|
||||||
|
("get", "/addons/local_ssh/stats", True),
|
||||||
|
("get", "/addons/local_ssh/logs", False),
|
||||||
|
("get", "/addons/local_ssh/logs/follow", False),
|
||||||
|
("get", "/addons/local_ssh/logs/boots/1", False),
|
||||||
|
("get", "/addons/local_ssh/logs/boots/1/follow", False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.usefixtures("repository")
|
||||||
|
async def test_addon_not_installed(
|
||||||
|
api_client: TestClient, method: str, url: str, json_expected: bool
|
||||||
|
):
|
||||||
|
"""Test addon not installed error."""
|
||||||
|
resp = await api_client.request(method, url)
|
||||||
|
assert resp.status == 400
|
||||||
|
assert await get_message(resp, json_expected) == "Addon is not installed"
|
||||||
|
@ -138,3 +138,15 @@ async def test_api_invalid_discovery(api_client: TestClient, install_addon_ssh:
|
|||||||
|
|
||||||
resp = await api_client.post("/discovery", json={"service": "test", "config": None})
|
resp = await api_client.post("/discovery", json={"service": "test", "config": None})
|
||||||
assert resp.status == 400
|
assert resp.status == 400
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("method", "url"),
|
||||||
|
[("get", "/discovery/bad"), ("delete", "/discovery/bad")],
|
||||||
|
)
|
||||||
|
async def test_discovery_not_found(api_client: TestClient, method: str, url: str):
|
||||||
|
"""Test discovery not found error."""
|
||||||
|
resp = await api_client.request(method, url)
|
||||||
|
assert resp.status == 404
|
||||||
|
resp = await resp.json()
|
||||||
|
assert resp["message"] == "Discovery message not found"
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
"""Test Docker API."""
|
"""Test Docker API."""
|
||||||
|
|
||||||
|
from aiohttp.test_utils import TestClient
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_api_docker_info(api_client):
|
async def test_api_docker_info(api_client: TestClient):
|
||||||
"""Test docker info api."""
|
"""Test docker info api."""
|
||||||
resp = await api_client.get("/docker/info")
|
resp = await api_client.get("/docker/info")
|
||||||
result = await resp.json()
|
result = await resp.json()
|
||||||
@ -12,3 +13,11 @@ async def test_api_docker_info(api_client):
|
|||||||
assert result["data"]["logging"] == "journald"
|
assert result["data"]["logging"] == "journald"
|
||||||
assert result["data"]["storage"] == "overlay2"
|
assert result["data"]["storage"] == "overlay2"
|
||||||
assert result["data"]["version"] == "1.0.0"
|
assert result["data"]["version"] == "1.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_registry_not_found(api_client: TestClient):
|
||||||
|
"""Test registry not found error."""
|
||||||
|
resp = await api_client.delete("/docker/registries/bad")
|
||||||
|
assert resp.status == 404
|
||||||
|
body = await resp.json()
|
||||||
|
assert body["message"] == "Hostname bad does not exist in registries"
|
||||||
|
@ -4,6 +4,7 @@ import asyncio
|
|||||||
from unittest.mock import ANY
|
from unittest.mock import ANY
|
||||||
|
|
||||||
from aiohttp.test_utils import TestClient
|
from aiohttp.test_utils import TestClient
|
||||||
|
import pytest
|
||||||
|
|
||||||
from supervisor.coresys import CoreSys
|
from supervisor.coresys import CoreSys
|
||||||
from supervisor.jobs.const import ATTR_IGNORE_CONDITIONS, JobCondition
|
from supervisor.jobs.const import ATTR_IGNORE_CONDITIONS, JobCondition
|
||||||
@ -213,6 +214,18 @@ async def test_job_manual_cleanup(api_client: TestClient, coresys: CoreSys):
|
|||||||
|
|
||||||
# Confirm it no longer exists
|
# Confirm it no longer exists
|
||||||
resp = await api_client.get(f"/jobs/{test.job_id}")
|
resp = await api_client.get(f"/jobs/{test.job_id}")
|
||||||
assert resp.status == 400
|
assert resp.status == 404
|
||||||
result = await resp.json()
|
result = await resp.json()
|
||||||
assert result["message"] == f"No job found with id {test.job_id}"
|
assert result["message"] == "Job does not exist"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("method", "url"),
|
||||||
|
[("get", "/jobs/bad"), ("delete", "/jobs/bad")],
|
||||||
|
)
|
||||||
|
async def test_job_not_found(api_client: TestClient, method: str, url: str):
|
||||||
|
"""Test job not found error."""
|
||||||
|
resp = await api_client.request(method, url)
|
||||||
|
assert resp.status == 404
|
||||||
|
body = await resp.json()
|
||||||
|
assert body["message"] == "Job does not exist"
|
||||||
|
@ -264,25 +264,6 @@ async def test_api_update_mount(
|
|||||||
coresys.mounts.save_data.assert_called_once()
|
coresys.mounts.save_data.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
async def test_api_update_error_mount_missing(
|
|
||||||
api_client: TestClient, mount_propagation
|
|
||||||
):
|
|
||||||
"""Test update mount API errors when mount does not exist."""
|
|
||||||
resp = await api_client.put(
|
|
||||||
"/mounts/backup_test",
|
|
||||||
json={
|
|
||||||
"type": "cifs",
|
|
||||||
"usage": "backup",
|
|
||||||
"server": "backup.local",
|
|
||||||
"share": "new_backups",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
assert resp.status == 400
|
|
||||||
result = await resp.json()
|
|
||||||
assert result["result"] == "error"
|
|
||||||
assert result["message"] == "No mount exists with name backup_test"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_api_update_dbus_error_mount_remains(
|
async def test_api_update_dbus_error_mount_remains(
|
||||||
api_client: TestClient,
|
api_client: TestClient,
|
||||||
all_dbus_services: dict[str, DBusServiceMock],
|
all_dbus_services: dict[str, DBusServiceMock],
|
||||||
@ -399,20 +380,6 @@ async def test_api_reload_mount(
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
async def test_api_reload_error_mount_missing(
|
|
||||||
api_client: TestClient, mount_propagation
|
|
||||||
):
|
|
||||||
"""Test reload mount API errors when mount does not exist."""
|
|
||||||
resp = await api_client.post("/mounts/backup_test/reload")
|
|
||||||
assert resp.status == 400
|
|
||||||
result = await resp.json()
|
|
||||||
assert result["result"] == "error"
|
|
||||||
assert (
|
|
||||||
result["message"]
|
|
||||||
== "Cannot reload 'backup_test', no mount exists with that name"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_api_delete_mount(
|
async def test_api_delete_mount(
|
||||||
api_client: TestClient,
|
api_client: TestClient,
|
||||||
coresys: CoreSys,
|
coresys: CoreSys,
|
||||||
@ -435,20 +402,6 @@ async def test_api_delete_mount(
|
|||||||
coresys.mounts.save_data.assert_called_once()
|
coresys.mounts.save_data.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
async def test_api_delete_error_mount_missing(
|
|
||||||
api_client: TestClient, mount_propagation
|
|
||||||
):
|
|
||||||
"""Test delete mount API errors when mount does not exist."""
|
|
||||||
resp = await api_client.delete("/mounts/backup_test")
|
|
||||||
assert resp.status == 400
|
|
||||||
result = await resp.json()
|
|
||||||
assert result["result"] == "error"
|
|
||||||
assert (
|
|
||||||
result["message"]
|
|
||||||
== "Cannot remove 'backup_test', no mount exists with that name"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_api_create_backup_mount_sets_default(
|
async def test_api_create_backup_mount_sets_default(
|
||||||
api_client: TestClient,
|
api_client: TestClient,
|
||||||
coresys: CoreSys,
|
coresys: CoreSys,
|
||||||
@ -903,3 +856,19 @@ async def test_api_read_only_backup_mount_invalid(
|
|||||||
result = await resp.json()
|
result = await resp.json()
|
||||||
assert result["result"] == "error"
|
assert result["result"] == "error"
|
||||||
assert "Backup mounts cannot be read only" in result["message"]
|
assert "Backup mounts cannot be read only" in result["message"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("method", "url"),
|
||||||
|
[
|
||||||
|
("put", "/mounts/bad"),
|
||||||
|
("delete", "/mounts/bad"),
|
||||||
|
("post", "/mounts/bad/reload"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_mount_not_found(api_client: TestClient, method: str, url: str):
|
||||||
|
"""Test mount not found error."""
|
||||||
|
resp = await api_client.request(method, url)
|
||||||
|
assert resp.status == 404
|
||||||
|
resp = await resp.json()
|
||||||
|
assert resp["message"] == "No mount exists with name bad"
|
||||||
|
@ -400,3 +400,22 @@ async def test_api_network_vlan(
|
|||||||
"id": Variant("u", 1),
|
"id": Variant("u", 1),
|
||||||
"parent": Variant("s", "0c23631e-2118-355c-bbb0-8943229cb0d6"),
|
"parent": Variant("s", "0c23631e-2118-355c-bbb0-8943229cb0d6"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("method", "url"),
|
||||||
|
[
|
||||||
|
("get", "/network/interface/bad/info"),
|
||||||
|
("post", "/network/interface/bad/update"),
|
||||||
|
("get", "/network/interface/bad/accesspoints"),
|
||||||
|
("post", "/network/interface/bad/vlan/1"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_network_interface_not_found(
|
||||||
|
api_client: TestClient, method: str, url: str
|
||||||
|
):
|
||||||
|
"""Test network interface not found error."""
|
||||||
|
resp = await api_client.request(method, url)
|
||||||
|
assert resp.status == 404
|
||||||
|
body = await resp.json()
|
||||||
|
assert body["message"] == "Interface bad does not exist"
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
|
from aiohttp.test_utils import TestClient
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from supervisor.const import (
|
from supervisor.const import (
|
||||||
@ -24,7 +25,7 @@ from supervisor.resolution.data import Issue, Suggestion
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_api_resolution_base(coresys: CoreSys, api_client):
|
async def test_api_resolution_base(coresys: CoreSys, api_client: TestClient):
|
||||||
"""Test resolution manager api."""
|
"""Test resolution manager api."""
|
||||||
coresys.resolution.unsupported = UnsupportedReason.OS
|
coresys.resolution.unsupported = UnsupportedReason.OS
|
||||||
coresys.resolution.suggestions = Suggestion(
|
coresys.resolution.suggestions = Suggestion(
|
||||||
@ -42,7 +43,9 @@ async def test_api_resolution_base(coresys: CoreSys, api_client):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_api_resolution_dismiss_suggestion(coresys: CoreSys, api_client):
|
async def test_api_resolution_dismiss_suggestion(
|
||||||
|
coresys: CoreSys, api_client: TestClient
|
||||||
|
):
|
||||||
"""Test resolution manager suggestion apply api."""
|
"""Test resolution manager suggestion apply api."""
|
||||||
coresys.resolution.suggestions = clear_backup = Suggestion(
|
coresys.resolution.suggestions = clear_backup = Suggestion(
|
||||||
SuggestionType.CLEAR_FULL_BACKUP, ContextType.SYSTEM
|
SuggestionType.CLEAR_FULL_BACKUP, ContextType.SYSTEM
|
||||||
@ -54,7 +57,9 @@ async def test_api_resolution_dismiss_suggestion(coresys: CoreSys, api_client):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_api_resolution_apply_suggestion(coresys: CoreSys, api_client):
|
async def test_api_resolution_apply_suggestion(
|
||||||
|
coresys: CoreSys, api_client: TestClient
|
||||||
|
):
|
||||||
"""Test resolution manager suggestion apply api."""
|
"""Test resolution manager suggestion apply api."""
|
||||||
coresys.resolution.suggestions = clear_backup = Suggestion(
|
coresys.resolution.suggestions = clear_backup = Suggestion(
|
||||||
SuggestionType.CLEAR_FULL_BACKUP, ContextType.SYSTEM
|
SuggestionType.CLEAR_FULL_BACKUP, ContextType.SYSTEM
|
||||||
@ -82,7 +87,7 @@ async def test_api_resolution_apply_suggestion(coresys: CoreSys, api_client):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_api_resolution_dismiss_issue(coresys: CoreSys, api_client):
|
async def test_api_resolution_dismiss_issue(coresys: CoreSys, api_client: TestClient):
|
||||||
"""Test resolution manager issue apply api."""
|
"""Test resolution manager issue apply api."""
|
||||||
coresys.resolution.issues = updated_failed = Issue(
|
coresys.resolution.issues = updated_failed = Issue(
|
||||||
IssueType.UPDATE_FAILED, ContextType.SYSTEM
|
IssueType.UPDATE_FAILED, ContextType.SYSTEM
|
||||||
@ -94,7 +99,7 @@ async def test_api_resolution_dismiss_issue(coresys: CoreSys, api_client):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_api_resolution_unhealthy(coresys: CoreSys, api_client):
|
async def test_api_resolution_unhealthy(coresys: CoreSys, api_client: TestClient):
|
||||||
"""Test resolution manager api."""
|
"""Test resolution manager api."""
|
||||||
coresys.resolution.unhealthy = UnhealthyReason.DOCKER
|
coresys.resolution.unhealthy = UnhealthyReason.DOCKER
|
||||||
|
|
||||||
@ -104,7 +109,7 @@ async def test_api_resolution_unhealthy(coresys: CoreSys, api_client):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_api_resolution_check_options(coresys: CoreSys, api_client):
|
async def test_api_resolution_check_options(coresys: CoreSys, api_client: TestClient):
|
||||||
"""Test client API with checks options."""
|
"""Test client API with checks options."""
|
||||||
free_space = coresys.resolution.check.get("free_space")
|
free_space = coresys.resolution.check.get("free_space")
|
||||||
|
|
||||||
@ -121,7 +126,7 @@ async def test_api_resolution_check_options(coresys: CoreSys, api_client):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_api_resolution_check_run(coresys: CoreSys, api_client):
|
async def test_api_resolution_check_run(coresys: CoreSys, api_client: TestClient):
|
||||||
"""Test client API with run check."""
|
"""Test client API with run check."""
|
||||||
coresys.core.state = CoreState.RUNNING
|
coresys.core.state = CoreState.RUNNING
|
||||||
free_space = coresys.resolution.check.get("free_space")
|
free_space = coresys.resolution.check.get("free_space")
|
||||||
@ -133,7 +138,9 @@ async def test_api_resolution_check_run(coresys: CoreSys, api_client):
|
|||||||
assert free_space.run_check.called
|
assert free_space.run_check.called
|
||||||
|
|
||||||
|
|
||||||
async def test_api_resolution_suggestions_for_issue(coresys: CoreSys, api_client):
|
async def test_api_resolution_suggestions_for_issue(
|
||||||
|
coresys: CoreSys, api_client: TestClient
|
||||||
|
):
|
||||||
"""Test getting suggestions that fix an issue."""
|
"""Test getting suggestions that fix an issue."""
|
||||||
coresys.resolution.issues = corrupt_repo = Issue(
|
coresys.resolution.issues = corrupt_repo = Issue(
|
||||||
IssueType.CORRUPT_REPOSITORY, ContextType.STORE, "repo_1"
|
IssueType.CORRUPT_REPOSITORY, ContextType.STORE, "repo_1"
|
||||||
@ -165,3 +172,39 @@ async def test_api_resolution_suggestions_for_issue(coresys: CoreSys, api_client
|
|||||||
]
|
]
|
||||||
assert len(suggestion) == 1
|
assert len(suggestion) == 1
|
||||||
assert suggestion[0]["auto"] is False
|
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: TestClient, method: str, url: str):
|
||||||
|
"""Test issue not found error."""
|
||||||
|
resp = await api_client.request(method, url)
|
||||||
|
assert resp.status == 404
|
||||||
|
body = await resp.json()
|
||||||
|
assert body["message"] == "The supplied UUID is not a valid issue"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("method", "url"),
|
||||||
|
[("delete", "/resolution/suggestion/bad"), ("post", "/resolution/suggestion/bad")],
|
||||||
|
)
|
||||||
|
async def test_suggestion_not_found(api_client: TestClient, method: str, url: str):
|
||||||
|
"""Test suggestion not found error."""
|
||||||
|
resp = await api_client.request(method, url)
|
||||||
|
assert resp.status == 404
|
||||||
|
body = await resp.json()
|
||||||
|
assert body["message"] == "The supplied UUID is not a valid suggestion"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("method", "url"),
|
||||||
|
[("post", "/resolution/check/bad/options"), ("post", "/resolution/check/bad/run")],
|
||||||
|
)
|
||||||
|
async def test_check_not_found(api_client: TestClient, method: str, url: str):
|
||||||
|
"""Test check not found error."""
|
||||||
|
resp = await api_client.request(method, url)
|
||||||
|
assert resp.status == 404
|
||||||
|
body = await resp.json()
|
||||||
|
assert body["message"] == "The supplied check slug is not available"
|
||||||
|
16
tests/api/test_services.py
Normal file
16
tests/api/test_services.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""Test services API."""
|
||||||
|
|
||||||
|
from aiohttp.test_utils import TestClient
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("method", "url"),
|
||||||
|
[("get", "/services/bad"), ("post", "/services/bad"), ("delete", "/services/bad")],
|
||||||
|
)
|
||||||
|
async def test_service_not_found(api_client: TestClient, method: str, url: str):
|
||||||
|
"""Test service not found error."""
|
||||||
|
resp = await api_client.request(method, url)
|
||||||
|
assert resp.status == 404
|
||||||
|
body = await resp.json()
|
||||||
|
assert body["message"] == "Service does not exist"
|
@ -4,6 +4,7 @@ import asyncio
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import MagicMock, PropertyMock, patch
|
from unittest.mock import MagicMock, PropertyMock, patch
|
||||||
|
|
||||||
|
from aiohttp import ClientResponse
|
||||||
from aiohttp.test_utils import TestClient
|
from aiohttp.test_utils import TestClient
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -235,7 +236,7 @@ async def test_api_detached_addon_changelog(
|
|||||||
resp = await api_client.get(f"/{resource}/{install_addon_ssh.slug}/changelog")
|
resp = await api_client.get(f"/{resource}/{install_addon_ssh.slug}/changelog")
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
result = await resp.text()
|
result = await resp.text()
|
||||||
assert result == "Addon local_ssh with version latest does not exist in the store"
|
assert result == "Addon local_ssh does not exist in the store"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("resource", ["store/addons", "addons"])
|
@pytest.mark.parametrize("resource", ["store/addons", "addons"])
|
||||||
@ -279,4 +280,72 @@ async def test_api_detached_addon_documentation(
|
|||||||
resp = await api_client.get(f"/{resource}/{install_addon_ssh.slug}/documentation")
|
resp = await api_client.get(f"/{resource}/{install_addon_ssh.slug}/documentation")
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
result = await resp.text()
|
result = await resp.text()
|
||||||
assert result == "Addon local_ssh with version latest does not exist in the store"
|
assert result == "Addon local_ssh does not exist in the store"
|
||||||
|
|
||||||
|
|
||||||
|
async def get_message(resp: ClientResponse, json_expected: bool) -> str:
|
||||||
|
"""Get message from response based on response type."""
|
||||||
|
if json_expected:
|
||||||
|
body = await resp.json()
|
||||||
|
return body["message"]
|
||||||
|
return await resp.text()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("method", "url", "json_expected"),
|
||||||
|
[
|
||||||
|
("get", "/store/addons/bad", True),
|
||||||
|
("get", "/store/addons/bad/1", True),
|
||||||
|
("get", "/store/addons/bad/icon", False),
|
||||||
|
("get", "/store/addons/bad/logo", False),
|
||||||
|
("post", "/store/addons/bad/install", True),
|
||||||
|
("post", "/store/addons/bad/install/1", True),
|
||||||
|
("post", "/store/addons/bad/update", True),
|
||||||
|
("post", "/store/addons/bad/update/1", True),
|
||||||
|
# Legacy paths
|
||||||
|
("get", "/addons/bad/icon", False),
|
||||||
|
("get", "/addons/bad/logo", False),
|
||||||
|
("post", "/addons/bad/install", True),
|
||||||
|
("post", "/addons/bad/update", True),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_store_addon_not_found(
|
||||||
|
api_client: TestClient, method: str, url: str, json_expected: bool
|
||||||
|
):
|
||||||
|
"""Test store addon not found error."""
|
||||||
|
resp = await api_client.request(method, url)
|
||||||
|
assert resp.status == 404
|
||||||
|
assert await get_message(resp, json_expected) == "Addon bad does not exist"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("method", "url"),
|
||||||
|
[
|
||||||
|
("post", "/store/addons/local_ssh/update"),
|
||||||
|
("post", "/store/addons/local_ssh/update/1"),
|
||||||
|
# Legacy paths
|
||||||
|
("post", "/addons/local_ssh/update"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.usefixtures("repository")
|
||||||
|
async def test_store_addon_not_installed(api_client: TestClient, method: str, url: str):
|
||||||
|
"""Test store addon not installed error."""
|
||||||
|
resp = await api_client.request(method, url)
|
||||||
|
assert resp.status == 400
|
||||||
|
body = await resp.json()
|
||||||
|
assert body["message"] == "Addon local_ssh is not installed"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("method", "url"),
|
||||||
|
[
|
||||||
|
("get", "/store/repositories/bad"),
|
||||||
|
("delete", "/store/repositories/bad"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_repository_not_found(api_client: TestClient, method: str, url: str):
|
||||||
|
"""Test repository not found error."""
|
||||||
|
resp = await api_client.request(method, url)
|
||||||
|
assert resp.status == 404
|
||||||
|
body = await resp.json()
|
||||||
|
assert body["message"] == "Repository bad does not exist in the store"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user