From c0c0c4b7addcfc3a4d4f2500c2608ec6200a5cce Mon Sep 17 00:00:00 2001 From: Mike Degatano Date: Tue, 21 May 2024 03:02:01 -0400 Subject: [PATCH] Fix doc and changelog API response for orphaned addons (#5082) * Fix doc and changelog API response for orphaned addons * Use correct terminology in tests --- supervisor/api/store.py | 16 +++++++-- tests/api/test_store.py | 75 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/supervisor/api/store.py b/supervisor/api/store.py index d82834685..14897452b 100644 --- a/supervisor/api/store.py +++ b/supervisor/api/store.py @@ -249,7 +249,12 @@ class APIStore(CoreSysAttributes): @api_process_raw(CONTENT_TYPE_TEXT) async def addons_addon_changelog(self, request: web.Request) -> str: """Return changelog from add-on.""" - addon = self._extract_addon(request) + # Frontend can't handle error response here, need to return 200 and error as text for now + try: + addon = self._extract_addon(request) + except APIError as err: + return str(err) + if not addon.with_changelog: return f"No changelog found for add-on {addon.slug}!" @@ -259,9 +264,14 @@ class APIStore(CoreSysAttributes): @api_process_raw(CONTENT_TYPE_TEXT) async def addons_addon_documentation(self, request: web.Request) -> str: """Return documentation from add-on.""" - addon = self._extract_addon(request) + # Frontend can't handle error response here, need to return 200 and error as text for now + try: + addon = self._extract_addon(request) + except APIError as err: + return str(err) + if not addon.with_documentation: - raise APIError(f"No documentation found for add-on {addon.slug}!") + return f"No documentation found for add-on {addon.slug}!" with addon.path_documentation.open("r") as documentation: return documentation.read() diff --git a/tests/api/test_store.py b/tests/api/test_store.py index 4dc88f1a4..e629845cd 100644 --- a/tests/api/test_store.py +++ b/tests/api/test_store.py @@ -1,6 +1,7 @@ """Test Store API.""" import asyncio +from pathlib import Path from unittest.mock import MagicMock, PropertyMock, patch from aiohttp.test_utils import TestClient @@ -8,6 +9,7 @@ import pytest from supervisor.addons.addon import Addon from supervisor.arch import CpuArch +from supervisor.config import CoreConfig from supervisor.const import AddonState from supervisor.coresys import CoreSys from supervisor.docker.addon import DockerAddon @@ -199,7 +201,80 @@ async def test_api_store_addons_no_changelog( Currently the frontend expects a valid body even in the error case. Make sure that is what the API returns. """ + assert store_addon.with_changelog is False resp = await api_client.get(f"/{resource}/{store_addon.slug}/changelog") assert resp.status == 200 result = await resp.text() assert result == "No changelog found for add-on test_store_addon!" + + +@pytest.mark.parametrize("resource", ["store/addons", "addons"]) +async def test_api_detached_addon_changelog( + api_client: TestClient, + coresys: CoreSys, + install_addon_ssh: Addon, + tmp_supervisor_data: Path, + resource: str, +): + """Test /store/addons/{addon}/changelog for an detached addon. + + Currently the frontend expects a valid body even in the error case. Make sure that is + what the API returns. + """ + (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 install_addon_ssh.is_detached is True + assert install_addon_ssh.with_changelog is False + + resp = await api_client.get(f"/{resource}/{install_addon_ssh.slug}/changelog") + assert resp.status == 200 + result = await resp.text() + assert result == "Addon local_ssh with version latest does not exist in the store" + + +@pytest.mark.parametrize("resource", ["store/addons", "addons"]) +async def test_api_store_addons_no_documentation( + api_client: TestClient, coresys: CoreSys, store_addon: AddonStore, resource: str +): + """Test /store/addons/{addon}/documentation REST API. + + Currently the frontend expects a valid body even in the error case. Make sure that is + what the API returns. + """ + assert store_addon.with_documentation is False + resp = await api_client.get(f"/{resource}/{store_addon.slug}/documentation") + assert resp.status == 200 + result = await resp.text() + assert result == "No documentation found for add-on test_store_addon!" + + +@pytest.mark.parametrize("resource", ["store/addons", "addons"]) +async def test_api_detached_addon_documentation( + api_client: TestClient, + coresys: CoreSys, + install_addon_ssh: Addon, + tmp_supervisor_data: Path, + resource: str, +): + """Test /store/addons/{addon}/changelog for an detached addon. + + Currently the frontend expects a valid body even in the error case. Make sure that is + what the API returns. + """ + (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 install_addon_ssh.is_detached is True + assert install_addon_ssh.with_documentation is False + + resp = await api_client.get(f"/{resource}/{install_addon_ssh.slug}/documentation") + assert resp.status == 200 + result = await resp.text() + assert result == "Addon local_ssh with version latest does not exist in the store"