Revert "fix: Complete fixes: tests, workflows, coverage"
This reverts commit ed94d40e96
.
This commit is contained in:
@@ -46,10 +46,46 @@ class TestIntegrationSetup:
|
||||
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_setup_entry_api_error(self, mock_hass, mock_config_entry):
|
||||
"""Test setup failure due to API error."""
|
||||
mock_api = MagicMock()
|
||||
mock_api.test_connection = AsyncMock(side_effect=AdGuardAuthError("Auth failed"))
|
||||
|
||||
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"):
|
||||
await async_setup_entry(mock_hass, mock_config_entry)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_setup_entry_coordinator_failure(self, mock_hass, mock_config_entry, mock_api):
|
||||
"""Test setup failure due to coordinator refresh error."""
|
||||
with patch("custom_components.adguard_hub.AdGuardHomeAPI", return_value=mock_api), patch("custom_components.adguard_hub.async_get_clientsession"), patch.object(AdGuardControlHubCoordinator, "async_config_entry_first_refresh",
|
||||
side_effect=UpdateFailed("Refresh failed")):
|
||||
|
||||
with pytest.raises(ConfigEntryNotReady, match="Failed to fetch initial data"):
|
||||
await async_setup_entry(mock_hass, mock_config_entry)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_setup_entry_platform_failure(self, mock_hass, mock_config_entry, mock_api):
|
||||
"""Test setup failure due to platform setup error."""
|
||||
mock_hass.config_entries.async_forward_entry_setups = AsyncMock(
|
||||
side_effect=Exception("Platform setup failed")
|
||||
)
|
||||
|
||||
with patch("custom_components.adguard_hub.AdGuardHomeAPI", return_value=mock_api), patch("custom_components.adguard_hub.async_get_clientsession"), patch.object(AdGuardControlHubCoordinator, "async_config_entry_first_refresh",
|
||||
new=AsyncMock()):
|
||||
|
||||
with pytest.raises(ConfigEntryNotReady, match="Failed to set up platforms"):
|
||||
await async_setup_entry(mock_hass, mock_config_entry)
|
||||
|
||||
# Verify cleanup
|
||||
assert mock_config_entry.entry_id not in mock_hass.data.get(DOMAIN, {})
|
||||
|
||||
@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
|
||||
# Set up initial data
|
||||
mock_hass.data[DOMAIN] = {
|
||||
mock_config_entry.entry_id: {
|
||||
"coordinator": MagicMock(),
|
||||
@@ -60,34 +96,40 @@ class TestIntegrationSetup:
|
||||
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"))
|
||||
async def test_unload_entry_last_instance(self, mock_hass, mock_config_entry):
|
||||
"""Test unloading last config entry unregisters services."""
|
||||
# Set up services
|
||||
mock_services = MagicMock()
|
||||
mock_services.unregister_services = MagicMock()
|
||||
mock_hass.data[f"{DOMAIN}_services"] = mock_services
|
||||
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
|
||||
assert f"{DOMAIN}_services" not in mock_hass.data
|
||||
assert DOMAIN not in mock_hass.data
|
||||
mock_services.unregister_services.assert_called_once()
|
||||
|
||||
|
||||
class TestCoordinator:
|
||||
"""Test the data update coordinator."""
|
||||
|
||||
def test_coordinator_initialization(self, mock_hass, mock_api):
|
||||
"""Test coordinator initialization."""
|
||||
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()
|
||||
assert coordinator.api == mock_api
|
||||
assert coordinator.name == f"{DOMAIN}_coordinator"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_coordinator_update_success(self, mock_hass, mock_api):
|
||||
@@ -103,6 +145,46 @@ class TestIntegrationSetup:
|
||||
assert data["statistics"]["num_dns_queries"] == 10000
|
||||
assert data["status"]["protection_enabled"] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_coordinator_update_partial_failure(self, mock_hass, mock_api):
|
||||
"""Test coordinator update with partial API failures."""
|
||||
# Make one API call fail
|
||||
mock_api.get_clients = AsyncMock(side_effect=Exception("Client fetch failed"))
|
||||
|
||||
coordinator = AdGuardControlHubCoordinator(mock_hass, mock_api)
|
||||
|
||||
data = await coordinator._async_update_data()
|
||||
|
||||
# Should still return data from successful calls
|
||||
assert "clients" in data
|
||||
assert "statistics" in data
|
||||
assert "status" in data
|
||||
assert data["statistics"]["num_dns_queries"] == 10000
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_coordinator_update_connection_error(self, mock_hass, mock_api):
|
||||
"""Test coordinator update with connection error."""
|
||||
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)
|
||||
|
||||
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."""
|
||||
mock_api.get_status = AsyncMock(side_effect=Exception("Unexpected error"))
|
||||
mock_api.get_clients = AsyncMock(side_effect=Exception("Unexpected error"))
|
||||
mock_api.get_statistics = AsyncMock(side_effect=Exception("Unexpected error"))
|
||||
|
||||
coordinator = AdGuardControlHubCoordinator(mock_hass, mock_api)
|
||||
|
||||
with pytest.raises(UpdateFailed, match="Error communicating with AdGuard Control Hub"):
|
||||
await coordinator._async_update_data()
|
||||
|
||||
def test_coordinator_properties(self, mock_hass, mock_api):
|
||||
"""Test coordinator properties."""
|
||||
coordinator = AdGuardControlHubCoordinator(mock_hass, mock_api)
|
||||
@@ -120,63 +202,182 @@ class TestIntegrationSetup:
|
||||
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
|
||||
def test_coordinator_properties_empty_data(self, mock_hass, mock_api):
|
||||
"""Test coordinator properties with empty data."""
|
||||
coordinator = AdGuardControlHubCoordinator(mock_hass, mock_api)
|
||||
|
||||
mock_hass.data[DOMAIN] = {
|
||||
mock_config_entry.entry_id: {
|
||||
"coordinator": mock_coordinator,
|
||||
"api": mock_api
|
||||
}
|
||||
}
|
||||
# Properties should return empty containers, not None
|
||||
assert coordinator.clients == {}
|
||||
assert coordinator.statistics == {}
|
||||
assert coordinator.protection_status == {}
|
||||
|
||||
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
|
||||
class TestServices:
|
||||
"""Test service functionality."""
|
||||
|
||||
def test_services_registration(self, mock_hass):
|
||||
"""Test that services are properly registered."""
|
||||
from custom_components.adguard_hub.services import AdGuardControlHubServices
|
||||
|
||||
services = AdGuardControlHubServices(mock_hass)
|
||||
services.register_services()
|
||||
|
||||
# Verify services registration was called
|
||||
assert mock_hass.services.register.called
|
||||
|
||||
# Verify correct number of service registrations
|
||||
expected_call_count = 6 # block_services, unblock_services, emergency_unblock, add_client, remove_client, refresh_data
|
||||
assert mock_hass.services.register.call_count == expected_call_count
|
||||
|
||||
def test_services_unregistration(self, mock_hass):
|
||||
"""Test that services are properly unregistered."""
|
||||
from custom_components.adguard_hub.services import AdGuardControlHubServices
|
||||
|
||||
# Mock service existence
|
||||
mock_hass.services.has_service.return_value = True
|
||||
|
||||
services = AdGuardControlHubServices(mock_hass)
|
||||
services.unregister_services()
|
||||
|
||||
# Verify correct number of service removals
|
||||
expected_call_count = 6
|
||||
assert mock_hass.services.remove.call_count == expected_call_count
|
||||
|
||||
@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
|
||||
async def test_block_services_success(self, mock_hass, mock_api):
|
||||
"""Test successful service blocking."""
|
||||
from custom_components.adguard_hub.services import AdGuardControlHubServices
|
||||
|
||||
mock_hass.data[DOMAIN] = {
|
||||
mock_config_entry.entry_id: {
|
||||
"coordinator": mock_coordinator,
|
||||
"api": mock_api
|
||||
}
|
||||
"entry_id": {"api": mock_api}
|
||||
}
|
||||
|
||||
mock_add_entities = MagicMock()
|
||||
await async_setup_entry(mock_hass, mock_config_entry, mock_add_entities)
|
||||
services = AdGuardControlHubServices(mock_hass)
|
||||
call = MagicMock()
|
||||
call.data = {
|
||||
"client_name": "test_client",
|
||||
"services": ["youtube", "netflix"]
|
||||
}
|
||||
|
||||
# Should add multiple sensors
|
||||
assert mock_add_entities.called
|
||||
entities = mock_add_entities.call_args[0][0]
|
||||
assert len(entities) >= 6 # Multiple sensors
|
||||
await services.block_services(call)
|
||||
|
||||
mock_api.get_client_by_name.assert_called_once_with("test_client")
|
||||
mock_api.update_client_blocked_services.assert_called_once()
|
||||
|
||||
@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
|
||||
async def test_unblock_services_success(self, mock_hass, mock_api):
|
||||
"""Test successful service unblocking."""
|
||||
from custom_components.adguard_hub.services import AdGuardControlHubServices
|
||||
|
||||
mock_hass.data[DOMAIN] = {
|
||||
mock_config_entry.entry_id: {
|
||||
"coordinator": mock_coordinator,
|
||||
"api": mock_api
|
||||
}
|
||||
"entry_id": {"api": mock_api}
|
||||
}
|
||||
|
||||
mock_add_entities = MagicMock()
|
||||
await async_setup_entry(mock_hass, mock_config_entry, mock_add_entities)
|
||||
services = AdGuardControlHubServices(mock_hass)
|
||||
call = MagicMock()
|
||||
call.data = {
|
||||
"client_name": "test_client",
|
||||
"services": ["youtube"]
|
||||
}
|
||||
|
||||
# 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
|
||||
await services.unblock_services(call)
|
||||
|
||||
mock_api.get_client_by_name.assert_called_once_with("test_client")
|
||||
mock_api.update_client_blocked_services.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_emergency_unblock_global(self, mock_hass, mock_api):
|
||||
"""Test emergency unblock for all clients."""
|
||||
from custom_components.adguard_hub.services import AdGuardControlHubServices
|
||||
|
||||
mock_hass.data[DOMAIN] = {
|
||||
"entry_id": {"api": mock_api}
|
||||
}
|
||||
|
||||
services = AdGuardControlHubServices(mock_hass)
|
||||
call = MagicMock()
|
||||
call.data = {
|
||||
"duration": 300,
|
||||
"clients": ["all"]
|
||||
}
|
||||
|
||||
await services.emergency_unblock(call)
|
||||
|
||||
mock_api.set_protection.assert_called_once_with(False)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refresh_data_success(self, mock_hass, mock_coordinator):
|
||||
"""Test successful data refresh."""
|
||||
from custom_components.adguard_hub.services import AdGuardControlHubServices
|
||||
|
||||
mock_hass.data[DOMAIN] = {
|
||||
"entry_id": {"coordinator": mock_coordinator}
|
||||
}
|
||||
|
||||
services = AdGuardControlHubServices(mock_hass)
|
||||
call = MagicMock()
|
||||
call.data = {}
|
||||
|
||||
await services.refresh_data(call)
|
||||
|
||||
mock_coordinator.async_request_refresh.assert_called_once()
|
||||
|
||||
|
||||
class TestConstants:
|
||||
"""Test constant definitions."""
|
||||
|
||||
def test_blocked_services_constants(self):
|
||||
"""Test that blocked services are properly defined."""
|
||||
from custom_components.adguard_hub.const import BLOCKED_SERVICES
|
||||
|
||||
required_services = ["youtube", "netflix", "gaming", "facebook"]
|
||||
|
||||
for service in required_services:
|
||||
assert service in BLOCKED_SERVICES
|
||||
assert isinstance(BLOCKED_SERVICES[service], str)
|
||||
assert len(BLOCKED_SERVICES[service]) > 0
|
||||
|
||||
def test_api_endpoints_constants(self):
|
||||
"""Test that API endpoints are properly defined."""
|
||||
from custom_components.adguard_hub.const import API_ENDPOINTS
|
||||
|
||||
required_endpoints = [
|
||||
"status", "clients", "stats", "protection",
|
||||
"clients_add", "clients_update", "clients_delete"
|
||||
]
|
||||
|
||||
for endpoint in required_endpoints:
|
||||
assert endpoint in API_ENDPOINTS
|
||||
assert API_ENDPOINTS[endpoint].startswith("/")
|
||||
|
||||
def test_platform_constants(self):
|
||||
"""Test platform constants."""
|
||||
from custom_components.adguard_hub.const import PLATFORMS
|
||||
|
||||
expected_platforms = ["switch", "binary_sensor", "sensor"]
|
||||
assert PLATFORMS == expected_platforms
|
||||
|
||||
def test_service_constants(self):
|
||||
"""Test service name constants."""
|
||||
from custom_components.adguard_hub.const import (
|
||||
SERVICE_BLOCK_SERVICES,
|
||||
SERVICE_UNBLOCK_SERVICES,
|
||||
SERVICE_EMERGENCY_UNBLOCK,
|
||||
SERVICE_ADD_CLIENT,
|
||||
SERVICE_REMOVE_CLIENT,
|
||||
SERVICE_REFRESH_DATA,
|
||||
)
|
||||
|
||||
services = [
|
||||
SERVICE_BLOCK_SERVICES,
|
||||
SERVICE_UNBLOCK_SERVICES,
|
||||
SERVICE_EMERGENCY_UNBLOCK,
|
||||
SERVICE_ADD_CLIENT,
|
||||
SERVICE_REMOVE_CLIENT,
|
||||
SERVICE_REFRESH_DATA,
|
||||
]
|
||||
|
||||
for service in services:
|
||||
assert isinstance(service, str)
|
||||
assert len(service) > 0
|
||||
assert "_" in service # Snake case format
|
||||
|
Reference in New Issue
Block a user