"""Common fixtures for Anova.""" from __future__ import annotations import asyncio from dataclasses import dataclass import json from typing import Any from unittest.mock import AsyncMock, patch from aiohttp import ClientSession from anova_wifi import ( AnovaApi, AnovaWebsocketHandler, InvalidLogin, NoDevicesFound, WebsocketFailure, ) import pytest from homeassistant.core import HomeAssistant DUMMY_ID = "anova_id" @dataclass class MockedanovaWebsocketMessage: """Mock the websocket message for Anova.""" input_data: dict[str, Any] data: str = "" def __post_init__(self) -> None: """Set up data after creation.""" self.data = json.dumps(self.input_data) class MockedAnovaWebsocketStream: """Mock the websocket stream for Anova.""" def __init__(self, messages: list[MockedanovaWebsocketMessage]) -> None: """Initialize a Anova Websocket Stream that can be manipulated for tests.""" self.messages = messages def __aiter__(self) -> MockedAnovaWebsocketStream: """Handle async iteration.""" return self async def __anext__(self) -> MockedanovaWebsocketMessage: """Get the next message in the websocket stream.""" if self.messages: return self.messages.pop(0) raise StopAsyncIteration def clear(self) -> None: """Clear the Websocket stream.""" self.messages.clear() class MockedAnovaWebsocketHandler(AnovaWebsocketHandler): """Mock the Anova websocket handler.""" def __init__( self, firebase_jwt: str, jwt: str, session: ClientSession, connect_messages: list[MockedanovaWebsocketMessage], post_connect_messages: list[MockedanovaWebsocketMessage], ) -> None: """Initialize the websocket handler with whatever messages you want.""" super().__init__(firebase_jwt, jwt, session) self.connect_messages = connect_messages self.post_connect_messages = post_connect_messages async def connect(self) -> None: """Create a future for the message listener.""" self.ws = MockedAnovaWebsocketStream(self.connect_messages) await self.message_listener() self.ws = MockedAnovaWebsocketStream(self.post_connect_messages) # RUF006 ignored as it replicates the parent library # https://github.com/Lash-L/anova_wifi/issues/35 asyncio.ensure_future(self.message_listener()) # noqa: RUF006 def anova_api_mock( connect_messages: list[MockedanovaWebsocketMessage] | None = None, post_connect_messages: list[MockedanovaWebsocketMessage] | None = None, ) -> AsyncMock: """Mock the api for Anova.""" api_mock = AsyncMock() async def authenticate_side_effect() -> None: api_mock.jwt = "my_test_jwt" api_mock._firebase_jwt = "my_test_firebase_jwt" async def create_websocket_side_effect() -> None: api_mock.websocket_handler = MockedAnovaWebsocketHandler( firebase_jwt=api_mock._firebase_jwt, jwt=api_mock.jwt, session=AsyncMock(), connect_messages=connect_messages if connect_messages is not None else [ MockedanovaWebsocketMessage( { "command": "EVENT_APC_WIFI_LIST", "payload": [ { "cookerId": DUMMY_ID, "type": "a5", "pairedAt": "2023-08-12T02:33:20.917716Z", "name": "Anova Precision Cooker", } ], } ), ], post_connect_messages=post_connect_messages if post_connect_messages is not None else [ MockedanovaWebsocketMessage( { "command": "EVENT_APC_STATE", "payload": { "cookerId": DUMMY_ID, "state": { "boot-id": "8620610049456548422", "job": { "cook-time-seconds": 0, "id": "8759286e3125b0c547", "mode": "IDLE", "ota-url": "", "target-temperature": 54.72, "temperature-unit": "F", }, "job-status": { "cook-time-remaining": 0, "job-start-systick": 599679, "provisioning-pairing-code": 7514, "state": "", "state-change-systick": 599679, }, "pin-info": { "device-safe": 0, "water-leak": 0, "water-level-critical": 0, "water-temp-too-high": 0, }, "system-info": { "class": "A5", "firmware-version": "2.2.0", "type": "RA2L1-128", }, "system-info-details": { "firmware-version-raw": "VM178_A_02.02.00_MKE15-128", "systick": 607026, "version-string": "VM171_A_02.02.00 RA2L1-128", }, "temperature-info": { "heater-temperature": 22.37, "triac-temperature": 36.04, "water-temperature": 18.33, }, }, }, } ), ], ) await api_mock.websocket_handler.connect() if not api_mock.websocket_handler.devices: raise NoDevicesFound("No devices were found on the websocket.") api_mock.authenticate.side_effect = authenticate_side_effect api_mock.create_websocket.side_effect = create_websocket_side_effect return api_mock @pytest.fixture async def anova_api( hass: HomeAssistant, ) -> AnovaApi: """Mock the api for Anova.""" api_mock = anova_api_mock() with ( patch("homeassistant.components.anova.AnovaApi", return_value=api_mock), patch( "homeassistant.components.anova.config_flow.AnovaApi", return_value=api_mock ), ): api = AnovaApi( None, "sample@gmail.com", "sample", ) yield api @pytest.fixture async def anova_api_no_devices( hass: HomeAssistant, ) -> AnovaApi: """Mock the api for Anova with no online devices.""" api_mock = anova_api_mock(connect_messages=[], post_connect_messages=[]) with ( patch("homeassistant.components.anova.AnovaApi", return_value=api_mock), patch( "homeassistant.components.anova.config_flow.AnovaApi", return_value=api_mock ), ): api = AnovaApi( None, "sample@gmail.com", "sample", ) yield api @pytest.fixture async def anova_api_wrong_login( hass: HomeAssistant, ) -> AnovaApi: """Mock the api for Anova with a wrong login.""" api_mock = anova_api_mock() async def authenticate_side_effect(): raise InvalidLogin api_mock.authenticate.side_effect = authenticate_side_effect with patch("homeassistant.components.anova.AnovaApi", return_value=api_mock): api = AnovaApi( None, "sample@gmail.com", "sample", ) yield api @pytest.fixture async def anova_api_no_data( hass: HomeAssistant, ) -> AnovaApi: """Mock the api for Anova with a wrong login.""" api_mock = anova_api_mock(post_connect_messages=[]) with patch("homeassistant.components.anova.AnovaApi", return_value=api_mock): api = AnovaApi( None, "sample@gmail.com", "sample", ) yield api @pytest.fixture async def anova_api_websocket_failure( hass: HomeAssistant, ) -> AnovaApi: """Mock the api for Anova with a websocket failure.""" api_mock = anova_api_mock() async def create_websocket_side_effect(): raise WebsocketFailure api_mock.create_websocket.side_effect = create_websocket_side_effect with patch("homeassistant.components.anova.AnovaApi", return_value=api_mock): api = AnovaApi( None, "sample@gmail.com", "sample", ) yield api