1
0
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:
Kurt Chrisford
2026-03-19 05:20:51 +10:00
committed by GitHub
parent 0e7c25488c
commit afe19147f8
7 changed files with 238 additions and 3 deletions

View File

@@ -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(

View File

@@ -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"]
}

View File

@@ -37,7 +37,7 @@ rules:
log-when-unavailable: done
parallel-updates: done
reauthentication-flow: done
test-coverage: todo
test-coverage: done
# Gold
devices: done

View File

@@ -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"

View File

@@ -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"

View 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

View 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