diff --git a/tests/components/adguard/conftest.py b/tests/components/adguard/conftest.py index 1430eb957d3..a2e24853696 100644 --- a/tests/components/adguard/conftest.py +++ b/tests/components/adguard/conftest.py @@ -2,7 +2,14 @@ from unittest.mock import AsyncMock -from adguardhome.update import AdGuardHomeAvailableUpdate +from adguardhome import AdGuardHome +from adguardhome.filtering import AdGuardHomeFiltering +from adguardhome.parental import AdGuardHomeParental +from adguardhome.querylog import AdGuardHomeQueryLog +from adguardhome.safebrowsing import AdGuardHomeSafeBrowsing +from adguardhome.safesearch import AdGuardHomeSafeSearch +from adguardhome.stats import AdGuardHomeStats +from adguardhome.update import AdGuardHomeAvailableUpdate, AdGuardHomeUpdate import pytest from homeassistant.components.adguard import DOMAIN @@ -38,7 +45,14 @@ def mock_config_entry() -> MockConfigEntry: @pytest.fixture async def mock_adguard() -> AsyncMock: """Fixture for setting up the component.""" - adguard_mock = AsyncMock() + adguard_mock = AsyncMock(spec=AdGuardHome) + adguard_mock.filtering = AsyncMock(spec=AdGuardHomeFiltering) + adguard_mock.parental = AsyncMock(spec=AdGuardHomeParental) + adguard_mock.querylog = AsyncMock(spec=AdGuardHomeQueryLog) + adguard_mock.safebrowsing = AsyncMock(spec=AdGuardHomeSafeBrowsing) + adguard_mock.safesearch = AsyncMock(spec=AdGuardHomeSafeSearch) + adguard_mock.stats = AsyncMock(spec=AdGuardHomeStats) + adguard_mock.update = AsyncMock(spec=AdGuardHomeUpdate) # static properties adguard_mock.host = "127.0.0.1" @@ -48,6 +62,10 @@ async def mock_adguard() -> AsyncMock: # async method mocks adguard_mock.version = AsyncMock(return_value="v0.107.50") + adguard_mock.protection_enabled = AsyncMock(return_value=True) + adguard_mock.parental.enabled = AsyncMock(return_value=True) + adguard_mock.safesearch.enabled = AsyncMock(return_value=True) + adguard_mock.safebrowsing.enabled = AsyncMock(return_value=True) adguard_mock.stats.dns_queries = AsyncMock(return_value=666) adguard_mock.stats.blocked_filtering = AsyncMock(return_value=1337) adguard_mock.stats.blocked_percentage = AsyncMock(return_value=200.75) @@ -56,11 +74,8 @@ async def mock_adguard() -> AsyncMock: adguard_mock.stats.replaced_safesearch = AsyncMock(return_value=18) adguard_mock.stats.avg_processing_time = AsyncMock(return_value=31.41) adguard_mock.filtering.rules_count = AsyncMock(return_value=100) - adguard_mock.filtering.add_url = AsyncMock() - adguard_mock.filtering.remove_url = AsyncMock() - adguard_mock.filtering.enable_url = AsyncMock() - adguard_mock.filtering.disable_url = AsyncMock() - adguard_mock.filtering.refresh = AsyncMock() + adguard_mock.filtering.enabled = AsyncMock(return_value=True) + adguard_mock.querylog.enabled = AsyncMock(return_value=True) adguard_mock.update.update_available = AsyncMock( return_value=AdGuardHomeAvailableUpdate( new_version="v0.107.59", @@ -70,6 +85,5 @@ async def mock_adguard() -> AsyncMock: disabled=False, ) ) - adguard_mock.update.begin_update = AsyncMock() return adguard_mock diff --git a/tests/components/adguard/snapshots/test_switch.ambr b/tests/components/adguard/snapshots/test_switch.ambr new file mode 100644 index 00000000000..b98165d7653 --- /dev/null +++ b/tests/components/adguard/snapshots/test_switch.ambr @@ -0,0 +1,289 @@ +# serializer version: 1 +# name: test_switch[switch.adguard_home_filtering-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.adguard_home_filtering', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Filtering', + 'platform': 'adguard', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'filtering', + 'unique_id': 'adguard_127.0.0.1_3000_switch_filtering', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[switch.adguard_home_filtering-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'AdGuard Home Filtering', + }), + 'context': , + 'entity_id': 'switch.adguard_home_filtering', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switch[switch.adguard_home_parental_control-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.adguard_home_parental_control', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Parental control', + 'platform': 'adguard', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'parental', + 'unique_id': 'adguard_127.0.0.1_3000_switch_parental', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[switch.adguard_home_parental_control-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'AdGuard Home Parental control', + }), + 'context': , + 'entity_id': 'switch.adguard_home_parental_control', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switch[switch.adguard_home_protection-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.adguard_home_protection', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Protection', + 'platform': 'adguard', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'protection', + 'unique_id': 'adguard_127.0.0.1_3000_switch_protection', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[switch.adguard_home_protection-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'AdGuard Home Protection', + }), + 'context': , + 'entity_id': 'switch.adguard_home_protection', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switch[switch.adguard_home_query_log-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.adguard_home_query_log', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Query log', + 'platform': 'adguard', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'query_log', + 'unique_id': 'adguard_127.0.0.1_3000_switch_querylog', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[switch.adguard_home_query_log-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'AdGuard Home Query log', + }), + 'context': , + 'entity_id': 'switch.adguard_home_query_log', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switch[switch.adguard_home_safe_browsing-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.adguard_home_safe_browsing', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Safe browsing', + 'platform': 'adguard', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'safe_browsing', + 'unique_id': 'adguard_127.0.0.1_3000_switch_safebrowsing', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[switch.adguard_home_safe_browsing-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'AdGuard Home Safe browsing', + }), + 'context': , + 'entity_id': 'switch.adguard_home_safe_browsing', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switch[switch.adguard_home_safe_search-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.adguard_home_safe_search', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Safe search', + 'platform': 'adguard', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'safe_search', + 'unique_id': 'adguard_127.0.0.1_3000_switch_safesearch', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[switch.adguard_home_safe_search-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'AdGuard Home Safe search', + }), + 'context': , + 'entity_id': 'switch.adguard_home_safe_search', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- diff --git a/tests/components/adguard/test_switch.py b/tests/components/adguard/test_switch.py new file mode 100644 index 00000000000..00014a6e5d8 --- /dev/null +++ b/tests/components/adguard/test_switch.py @@ -0,0 +1,161 @@ +"""Tests for the AdGuard Home switch entity.""" + +from collections.abc import Callable +import logging +from typing import Any +from unittest.mock import AsyncMock, patch + +from adguardhome import AdGuardHomeError +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.switch import SERVICE_TURN_OFF, SERVICE_TURN_ON +from homeassistant.const import ATTR_ENTITY_ID, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import setup_integration + +from tests.common import MockConfigEntry, snapshot_platform + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_switch( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, + mock_adguard: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the adguard switch platform.""" + with patch("homeassistant.components.adguard.PLATFORMS", [Platform.SWITCH]): + await setup_integration(hass, mock_config_entry, mock_adguard) + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +@pytest.mark.parametrize( + ("switch_name", "service", "call_assertion"), + [ + ( + "protection", + SERVICE_TURN_ON, + lambda mock: mock.enable_protection.assert_called_once(), + ), + ( + "protection", + SERVICE_TURN_OFF, + lambda mock: mock.disable_protection.assert_called_once(), + ), + ( + "parental_control", + SERVICE_TURN_ON, + lambda mock: mock.parental.enable.assert_called_once(), + ), + ( + "parental_control", + SERVICE_TURN_OFF, + lambda mock: mock.parental.disable.assert_called_once(), + ), + ( + "safe_search", + SERVICE_TURN_ON, + lambda mock: mock.safesearch.enable.assert_called_once(), + ), + ( + "safe_search", + SERVICE_TURN_OFF, + lambda mock: mock.safesearch.disable.assert_called_once(), + ), + ( + "safe_browsing", + SERVICE_TURN_ON, + lambda mock: mock.safebrowsing.enable.assert_called_once(), + ), + ( + "safe_browsing", + SERVICE_TURN_OFF, + lambda mock: mock.safebrowsing.disable.assert_called_once(), + ), + ( + "filtering", + SERVICE_TURN_ON, + lambda mock: mock.filtering.enable.assert_called_once(), + ), + ( + "filtering", + SERVICE_TURN_OFF, + lambda mock: mock.filtering.disable.assert_called_once(), + ), + ( + "query_log", + SERVICE_TURN_ON, + lambda mock: mock.querylog.enable.assert_called_once(), + ), + ( + "query_log", + SERVICE_TURN_OFF, + lambda mock: mock.querylog.disable.assert_called_once(), + ), + ], +) +async def test_switch_actions( + hass: HomeAssistant, + mock_adguard: AsyncMock, + mock_config_entry: MockConfigEntry, + switch_name: str, + service: str, + call_assertion: Callable[[AsyncMock], Any], +) -> None: + """Test the adguard switch actions.""" + with patch("homeassistant.components.adguard.PLATFORMS", [Platform.SWITCH]): + await setup_integration(hass, mock_config_entry, mock_adguard) + + await hass.services.async_call( + "switch", + service, + {ATTR_ENTITY_ID: f"switch.adguard_home_{switch_name}"}, + blocking=True, + ) + + call_assertion(mock_adguard) + + +@pytest.mark.parametrize( + ("service", "expected_message"), + [ + ( + SERVICE_TURN_ON, + "An error occurred while turning on AdGuard Home switch", + ), + ( + SERVICE_TURN_OFF, + "An error occurred while turning off AdGuard Home switch", + ), + ], +) +async def test_switch_action_failed( + hass: HomeAssistant, + mock_adguard: AsyncMock, + mock_config_entry: MockConfigEntry, + caplog: pytest.LogCaptureFixture, + service: str, + expected_message: str, +) -> None: + """Test the adguard switch actions.""" + caplog.set_level(logging.ERROR) + + with patch("homeassistant.components.adguard.PLATFORMS", [Platform.SWITCH]): + await setup_integration(hass, mock_config_entry, mock_adguard) + + mock_adguard.enable_protection.side_effect = AdGuardHomeError("Boom") + mock_adguard.disable_protection.side_effect = AdGuardHomeError("Boom") + + await hass.services.async_call( + "switch", + service, + {ATTR_ENTITY_ID: "switch.adguard_home_protection"}, + blocking=True, + ) + assert expected_message in caplog.text