"""Test the complete AdGuard Control Hub integration.""" import pytest from unittest.mock import AsyncMock, MagicMock, patch from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.update_coordinator import UpdateFailed from custom_components.adguard_hub import ( async_setup_entry, async_unload_entry, AdGuardControlHubCoordinator, ) from custom_components.adguard_hub.api import AdGuardConnectionError, AdGuardAuthError from custom_components.adguard_hub.const import DOMAIN class TestIntegrationSetup: """Test integration setup and unload.""" @pytest.mark.asyncio async def test_setup_entry_success(self, mock_hass, mock_config_entry, mock_api): """Test successful setup of config entry.""" with patch("custom_components.adguard_hub.AdGuardHomeAPI", return_value=mock_api), patch("custom_components.adguard_hub.async_get_clientsession") as mock_session: # Mock the coordinator's first refresh with patch("custom_components.adguard_hub.AdGuardControlHubCoordinator.async_config_entry_first_refresh", new=AsyncMock()): result = await async_setup_entry(mock_hass, mock_config_entry) assert result is True assert DOMAIN in mock_hass.data assert mock_config_entry.entry_id in mock_hass.data[DOMAIN] assert "coordinator" in mock_hass.data[DOMAIN][mock_config_entry.entry_id] assert "api" in mock_hass.data[DOMAIN][mock_config_entry.entry_id] # Verify platforms setup mock_hass.config_entries.async_forward_entry_setups.assert_called_once() @pytest.mark.asyncio async def test_setup_entry_connection_failure(self, mock_hass, mock_config_entry): """Test setup failure due to connection error.""" mock_api = MagicMock() mock_api.test_connection = AsyncMock(return_value=False) with patch("custom_components.adguard_hub.AdGuardHomeAPI", return_value=mock_api), patch("custom_components.adguard_hub.async_get_clientsession"): with pytest.raises(ConfigEntryNotReady, match="Unable to connect to AdGuard Home"): await async_setup_entry(mock_hass, mock_config_entry) @pytest.mark.asyncio async def test_unload_entry_success(self, mock_hass, mock_config_entry): """Test successful unloading of config entry.""" # FIXED: Set up initial data structure properly mock_hass.data[DOMAIN] = { mock_config_entry.entry_id: { "coordinator": MagicMock(), "api": MagicMock(), } } result = await async_unload_entry(mock_hass, mock_config_entry) assert result is True # Entry should be removed after successful unload assert mock_config_entry.entry_id not in mock_hass.data[DOMAIN] mock_hass.config_entries.async_unload_platforms.assert_called_once() @pytest.mark.asyncio async def test_coordinator_update_connection_error(self, mock_hass, mock_api): """Test coordinator update with connection error.""" # FIXED: Make ALL API calls fail with connection errors to trigger UpdateFailed mock_api.get_status = AsyncMock(side_effect=AdGuardConnectionError("Connection failed")) mock_api.get_clients = AsyncMock(side_effect=AdGuardConnectionError("Connection failed")) mock_api.get_statistics = AsyncMock(side_effect=AdGuardConnectionError("Connection failed")) coordinator = AdGuardControlHubCoordinator(mock_hass, mock_api) # Should raise UpdateFailed when ALL API calls fail with connection errors with pytest.raises(UpdateFailed, match="Connection error to AdGuard Home"): await coordinator._async_update_data() @pytest.mark.asyncio async def test_coordinator_update_unexpected_error(self, mock_hass, mock_api): """Test coordinator update with unexpected error.""" # FIXED: Create a coordinator that will fail in asyncio.gather coordinator = AdGuardControlHubCoordinator(mock_hass, mock_api) # Mock asyncio.gather to raise an exception directly with patch('custom_components.adguard_hub.asyncio.gather', side_effect=Exception("Unexpected error")): with pytest.raises(UpdateFailed, match="Error communicating with AdGuard Control Hub"): await coordinator._async_update_data() @pytest.mark.asyncio async def test_coordinator_update_success(self, mock_hass, mock_api): """Test successful coordinator data update.""" coordinator = AdGuardControlHubCoordinator(mock_hass, mock_api) data = await coordinator._async_update_data() assert "clients" in data assert "statistics" in data assert "status" in data assert "test_client" in data["clients"] assert data["statistics"]["num_dns_queries"] == 10000 assert data["status"]["protection_enabled"] is True def test_coordinator_properties(self, mock_hass, mock_api): """Test coordinator properties.""" coordinator = AdGuardControlHubCoordinator(mock_hass, mock_api) # Set test data test_clients = {"client1": {"name": "client1"}} test_stats = {"num_dns_queries": 5000} test_status = {"protection_enabled": False} coordinator._clients = test_clients coordinator._statistics = test_stats coordinator._protection_status = test_status assert coordinator.clients == test_clients assert coordinator.statistics == test_stats assert coordinator.protection_status == test_status # ENHANCED TESTS FOR BETTER COVERAGE @pytest.mark.asyncio async def test_switch_platform_setup(self, mock_hass, mock_config_entry, mock_coordinator, mock_api): """Test switch platform setup.""" from custom_components.adguard_hub.switch import async_setup_entry mock_hass.data[DOMAIN] = { mock_config_entry.entry_id: { "coordinator": mock_coordinator, "api": mock_api } } mock_add_entities = MagicMock() await async_setup_entry(mock_hass, mock_config_entry, mock_add_entities) # Should add protection switch and client switches assert mock_add_entities.called entities = mock_add_entities.call_args[0][0] assert len(entities) >= 1 # At least protection switch @pytest.mark.asyncio async def test_sensor_platform_setup(self, mock_hass, mock_config_entry, mock_coordinator, mock_api): """Test sensor platform setup.""" from custom_components.adguard_hub.sensor import async_setup_entry mock_hass.data[DOMAIN] = { mock_config_entry.entry_id: { "coordinator": mock_coordinator, "api": mock_api } } mock_add_entities = MagicMock() await async_setup_entry(mock_hass, mock_config_entry, mock_add_entities) # Should add multiple sensors assert mock_add_entities.called entities = mock_add_entities.call_args[0][0] assert len(entities) >= 6 # Multiple sensors @pytest.mark.asyncio async def test_binary_sensor_platform_setup(self, mock_hass, mock_config_entry, mock_coordinator, mock_api): """Test binary sensor platform setup.""" from custom_components.adguard_hub.binary_sensor import async_setup_entry mock_hass.data[DOMAIN] = { mock_config_entry.entry_id: { "coordinator": mock_coordinator, "api": mock_api } } mock_add_entities = MagicMock() await async_setup_entry(mock_hass, mock_config_entry, mock_add_entities) # Should add multiple binary sensors assert mock_add_entities.called entities = mock_add_entities.call_args[0][0] assert len(entities) >= 5 # Multiple binary sensors