Some checks failed
Code Quality Check / Code Formatting (push) Failing after 21s
Code Quality Check / Security Analysis (push) Failing after 20s
Integration Testing / Integration Tests (2024.12.0, 3.13) (push) Failing after 1m32s
Integration Testing / Integration Tests (2025.9.4, 3.13) (push) Failing after 20s
Signed-off-by: Rafal Zielinski <sq4ind@gmail.com>
183 lines
7.9 KiB
Python
183 lines
7.9 KiB
Python
"""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
|