diff --git a/homeassistant/components/portainer/strings.json b/homeassistant/components/portainer/strings.json index c0fffcf504b1a9..abb215e1455ca0 100644 --- a/homeassistant/components/portainer/strings.json +++ b/homeassistant/components/portainer/strings.json @@ -236,5 +236,10 @@ }, "name": "Prune unused images" } + }, + "system_health": { + "info": { + "can_reach_server": "Reach Portainer server" + } } } diff --git a/homeassistant/components/portainer/system_health.py b/homeassistant/components/portainer/system_health.py new file mode 100644 index 00000000000000..7fda712884bc27 --- /dev/null +++ b/homeassistant/components/portainer/system_health.py @@ -0,0 +1,28 @@ +"""Provide info to system health.""" + +from typing import Any + +from homeassistant.components import system_health +from homeassistant.const import CONF_URL +from homeassistant.core import HomeAssistant, callback + +from .const import DOMAIN + + +@callback +def async_register( + hass: HomeAssistant, register: system_health.SystemHealthRegistration +) -> None: + """Register system health callbacks.""" + register.async_register_info(system_health_info) + + +async def system_health_info(hass: HomeAssistant) -> dict[str, Any]: + """Get info for the info page.""" + config_entry = hass.config_entries.async_entries(DOMAIN)[0] + + return { + "can_reach_server": system_health.async_check_can_reach_url( + hass, f"{config_entry.data[CONF_URL].rstrip('/')}/api/system/status" + ), + } diff --git a/tests/components/portainer/test_system_health.py b/tests/components/portainer/test_system_health.py new file mode 100644 index 00000000000000..dad64e707fd9b4 --- /dev/null +++ b/tests/components/portainer/test_system_health.py @@ -0,0 +1,59 @@ +"""Test Portainer system health.""" + +import asyncio +from unittest.mock import AsyncMock + +from aiohttp import ClientError + +from homeassistant.components.portainer.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from . import setup_integration + +from tests.common import MockConfigEntry, get_system_health_info +from tests.test_util.aiohttp import AiohttpClientMocker + +MOCK_HEALTH_URL = "https://127.0.0.1:9000/api/system/status" + + +async def test_system_health( + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, + mock_config_entry: MockConfigEntry, + mock_portainer_client: AsyncMock, +) -> None: + """Test system health when server is reachable.""" + aioclient_mock.get(MOCK_HEALTH_URL, text="ok") + + assert await async_setup_component(hass, "system_health", {}) + await setup_integration(hass, mock_config_entry) + + info = await get_system_health_info(hass, DOMAIN) + + for key, val in info.items(): + if asyncio.iscoroutine(val): + info[key] = await val + + assert info["can_reach_server"] == "ok" + + +async def test_system_health_failed_connect( + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, + mock_config_entry: MockConfigEntry, + mock_portainer_client: AsyncMock, +) -> None: + """Test system health when server is unreachable.""" + aioclient_mock.get(MOCK_HEALTH_URL, exc=ClientError) + + assert await async_setup_component(hass, "system_health", {}) + await setup_integration(hass, mock_config_entry) + + info = await get_system_health_info(hass, DOMAIN) + + for key, val in info.items(): + if asyncio.iscoroutine(val): + info[key] = await val + + assert info["can_reach_server"] == {"error": "unreachable", "type": "failed"}