mirror of
https://github.com/home-assistant/core.git
synced 2026-04-02 00:20:30 +01:00
Test coverage for the Actron Air integration (#164446)
This commit is contained in:
@@ -120,7 +120,7 @@ class ActronAirConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return self.async_show_form(
|
||||
step_id="timeout",
|
||||
)
|
||||
del self.login_task
|
||||
self.login_task = None
|
||||
return await self.async_step_user()
|
||||
|
||||
async def async_step_reauth(
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/actron_air",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "bronze",
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["actron-neo-api==0.4.1"]
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ rules:
|
||||
log-when-unavailable: done
|
||||
parallel-updates: done
|
||||
reauthentication-flow: done
|
||||
test-coverage: todo
|
||||
test-coverage: done
|
||||
|
||||
# Gold
|
||||
devices: done
|
||||
|
||||
@@ -258,3 +258,61 @@ async def test_zone_set_hvac_mode_api_error(
|
||||
{ATTR_ENTITY_ID: "climate.living_room", ATTR_HVAC_MODE: HVACMode.OFF},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_system_hvac_mode_unmapped(
|
||||
hass: HomeAssistant,
|
||||
mock_actron_api: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test system climate entity returns None for unmapped HVAC mode."""
|
||||
status = mock_actron_api.state_manager.get_status.return_value
|
||||
status.user_aircon_settings.is_on = True
|
||||
status.user_aircon_settings.mode = "UNKNOWN_MODE"
|
||||
|
||||
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get("climate.test_system")
|
||||
assert state.state == "unknown"
|
||||
|
||||
|
||||
async def test_zone_hvac_mode_unmapped(
|
||||
hass: HomeAssistant,
|
||||
mock_actron_api: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_zone: MagicMock,
|
||||
) -> None:
|
||||
"""Test zone climate entity returns None for unmapped HVAC mode."""
|
||||
mock_zone.is_active = True
|
||||
mock_zone.hvac_mode = "UNKNOWN_MODE"
|
||||
|
||||
status = mock_actron_api.state_manager.get_status.return_value
|
||||
status.remote_zone_info = [mock_zone]
|
||||
status.zones = {1: mock_zone}
|
||||
|
||||
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get("climate.living_room")
|
||||
assert state.state == "unknown"
|
||||
|
||||
|
||||
async def test_zone_hvac_mode_inactive(
|
||||
hass: HomeAssistant,
|
||||
mock_actron_api: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_zone: MagicMock,
|
||||
) -> None:
|
||||
"""Test zone climate entity returns OFF when zone is inactive."""
|
||||
mock_zone.is_active = False
|
||||
|
||||
status = mock_actron_api.state_manager.get_status.return_value
|
||||
status.remote_zone_info = [mock_zone]
|
||||
status.zones = {1: mock_zone}
|
||||
|
||||
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get("climate.living_room")
|
||||
assert state.state == "off"
|
||||
|
||||
@@ -252,3 +252,84 @@ async def test_reauth_flow_wrong_account(
|
||||
# Should abort because of wrong account
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "wrong_account"
|
||||
|
||||
|
||||
async def test_user_flow_timeout(
|
||||
hass: HomeAssistant, mock_actron_api: AsyncMock, mock_setup_entry: AsyncMock
|
||||
) -> None:
|
||||
"""Test OAuth2 flow when login task raises a non-CannotConnect exception."""
|
||||
|
||||
# Override the default mock to raise a generic exception (not CannotConnect)
|
||||
async def raise_generic_error(device_code):
|
||||
raise RuntimeError("Unexpected error")
|
||||
|
||||
mock_actron_api.poll_for_token = raise_generic_error
|
||||
|
||||
# Start the config flow
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
# Task raises a non-CannotConnect exception, so it goes to timeout
|
||||
assert result["type"] is FlowResultType.SHOW_PROGRESS_DONE
|
||||
assert result["step_id"] == "timeout"
|
||||
|
||||
# Continue to the timeout step
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
# Should show the timeout form
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "timeout"
|
||||
|
||||
# Now fix the mock to allow successful token polling for recovery
|
||||
async def successful_poll_for_token(device_code):
|
||||
await asyncio.sleep(0.1)
|
||||
return {
|
||||
"access_token": "test_access_token",
|
||||
"refresh_token": "test_refresh_token",
|
||||
}
|
||||
|
||||
mock_actron_api.poll_for_token = successful_poll_for_token
|
||||
|
||||
# User clicks retry button
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
|
||||
# Should start progress again
|
||||
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||
assert result["step_id"] == "user"
|
||||
assert result["progress_action"] == "wait_for_authorization"
|
||||
|
||||
# Wait for the progress to complete
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Continue the flow after progress is done
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
# Should create entry on successful recovery
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "test@example.com"
|
||||
|
||||
|
||||
async def test_finish_login_auth_error(
|
||||
hass: HomeAssistant, mock_actron_api: AsyncMock, mock_setup_entry: AsyncMock
|
||||
) -> None:
|
||||
"""Test finish_login step when get_user_info raises ActronAirAuthError."""
|
||||
# Start the config flow
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
# Wait for the progress to complete
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Now make get_user_info fail with auth error
|
||||
mock_actron_api.get_user_info = AsyncMock(
|
||||
side_effect=ActronAirAuthError("Auth error getting user info")
|
||||
)
|
||||
|
||||
# Continue the flow after progress is done
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
# Should abort with oauth2_error
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "oauth2_error"
|
||||
|
||||
58
tests/components/actron_air/test_coordinator.py
Normal file
58
tests/components/actron_air/test_coordinator.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""Tests for the Actron Air coordinator."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from actron_neo_api import ActronAirAPIError, ActronAirAuthError
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
|
||||
from homeassistant.components.actron_air.coordinator import SCAN_INTERVAL
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
async def test_coordinator_update_auth_error(
|
||||
hass: HomeAssistant,
|
||||
mock_actron_api: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test coordinator handles auth error during update."""
|
||||
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
mock_actron_api.update_status.side_effect = ActronAirAuthError("Auth expired")
|
||||
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# ConfigEntryAuthFailed triggers a reauth flow
|
||||
assert len(hass.config_entries.flow.async_progress()) == 1
|
||||
|
||||
|
||||
async def test_coordinator_update_api_error(
|
||||
hass: HomeAssistant,
|
||||
mock_actron_api: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test coordinator handles API error during update."""
|
||||
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
mock_actron_api.update_status.side_effect = ActronAirAPIError("API error")
|
||||
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# UpdateFailed sets last_update_success to False on the coordinator
|
||||
coordinator = list(mock_config_entry.runtime_data.system_coordinators.values())[0]
|
||||
assert coordinator.last_update_success is False
|
||||
38
tests/components/actron_air/test_init.py
Normal file
38
tests/components/actron_air/test_init.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""Tests for the Actron Air integration setup."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from actron_neo_api import ActronAirAPIError, ActronAirAuthError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_setup_entry_auth_error(
|
||||
hass: HomeAssistant,
|
||||
mock_actron_api: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test setup entry raises ConfigEntryAuthFailed on auth error."""
|
||||
mock_actron_api.get_ac_systems.side_effect = ActronAirAuthError("Auth failed")
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
||||
|
||||
|
||||
async def test_setup_entry_api_error(
|
||||
hass: HomeAssistant,
|
||||
mock_actron_api: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test setup entry raises ConfigEntryNotReady on API error."""
|
||||
mock_actron_api.get_ac_systems.side_effect = ActronAirAPIError("API failed")
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
Reference in New Issue
Block a user