Nice G.O. code quality improvements (#124319)

* Bring Nice G.O. up to platinum

* Switch to listen in coordinator

* Tests

* Remove parallel updates from coordinator

* Unsub from events on config entry unload

* Detect WS disconnection

* Tests

* Fix tests

* Set unsub to None after unsubbing

* Wait 5 seconds before setting update error to prevent excessive errors

* Tweaks

* More tweaks

* Tweaks part 2

* Potential test for hass stopping

* Improve reconnect handling and test on Homeassistant stop event

* Move event handler to entry init

* Patch const instead of asyncio.sleep

---------

Co-authored-by: jbouwh <jan@jbsoft.nl>
This commit is contained in:
IceBotYT 2024-09-06 12:22:59 -04:00 committed by GitHub
parent 741add0666
commit cd3059aa14
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 221 additions and 28 deletions

View file

@ -1,7 +1,8 @@
"""Test Nice G.O. init."""
import asyncio
from datetime import timedelta
from unittest.mock import AsyncMock, MagicMock
from unittest.mock import AsyncMock, MagicMock, patch
from freezegun.api import FrozenDateTimeFactory
from nice_go import ApiError, AuthFailedError, Barrier, BarrierState
@ -10,8 +11,8 @@ from syrupy.assertion import SnapshotAssertion
from homeassistant.components.nice_go.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.helpers import issue_registry as ir
from . import setup_integration
@ -209,11 +210,11 @@ async def test_on_data_none_parsed(
) -> None:
"""Test on data with None parsed."""
mock_nice_go.event = MagicMock()
mock_nice_go.listen = MagicMock()
await setup_integration(hass, mock_config_entry, [Platform.COVER])
await mock_nice_go.event.call_args[0][0](
await mock_nice_go.listen.call_args_list[1][0][1](
{
"data": {
"devicesStatesUpdateFeed": {
@ -243,18 +244,74 @@ async def test_on_connected(
) -> None:
"""Test on connected."""
mock_nice_go.event = MagicMock()
mock_nice_go.listen = MagicMock()
await setup_integration(hass, mock_config_entry, [Platform.COVER])
assert mock_nice_go.event.call_count == 2
assert mock_nice_go.listen.call_count == 3
mock_nice_go.subscribe = AsyncMock()
await mock_nice_go.event.call_args_list[0][0][0]()
await mock_nice_go.listen.call_args_list[0][0][1]()
assert mock_nice_go.subscribe.call_count == 1
async def test_on_connection_lost(
hass: HomeAssistant,
mock_nice_go: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test on connection lost."""
mock_nice_go.listen = MagicMock()
await setup_integration(hass, mock_config_entry, [Platform.COVER])
assert mock_nice_go.listen.call_count == 3
with patch("homeassistant.components.nice_go.coordinator.RECONNECT_DELAY", 0):
await mock_nice_go.listen.call_args_list[2][0][1](
{"exception": ValueError("test")}
)
assert hass.states.get("cover.test_garage_1").state == "unavailable"
# Now fire connected
mock_nice_go.subscribe = AsyncMock()
await mock_nice_go.listen.call_args_list[0][0][1]()
assert mock_nice_go.subscribe.call_count == 1
assert hass.states.get("cover.test_garage_1").state == "closed"
async def test_on_connection_lost_reconnect(
hass: HomeAssistant,
mock_nice_go: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test on connection lost with reconnect."""
mock_nice_go.listen = MagicMock()
await setup_integration(hass, mock_config_entry, [Platform.COVER])
assert mock_nice_go.listen.call_count == 3
assert hass.states.get("cover.test_garage_1").state == "closed"
with patch("homeassistant.components.nice_go.coordinator.RECONNECT_DELAY", 0):
await mock_nice_go.listen.call_args_list[2][0][1](
{"exception": ValueError("test")}
)
assert hass.states.get("cover.test_garage_1").state == "unavailable"
async def test_no_connection_state(
hass: HomeAssistant,
mock_nice_go: AsyncMock,
@ -262,13 +319,13 @@ async def test_no_connection_state(
) -> None:
"""Test parsing barrier with no connection state."""
mock_nice_go.event = MagicMock()
mock_nice_go.listen = MagicMock()
await setup_integration(hass, mock_config_entry, [Platform.COVER])
assert mock_nice_go.event.call_count == 2
assert mock_nice_go.listen.call_count == 3
await mock_nice_go.event.call_args[0][0](
await mock_nice_go.listen.call_args_list[1][0][1](
{
"data": {
"devicesStatesUpdateFeed": {
@ -286,3 +343,65 @@ async def test_no_connection_state(
)
assert hass.states.get("cover.test_garage_1").state == "unavailable"
async def test_connection_attempts_exhausted(
hass: HomeAssistant,
mock_nice_go: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test connection attempts exhausted."""
mock_nice_go.connect.side_effect = ApiError
with (
patch("homeassistant.components.nice_go.coordinator.RECONNECT_ATTEMPTS", 1),
patch("homeassistant.components.nice_go.coordinator.RECONNECT_DELAY", 0),
):
await setup_integration(hass, mock_config_entry, [Platform.COVER])
assert "API error" in caplog.text
assert "Error requesting Nice G.O. data" in caplog.text
async def test_reconnect_hass_stopping(
hass: HomeAssistant,
mock_nice_go: AsyncMock,
mock_config_entry: MockConfigEntry,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test reconnect with hass stopping."""
mock_nice_go.listen = MagicMock()
mock_nice_go.connect.side_effect = ApiError
wait_for_hass = asyncio.Event()
@callback
def _async_ha_stop(event: Event) -> None:
"""Stop reconnecting if hass is stopping."""
wait_for_hass.set()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_ha_stop)
with (
patch("homeassistant.components.nice_go.coordinator.RECONNECT_DELAY", 0.1),
patch("homeassistant.components.nice_go.coordinator.RECONNECT_ATTEMPTS", 20),
):
await setup_integration(hass, mock_config_entry, [Platform.COVER])
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await wait_for_hass.wait()
await hass.async_block_till_done(wait_background_tasks=True)
assert mock_nice_go.connect.call_count < 10
assert len(hass._background_tasks) == 0
assert "API error" in caplog.text
assert (
"Failed to connect to the websocket, reconnect attempts exhausted"
not in caplog.text
)