Skip to content

Commit 9180282

Browse files
mib1185Copilot
andauthored
Add update entity to AdGUard Home (home-assistant#156682)
Co-authored-by: Copilot <[email protected]>
1 parent 118f30f commit 9180282

File tree

6 files changed

+327
-1
lines changed

6 files changed

+327
-1
lines changed

homeassistant/components/adguard/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
{vol.Optional(CONF_FORCE, default=False): cv.boolean}
4646
)
4747

48-
PLATFORMS = [Platform.SENSOR, Platform.SWITCH]
48+
PLATFORMS = [Platform.SENSOR, Platform.SWITCH, Platform.UPDATE]
4949
type AdGuardConfigEntry = ConfigEntry[AdGuardData]
5050

5151

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""AdGuard Home Update platform."""
2+
3+
from __future__ import annotations
4+
5+
from datetime import timedelta
6+
from typing import Any
7+
8+
from adguardhome import AdGuardHomeError
9+
10+
from homeassistant.components.update import UpdateEntity, UpdateEntityFeature
11+
from homeassistant.core import HomeAssistant
12+
from homeassistant.exceptions import HomeAssistantError
13+
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
14+
15+
from . import AdGuardConfigEntry, AdGuardData
16+
from .const import DOMAIN
17+
from .entity import AdGuardHomeEntity
18+
19+
SCAN_INTERVAL = timedelta(seconds=300)
20+
PARALLEL_UPDATES = 1
21+
22+
23+
async def async_setup_entry(
24+
hass: HomeAssistant,
25+
entry: AdGuardConfigEntry,
26+
async_add_entities: AddConfigEntryEntitiesCallback,
27+
) -> None:
28+
"""Set up AdGuard Home update entity based on a config entry."""
29+
data = entry.runtime_data
30+
31+
if (await data.client.update.update_available()).disabled:
32+
return
33+
34+
async_add_entities([AdGuardHomeUpdate(data, entry)], True)
35+
36+
37+
class AdGuardHomeUpdate(AdGuardHomeEntity, UpdateEntity):
38+
"""Defines an AdGuard Home update."""
39+
40+
_attr_supported_features = UpdateEntityFeature.INSTALL
41+
_attr_name = None
42+
43+
def __init__(
44+
self,
45+
data: AdGuardData,
46+
entry: AdGuardConfigEntry,
47+
) -> None:
48+
"""Initialize AdGuard Home update."""
49+
super().__init__(data, entry)
50+
51+
self._attr_unique_id = "_".join(
52+
[DOMAIN, self.adguard.host, str(self.adguard.port), "update"]
53+
)
54+
55+
async def _adguard_update(self) -> None:
56+
"""Update AdGuard Home entity."""
57+
value = await self.adguard.update.update_available()
58+
self._attr_installed_version = self.data.version
59+
self._attr_latest_version = value.new_version
60+
self._attr_release_summary = value.announcement
61+
self._attr_release_url = value.announcement_url
62+
63+
async def async_install(
64+
self, version: str | None, backup: bool, **kwargs: Any
65+
) -> None:
66+
"""Install latest update."""
67+
try:
68+
await self.adguard.update.begin_update()
69+
except AdGuardHomeError as err:
70+
raise HomeAssistantError(f"Failed to install update: {err}") from err
71+
self.hass.config_entries.async_schedule_reload(self._entry.entry_id)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,25 @@
11
"""Tests for the AdGuard Home integration."""
2+
3+
from homeassistant.const import CONTENT_TYPE_JSON
4+
from homeassistant.core import HomeAssistant
5+
6+
from tests.common import MockConfigEntry
7+
from tests.test_util.aiohttp import AiohttpClientMocker
8+
9+
10+
async def setup_integration(
11+
hass: HomeAssistant,
12+
config_entry: MockConfigEntry,
13+
aioclient_mock: AiohttpClientMocker,
14+
) -> None:
15+
"""Fixture for setting up the component."""
16+
config_entry.add_to_hass(hass)
17+
18+
aioclient_mock.get(
19+
"https://127.0.0.1:3000/control/status",
20+
json={"version": "v0.107.50"},
21+
headers={"Content-Type": CONTENT_TYPE_JSON},
22+
)
23+
24+
await hass.config_entries.async_setup(config_entry.entry_id)
25+
await hass.async_block_till_done()
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Common fixtures for the adguard tests."""
2+
3+
import pytest
4+
5+
from homeassistant.components.adguard import DOMAIN
6+
from homeassistant.const import (
7+
CONF_HOST,
8+
CONF_PASSWORD,
9+
CONF_PORT,
10+
CONF_SSL,
11+
CONF_USERNAME,
12+
CONF_VERIFY_SSL,
13+
)
14+
15+
from tests.common import MockConfigEntry
16+
17+
18+
@pytest.fixture
19+
def mock_config_entry() -> MockConfigEntry:
20+
"""Mock a config entry."""
21+
return MockConfigEntry(
22+
domain=DOMAIN,
23+
data={
24+
CONF_HOST: "127.0.0.1",
25+
CONF_PORT: 3000,
26+
CONF_USERNAME: "user",
27+
CONF_PASSWORD: "pass",
28+
CONF_SSL: True,
29+
CONF_VERIFY_SSL: True,
30+
},
31+
title="AdGuard Home",
32+
)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# serializer version: 1
2+
# name: test_update[update.adguard_home-entry]
3+
EntityRegistryEntrySnapshot({
4+
'aliases': set({
5+
}),
6+
'area_id': None,
7+
'capabilities': None,
8+
'config_entry_id': <ANY>,
9+
'config_subentry_id': <ANY>,
10+
'device_class': None,
11+
'device_id': <ANY>,
12+
'disabled_by': None,
13+
'domain': 'update',
14+
'entity_category': <EntityCategory.CONFIG: 'config'>,
15+
'entity_id': 'update.adguard_home',
16+
'has_entity_name': True,
17+
'hidden_by': None,
18+
'icon': None,
19+
'id': <ANY>,
20+
'labels': set({
21+
}),
22+
'name': None,
23+
'options': dict({
24+
}),
25+
'original_device_class': None,
26+
'original_icon': None,
27+
'original_name': None,
28+
'platform': 'adguard',
29+
'previous_unique_id': None,
30+
'suggested_object_id': None,
31+
'supported_features': <UpdateEntityFeature: 1>,
32+
'translation_key': None,
33+
'unique_id': 'adguard_127.0.0.1_3000_update',
34+
'unit_of_measurement': None,
35+
})
36+
# ---
37+
# name: test_update[update.adguard_home-state]
38+
StateSnapshot({
39+
'attributes': ReadOnlyDict({
40+
'auto_update': False,
41+
'display_precision': 0,
42+
'entity_picture': 'https://brands.home-assistant.io/_/adguard/icon.png',
43+
'friendly_name': 'AdGuard Home',
44+
'in_progress': False,
45+
'installed_version': 'v0.107.50',
46+
'latest_version': 'v0.107.59',
47+
'release_summary': 'AdGuard Home v0.107.59 is now available!',
48+
'release_url': 'https://github.com/AdguardTeam/AdGuardHome/releases/tag/v0.107.59',
49+
'skipped_version': None,
50+
'supported_features': <UpdateEntityFeature: 1>,
51+
'title': None,
52+
'update_percentage': None,
53+
}),
54+
'context': <ANY>,
55+
'entity_id': 'update.adguard_home',
56+
'last_changed': <ANY>,
57+
'last_reported': <ANY>,
58+
'last_updated': <ANY>,
59+
'state': 'on',
60+
})
61+
# ---
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
"""Tests for the AdGuard Home update entity."""
2+
3+
from unittest.mock import patch
4+
5+
from adguardhome import AdGuardHomeError
6+
import pytest
7+
from syrupy.assertion import SnapshotAssertion
8+
9+
from homeassistant.const import CONTENT_TYPE_JSON, Platform
10+
from homeassistant.core import HomeAssistant
11+
from homeassistant.exceptions import HomeAssistantError
12+
from homeassistant.helpers import entity_registry as er
13+
14+
from . import setup_integration
15+
16+
from tests.common import MockConfigEntry, snapshot_platform
17+
from tests.test_util.aiohttp import AiohttpClientMocker
18+
19+
20+
async def test_update(
21+
hass: HomeAssistant,
22+
entity_registry: er.EntityRegistry,
23+
aioclient_mock: AiohttpClientMocker,
24+
snapshot: SnapshotAssertion,
25+
mock_config_entry: MockConfigEntry,
26+
) -> None:
27+
"""Test the adguard update platform."""
28+
aioclient_mock.post(
29+
"https://127.0.0.1:3000/control/version.json",
30+
json={
31+
"new_version": "v0.107.59",
32+
"announcement": "AdGuard Home v0.107.59 is now available!",
33+
"announcement_url": "https://github.com/AdguardTeam/AdGuardHome/releases/tag/v0.107.59",
34+
"can_autoupdate": True,
35+
"disabled": False,
36+
},
37+
headers={"Content-Type": CONTENT_TYPE_JSON},
38+
)
39+
40+
with patch("homeassistant.components.adguard.PLATFORMS", [Platform.UPDATE]):
41+
await setup_integration(hass, mock_config_entry, aioclient_mock)
42+
43+
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
44+
45+
46+
async def test_update_disabled(
47+
hass: HomeAssistant,
48+
aioclient_mock: AiohttpClientMocker,
49+
mock_config_entry: MockConfigEntry,
50+
) -> None:
51+
"""Test the adguard update is disabled."""
52+
aioclient_mock.post(
53+
"https://127.0.0.1:3000/control/version.json",
54+
json={"disabled": True},
55+
headers={"Content-Type": CONTENT_TYPE_JSON},
56+
)
57+
58+
with patch("homeassistant.components.adguard.PLATFORMS", [Platform.UPDATE]):
59+
await setup_integration(hass, mock_config_entry, aioclient_mock)
60+
61+
assert not hass.states.async_all()
62+
63+
64+
async def test_update_install(
65+
hass: HomeAssistant,
66+
aioclient_mock: AiohttpClientMocker,
67+
mock_config_entry: MockConfigEntry,
68+
) -> None:
69+
"""Test the adguard update installation."""
70+
aioclient_mock.post(
71+
"https://127.0.0.1:3000/control/version.json",
72+
json={
73+
"new_version": "v0.107.59",
74+
"announcement": "AdGuard Home v0.107.59 is now available!",
75+
"announcement_url": "https://github.com/AdguardTeam/AdGuardHome/releases/tag/v0.107.59",
76+
"can_autoupdate": True,
77+
"disabled": False,
78+
},
79+
headers={"Content-Type": CONTENT_TYPE_JSON},
80+
)
81+
aioclient_mock.post("https://127.0.0.1:3000/control/update")
82+
83+
with patch("homeassistant.components.adguard.PLATFORMS", [Platform.UPDATE]):
84+
await setup_integration(hass, mock_config_entry, aioclient_mock)
85+
86+
aioclient_mock.mock_calls.clear()
87+
88+
await hass.services.async_call(
89+
"update",
90+
"install",
91+
{"entity_id": "update.adguard_home"},
92+
blocking=True,
93+
)
94+
95+
assert aioclient_mock.mock_calls[0][0] == "POST"
96+
assert (
97+
str(aioclient_mock.mock_calls[0][1]) == "https://127.0.0.1:3000/control/update"
98+
)
99+
100+
101+
async def test_update_install_failed(
102+
hass: HomeAssistant,
103+
aioclient_mock: AiohttpClientMocker,
104+
mock_config_entry: MockConfigEntry,
105+
) -> None:
106+
"""Test the adguard update install failed."""
107+
aioclient_mock.post(
108+
"https://127.0.0.1:3000/control/version.json",
109+
json={
110+
"new_version": "v0.107.59",
111+
"announcement": "AdGuard Home v0.107.59 is now available!",
112+
"announcement_url": "https://github.com/AdguardTeam/AdGuardHome/releases/tag/v0.107.59",
113+
"can_autoupdate": True,
114+
"disabled": False,
115+
},
116+
headers={"Content-Type": CONTENT_TYPE_JSON},
117+
)
118+
aioclient_mock.post(
119+
"https://127.0.0.1:3000/control/update", exc=AdGuardHomeError("boom")
120+
)
121+
122+
with patch("homeassistant.components.adguard.PLATFORMS", [Platform.UPDATE]):
123+
await setup_integration(hass, mock_config_entry, aioclient_mock)
124+
125+
aioclient_mock.mock_calls.clear()
126+
127+
with pytest.raises(HomeAssistantError):
128+
await hass.services.async_call(
129+
"update",
130+
"install",
131+
{"entity_id": "update.adguard_home"},
132+
blocking=True,
133+
)
134+
135+
assert aioclient_mock.mock_calls[0][0] == "POST"
136+
assert (
137+
str(aioclient_mock.mock_calls[0][1]) == "https://127.0.0.1:3000/control/update"
138+
)

0 commit comments

Comments
 (0)