mirror of
https://github.com/home-assistant/core.git
synced 2026-11-06 05:32:45 -04:00
Follow the Peblar charging session as it changes (#180744)
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
"""Fixtures for the Peblar integration tests."""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Generator
|
||||
from contextlib import nullcontext
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from peblar import (
|
||||
PeblarEVInterface,
|
||||
@@ -81,6 +82,15 @@ def mock_peblar(request: pytest.FixtureRequest) -> Generator[MagicMock]:
|
||||
system_information
|
||||
)
|
||||
|
||||
# The event stream parks here until the entry unloads, the way a
|
||||
# real one waits on the charger rather than returning.
|
||||
async def _listen_until_cancelled() -> None:
|
||||
await asyncio.Event().wait()
|
||||
|
||||
websocket = AsyncMock()
|
||||
websocket.listen.side_effect = _listen_until_cancelled
|
||||
peblar.websocket.return_value = websocket
|
||||
|
||||
api = peblar.rest_api.return_value
|
||||
api.ev_interface.return_value = PeblarEVInterface.from_json(
|
||||
load_fixture("ev_interface.json", DOMAIN)
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
"""Tests for the Peblar event stream."""
|
||||
|
||||
import asyncio
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from peblar import PeblarConnectionError, PeblarSessionStatus, SessionState
|
||||
import pytest
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.parametrize("init_integration", [Platform.SENSOR], indirect=True),
|
||||
pytest.mark.usefixtures("init_integration"),
|
||||
]
|
||||
|
||||
|
||||
async def test_the_stream_is_subscribed_to(mock_peblar: MagicMock) -> None:
|
||||
"""Test the charger's session is followed as soon as the entry loads."""
|
||||
websocket = mock_peblar.websocket.return_value
|
||||
websocket.connect.assert_awaited_once()
|
||||
websocket.subscribe_session_status.assert_awaited_once()
|
||||
|
||||
|
||||
async def test_a_session_change_pulls_the_poll_forward(
|
||||
hass: HomeAssistant,
|
||||
mock_peblar: MagicMock,
|
||||
) -> None:
|
||||
"""Test an event asks the poll to catch up rather than waiting it out."""
|
||||
meter = mock_peblar.rest_api.return_value.meter
|
||||
meter.reset_mock()
|
||||
|
||||
websocket = mock_peblar.websocket.return_value
|
||||
handle_session_status = websocket.subscribe_session_status.call_args.args[0]
|
||||
handle_session_status(
|
||||
PeblarSessionStatus(state=SessionState.CHARGING, meter_data=None)
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
meter.assert_awaited()
|
||||
|
||||
|
||||
async def test_the_wait_backs_off_and_settles_once_the_charger_answers(
|
||||
hass: HomeAssistant,
|
||||
mock_peblar: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test how long the stream waits between attempts.
|
||||
|
||||
A charger that cannot be reached is given more room each time. Once it
|
||||
answers, that is settled: a drop hours later starts over from the
|
||||
shortest wait rather than the longest one reached at startup.
|
||||
"""
|
||||
websocket = mock_peblar.websocket.return_value
|
||||
websocket.connect.side_effect = [
|
||||
PeblarConnectionError("Gone"),
|
||||
PeblarConnectionError("Still gone"),
|
||||
None,
|
||||
None,
|
||||
]
|
||||
|
||||
hang_ups = 0
|
||||
|
||||
async def _hang_up_once() -> None:
|
||||
nonlocal hang_ups
|
||||
hang_ups += 1
|
||||
if hang_ups == 1:
|
||||
return
|
||||
await asyncio.Event().wait()
|
||||
|
||||
websocket.listen.side_effect = _hang_up_once
|
||||
|
||||
waits: list[float] = []
|
||||
|
||||
async def _record(delay: float) -> None:
|
||||
waits.append(delay)
|
||||
|
||||
with patch("homeassistant.components.peblar.websocket.asyncio.sleep", _record):
|
||||
await hass.config_entries.async_reload(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Five seconds, then ten while the charger stays away. It answers on
|
||||
# the third try and hangs up, and the wait is back to five.
|
||||
assert waits[:3] == [5, 10, 5]
|
||||
|
||||
|
||||
async def test_a_subscription_that_never_lands_keeps_backing_off(
|
||||
hass: HomeAssistant,
|
||||
mock_peblar: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test taking the socket is not the same as having a stream.
|
||||
|
||||
A charger that accepts the connection but never completes the
|
||||
subscription would otherwise be retried every five seconds forever.
|
||||
"""
|
||||
websocket = mock_peblar.websocket.return_value
|
||||
subscriptions = 0
|
||||
|
||||
async def _refuse_twice(_callback: object) -> None:
|
||||
nonlocal subscriptions
|
||||
subscriptions += 1
|
||||
if subscriptions <= 2:
|
||||
raise PeblarConnectionError("Not listening")
|
||||
|
||||
websocket.subscribe_session_status.side_effect = _refuse_twice
|
||||
|
||||
waits: list[float] = []
|
||||
|
||||
async def _record(delay: float) -> None:
|
||||
waits.append(delay)
|
||||
|
||||
with patch("homeassistant.components.peblar.websocket.asyncio.sleep", _record):
|
||||
await hass.config_entries.async_reload(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# The socket opened every time, so a reset on that alone would have
|
||||
# left both waits at five seconds.
|
||||
assert waits[:2] == [5, 10]
|
||||
|
||||
|
||||
async def test_the_stream_is_closed_when_the_entry_unloads(
|
||||
hass: HomeAssistant,
|
||||
mock_peblar: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the charger is let go of when the entry goes away."""
|
||||
await hass.config_entries.async_unload(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_peblar.websocket.return_value.disconnect.assert_awaited()
|
||||
Reference in New Issue
Block a user