From 6095035b27a81f8523dd8b79f1d8a0b790e19953 Mon Sep 17 00:00:00 2001 From: Mike Degatano Date: Wed, 15 May 2024 21:54:49 -0400 Subject: [PATCH 1/2] Fix doc and changelog API response for orphaned addons --- 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 d8283468547..14897452bb6 100644 --- a/supervisor/api/store.py +++ b/supervisor/api/store.py @@ -249,7 +249,12 @@ async def addons_addon_logo(self, request: web.Request) -> bytes: @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 @@ async def addons_addon_changelog(self, request: web.Request) -> str: @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 4dc88f1a4c3..bfcb8114903 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 @@ 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_orphaned_addon_changelog( + api_client: TestClient, + coresys: CoreSys, + install_addon_ssh: Addon, + tmp_supervisor_data: Path, + resource: str, +): + """Test /store/addons/{addon}/changelog for an orphaned 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_orphaned_addon_documentation( + api_client: TestClient, + coresys: CoreSys, + install_addon_ssh: Addon, + tmp_supervisor_data: Path, + resource: str, +): + """Test /store/addons/{addon}/changelog for an orphaned 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" From 5b14a6189cbfbc3b61a2194656d4e149d2c51cab Mon Sep 17 00:00:00 2001 From: Mike Degatano Date: Thu, 16 May 2024 16:16:19 -0400 Subject: [PATCH 2/2] Use correct terminology in tests --- tests/api/test_store.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/api/test_store.py b/tests/api/test_store.py index bfcb8114903..e629845cd84 100644 --- a/tests/api/test_store.py +++ b/tests/api/test_store.py @@ -209,14 +209,14 @@ async def test_api_store_addons_no_changelog( @pytest.mark.parametrize("resource", ["store/addons", "addons"]) -async def test_api_orphaned_addon_changelog( +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 orphaned addon. + """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. @@ -253,14 +253,14 @@ async def test_api_store_addons_no_documentation( @pytest.mark.parametrize("resource", ["store/addons", "addons"]) -async def test_api_orphaned_addon_documentation( +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 orphaned addon. + """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.