From 59a7e9519da55e37a9465328a13596a43e736415 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 7 Apr 2025 11:09:45 +0200 Subject: [PATCH] Fix root path requests (#5815) * Fix root path requests Since #5759 we've tried to access the path explicitly. However, this raises KeyError exception when trying to access the proxied root path (e.g. http://supervisor/core/api/). Before #5759 get was used, which lead to no exception, but instead inserted a `None` into the path. It seems aiohttp doesn't provide a path when the root is accessed. So simply convert this to no path as well by setting path to an empty string. * Add rudimentary pytest for regular proxy requets --- supervisor/api/ingress.py | 2 +- supervisor/api/proxy.py | 2 +- tests/api/test_proxy.py | 36 +++++++++++++++++++++++++++++++++++- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/supervisor/api/ingress.py b/supervisor/api/ingress.py index 4ba7f8e1a..a01d4de21 100644 --- a/supervisor/api/ingress.py +++ b/supervisor/api/ingress.py @@ -154,7 +154,7 @@ class APIIngress(CoreSysAttributes): # Process requests addon = self._extract_addon(request) - path = request.match_info["path"] + path = request.match_info.get("path", "") session_data = self.sys_ingress.get_session_data(session) try: # Websocket diff --git a/supervisor/api/proxy.py b/supervisor/api/proxy.py index 69e095e9a..2a17566eb 100644 --- a/supervisor/api/proxy.py +++ b/supervisor/api/proxy.py @@ -117,7 +117,7 @@ class APIProxy(CoreSysAttributes): raise HTTPBadGateway() # Normal request - path = request.match_info["path"] + path = request.match_info.get("path", "") async with self._api_client(request, path) as client: data = await client.read() return web.Response( diff --git a/tests/api/test_proxy.py b/tests/api/test_proxy.py index d031f7b71..9694e4f3f 100644 --- a/tests/api/test_proxy.py +++ b/tests/api/test_proxy.py @@ -7,7 +7,7 @@ from collections.abc import Awaitable, Callable, Coroutine, Generator from json import dumps import logging from typing import Any, cast -from unittest.mock import patch +from unittest.mock import AsyncMock, patch from aiohttp import ClientWebSocketResponse, WSCloseCode from aiohttp.http_websocket import WSMessage, WSMsgType @@ -17,6 +17,7 @@ import pytest from supervisor.addons.addon import Addon from supervisor.api.proxy import APIProxy from supervisor.const import ATTR_ACCESS_TOKEN +from supervisor.homeassistant.api import HomeAssistantAPI def id_generator() -> Generator[int]: @@ -220,3 +221,36 @@ async def test_proxy_auth_abort_log( assert ( "Unexpected message during authentication for WebSocket API" in caplog.text ) + + +@pytest.mark.parametrize("path", ["", "mock_path"]) +async def test_api_proxy_get_request( + api_client: TestClient, + install_addon_example: Addon, + request: pytest.FixtureRequest, + path: str, +): + """Test the API proxy request using patch for make_request.""" + install_addon_example.persist[ATTR_ACCESS_TOKEN] = "abc123" + install_addon_example.data["homeassistant_api"] = True + + request.param = "local_example" + + with patch.object(HomeAssistantAPI, "make_request") as make_request: + # Mock the response from make_request + mock_response = AsyncMock() + mock_response.status = 200 + mock_response.content_type = "application/json" + mock_response.read.return_value = b"mocked response" + make_request.return_value.__aenter__.return_value = mock_response + + response = await api_client.get( + f"/core/api/{path}", headers={"Authorization": "Bearer abc123"} + ) + + assert make_request.call_args[0][0] == "get" + assert make_request.call_args[0][1] == f"api/{path}" + + assert response.status == 200 + assert await response.text() == "mocked response" + assert response.content_type == "application/json"