Revert "fix: Complete fixes: tests, workflows, coverage"

This reverts commit ed94d40e96.
This commit is contained in:
2025-10-02 12:47:35 +01:00
parent 089e046fd9
commit f4bea6863f
17 changed files with 1756 additions and 1081 deletions

View File

@@ -1,16 +1,17 @@
"""AdGuard Control Hub switch platform."""
"""Switch platform for AdGuard Control Hub integration."""
import logging
from typing import Any, Dict, List, Optional
from typing import Any, Optional
from homeassistant.components.switch import SwitchEntity
from homeassistant.components.switch import SwitchEntity, SwitchDeviceClass
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.helpers.entity import DeviceInfo
from .api import AdGuardHomeAPI, AdGuardConnectionError
from .const import DOMAIN, MANUFACTURER
from . import AdGuardControlHubCoordinator
from .api import AdGuardHomeAPI, AdGuardHomeError
from .const import DOMAIN, ICON_PROTECTION, ICON_PROTECTION_OFF, ICON_CLIENT, MANUFACTURER
_LOGGER = logging.getLogger(__name__)
@@ -24,122 +25,189 @@ async def async_setup_entry(
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
api = hass.data[DOMAIN][config_entry.entry_id]["api"]
entities: List[SwitchEntity] = []
entities = [AdGuardProtectionSwitch(coordinator, api)]
# Add main protection switch
entities.append(AdGuardProtectionSwitch(coordinator, api))
# Add client switches
for client_name in coordinator.clients:
# Add client switches if clients exist
for client_name in coordinator.clients.keys():
entities.append(AdGuardClientSwitch(coordinator, api, client_name))
async_add_entities(entities)
async_add_entities(entities, update_before_add=True)
class AdGuardProtectionSwitch(CoordinatorEntity, SwitchEntity):
"""AdGuard Home protection switch."""
class AdGuardBaseSwitch(CoordinatorEntity, SwitchEntity):
"""Base class for AdGuard switches."""
def __init__(self, coordinator, api: AdGuardHomeAPI) -> None:
def __init__(self, coordinator: AdGuardControlHubCoordinator, api: AdGuardHomeAPI) -> None:
"""Initialize the switch."""
super().__init__(coordinator)
self.api = api
self._attr_device_info = {
"identifiers": {(DOMAIN, f"{api.host}:{api.port}")},
"name": f"AdGuard Control Hub ({api.host})",
"manufacturer": MANUFACTURER,
"model": "AdGuard Home",
"configuration_url": f"{'https' if api.ssl else 'http'}://{api.host}:{api.port}",
}
@property
def available(self) -> bool:
"""Return if switch is available."""
return self.coordinator.last_update_success
class AdGuardProtectionSwitch(AdGuardBaseSwitch):
"""Switch to control global AdGuard protection."""
def __init__(self, coordinator: AdGuardControlHubCoordinator, api: AdGuardHomeAPI) -> None:
"""Initialize the switch."""
super().__init__(coordinator, api)
self._attr_unique_id = f"{api.host}_{api.port}_protection"
self._attr_name = "AdGuard Protection"
self._attr_unique_id = f"{DOMAIN}_protection"
self._attr_device_class = SwitchDeviceClass.SWITCH
self._attr_entity_category = EntityCategory.CONFIG
@property
def device_info(self) -> DeviceInfo:
"""Return device info."""
return DeviceInfo(
identifiers={(DOMAIN, "adguard_home")},
name="AdGuard Home",
manufacturer=MANUFACTURER, # FIXED: Now uses imported MANUFACTURER
model="AdGuard Home",
configuration_url=self.api.base_url,
)
@property
def is_on(self) -> bool:
def is_on(self) -> Optional[bool]:
"""Return true if protection is enabled."""
return self.coordinator.protection_status.get("protection_enabled", False)
@property
def icon(self) -> str:
"""Return the icon."""
return "mdi:shield-check" if self.is_on else "mdi:shield-off"
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on protection."""
try:
await self.api.set_protection(True)
await self.coordinator.async_request_refresh()
except AdGuardConnectionError as err:
_LOGGER.error("Failed to turn on protection: %s", err)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off protection."""
try:
await self.api.set_protection(False)
await self.coordinator.async_request_refresh()
except AdGuardConnectionError as err:
_LOGGER.error("Failed to turn off protection: %s", err)
class AdGuardClientSwitch(CoordinatorEntity, SwitchEntity):
"""AdGuard Home client switch."""
def __init__(self, coordinator, api: AdGuardHomeAPI, client_name: str) -> None:
"""Initialize the client switch."""
super().__init__(coordinator)
self.api = api
self._client_name = client_name
self._attr_name = f"AdGuard {client_name}"
self._attr_unique_id = f"{DOMAIN}_{client_name.lower().replace(' ', '_')}"
@property
def device_info(self) -> DeviceInfo:
"""Return device info."""
return DeviceInfo(
identifiers={(DOMAIN, f"client_{self._client_name}")},
name=f"AdGuard Client: {self._client_name}",
manufacturer=MANUFACTURER,
model="AdGuard Client",
via_device=(DOMAIN, "adguard_home"),
)
@property
def is_on(self) -> bool:
"""Return true if client filtering is enabled."""
client = self.coordinator.clients.get(self._client_name, {})
return not client.get("filtering_enabled", True) is False
@property
def icon(self) -> str:
"""Return the icon."""
return "mdi:devices" if self.is_on else "mdi:devices-off"
"""Return the icon for the switch."""
return ICON_PROTECTION if self.is_on else ICON_PROTECTION_OFF
@property
def available(self) -> bool:
"""Return if entity is available."""
return self._client_name in self.coordinator.clients
"""Return if switch is available."""
return self.coordinator.last_update_success and bool(self.coordinator.protection_status)
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return additional state attributes."""
status = self.coordinator.protection_status
return {
"dns_port": status.get("dns_port", "N/A"),
"version": status.get("version", "N/A"),
"running": status.get("running", False),
"dns_addresses": status.get("dns_addresses", []),
}
async def async_turn_on(self, **kwargs: Any) -> None:
"""Enable filtering for client."""
"""Turn on AdGuard protection."""
try:
client = await self.api.get_client_by_name(self._client_name)
if client:
client["filtering_enabled"] = True
await self.api._request("POST", "/control/clients/update", json=client)
await self.coordinator.async_request_refresh()
except AdGuardConnectionError as err:
_LOGGER.error("Failed to enable filtering for %s: %s", self._client_name, err)
await self.api.set_protection(True)
await self.coordinator.async_request_refresh()
_LOGGER.info("AdGuard protection enabled")
except AdGuardHomeError as err:
_LOGGER.error("Failed to enable AdGuard protection: %s", err)
raise
except Exception as err:
_LOGGER.exception("Unexpected error enabling AdGuard protection")
raise
async def async_turn_off(self, **kwargs: Any) -> None:
"""Disable filtering for client."""
"""Turn off AdGuard protection."""
try:
client = await self.api.get_client_by_name(self._client_name)
await self.api.set_protection(False)
await self.coordinator.async_request_refresh()
_LOGGER.warning("AdGuard protection disabled")
except AdGuardHomeError as err:
_LOGGER.error("Failed to disable AdGuard protection: %s", err)
raise
except Exception as err:
_LOGGER.exception("Unexpected error disabling AdGuard protection")
raise
class AdGuardClientSwitch(AdGuardBaseSwitch):
"""Switch to control client-specific protection."""
def __init__(
self,
coordinator: AdGuardControlHubCoordinator,
api: AdGuardHomeAPI,
client_name: str,
) -> None:
"""Initialize the switch."""
super().__init__(coordinator, api)
self.client_name = client_name
self._attr_unique_id = f"{api.host}_{api.port}_client_{client_name}"
self._attr_name = f"AdGuard {client_name}"
self._attr_icon = ICON_CLIENT
self._attr_device_class = SwitchDeviceClass.SWITCH
self._attr_entity_category = EntityCategory.CONFIG
@property
def is_on(self) -> Optional[bool]:
"""Return true if client protection is enabled."""
client = self.coordinator.clients.get(self.client_name, {})
return client.get("filtering_enabled", True)
@property
def available(self) -> bool:
"""Return if switch is available."""
return (
self.coordinator.last_update_success
and self.client_name in self.coordinator.clients
)
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return additional state attributes."""
client = self.coordinator.clients.get(self.client_name, {})
blocked_services = client.get("blocked_services", {})
if isinstance(blocked_services, dict):
blocked_list = blocked_services.get("ids", [])
else:
blocked_list = blocked_services or []
return {
"client_ids": client.get("ids", []),
"safebrowsing_enabled": client.get("safebrowsing_enabled", False),
"parental_enabled": client.get("parental_enabled", False),
"safesearch_enabled": client.get("safesearch_enabled", False),
"blocked_services_count": len(blocked_list),
"blocked_services": blocked_list,
}
async def async_turn_on(self, **kwargs: Any) -> None:
"""Enable protection for this client."""
try:
client = await self.api.get_client_by_name(self.client_name)
if client:
client["filtering_enabled"] = False
await self.api._request("POST", "/control/clients/update", json=client)
update_data = {
"name": self.client_name,
"data": {**client, "filtering_enabled": True}
}
await self.api.update_client(update_data)
await self.coordinator.async_request_refresh()
except AdGuardConnectionError as err:
_LOGGER.error("Failed to disable filtering for %s: %s", self._client_name, err)
_LOGGER.info("Enabled protection for client: %s", self.client_name)
else:
_LOGGER.error("Client not found: %s", self.client_name)
except AdGuardHomeError as err:
_LOGGER.error("Failed to enable protection for %s: %s", self.client_name, err)
raise
except Exception as err:
_LOGGER.exception("Unexpected error enabling protection for %s", self.client_name)
raise
async def async_turn_off(self, **kwargs: Any) -> None:
"""Disable protection for this client."""
try:
client = await self.api.get_client_by_name(self.client_name)
if client:
update_data = {
"name": self.client_name,
"data": {**client, "filtering_enabled": False}
}
await self.api.update_client(update_data)
await self.coordinator.async_request_refresh()
_LOGGER.info("Disabled protection for client: %s", self.client_name)
else:
_LOGGER.error("Client not found: %s", self.client_name)
except AdGuardHomeError as err:
_LOGGER.error("Failed to disable protection for %s: %s", self.client_name, err)
raise
except Exception as err:
_LOGGER.exception("Unexpected error disabling protection for %s", self.client_name)
raise