core/tests/components/rainbird/conftest.py

253 lines
6.8 KiB
Python

"""Test fixtures for rainbird."""
from __future__ import annotations
from http import HTTPStatus
import json
from typing import Any
from unittest.mock import patch
from pyrainbird import encryption
import pytest
from homeassistant.components.rainbird import DOMAIN
from homeassistant.components.rainbird.const import (
ATTR_DURATION,
DEFAULT_TRIGGER_TIME_MINUTES,
)
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
from tests.test_util.aiohttp import AiohttpClientMocker, AiohttpClientMockResponse
HOST = "example.com"
URL = "http://example.com/stick"
PASSWORD = "password"
SERIAL_NUMBER = 0x12635436566
MAC_ADDRESS = "4C:A1:61:00:11:22"
MAC_ADDRESS_UNIQUE_ID = "4c:a1:61:00:11:22"
#
# Response payloads below come from pyrainbird test cases.
#
# Get serial number Command 0x85. Serial is 0x12635436566
SERIAL_RESPONSE = "850000012635436566"
ZERO_SERIAL_RESPONSE = "850000000000000000"
# Model and version command 0x82
MODEL_AND_VERSION_RESPONSE = "820005090C" # ESP-TM2
# Get available stations command 0x83
AVAILABLE_STATIONS_RESPONSE = "83017F000000" # Mask for 7 zones
EMPTY_STATIONS_RESPONSE = "830000000000"
# Get zone state command 0xBF.
ZONE_3_ON_RESPONSE = "BF0004000000" # Zone 3 is on
ZONE_5_ON_RESPONSE = "BF0010000000" # Zone 5 is on
ZONE_OFF_RESPONSE = "BF0000000000" # All zones off
ZONE_STATE_OFF_RESPONSE = "BF0000000000"
# Get rain sensor state command 0XBE
RAIN_SENSOR_OFF = "BE00"
RAIN_SENSOR_ON = "BE01"
# Get rain delay command 0xB6
RAIN_DELAY = "B60010" # 0x10 is 16
RAIN_DELAY_OFF = "B60000"
# ACK command 0x10, Echo 0x06
ACK_ECHO = "0106"
WIFI_PARAMS_RESPONSE = {
"macAddress": MAC_ADDRESS,
"localIpAddress": "1.1.1.38",
"localNetmask": "255.255.255.0",
"localGateway": "1.1.1.1",
"rssi": -61,
"wifiSsid": "wifi-ssid-name",
"wifiPassword": "wifi-password-name",
"wifiSecurity": "wpa2-aes",
"apTimeoutNoLan": 20,
"apTimeoutIdle": 20,
"apSecurity": "unknown",
"stickVersion": "Rain Bird Stick Rev C/1.63",
}
CONFIG = {
DOMAIN: {
"host": HOST,
"password": PASSWORD,
"trigger_time": {
"minutes": 6,
},
}
}
CONFIG_ENTRY_DATA_OLD_FORMAT = {
"host": HOST,
"password": PASSWORD,
"serial_number": SERIAL_NUMBER,
}
CONFIG_ENTRY_DATA = {
"host": HOST,
"password": PASSWORD,
"serial_number": SERIAL_NUMBER,
"mac": MAC_ADDRESS,
}
@pytest.fixture
def platforms() -> list[Platform]:
"""Fixture to specify platforms to test."""
return []
@pytest.fixture
async def config_entry_unique_id() -> str:
"""Fixture for config entry unique id."""
return MAC_ADDRESS_UNIQUE_ID
@pytest.fixture
async def serial_number() -> int:
"""Fixture for serial number used in the config entry data."""
return SERIAL_NUMBER
@pytest.fixture
async def config_entry_data(serial_number: int) -> dict[str, Any]:
"""Fixture for MockConfigEntry data."""
return {
**CONFIG_ENTRY_DATA,
"serial_number": serial_number,
}
@pytest.fixture
async def config_entry(
config_entry_data: dict[str, Any] | None,
config_entry_unique_id: str | None,
) -> MockConfigEntry | None:
"""Fixture for MockConfigEntry."""
if config_entry_data is None:
return None
return MockConfigEntry(
unique_id=config_entry_unique_id,
domain=DOMAIN,
data=config_entry_data,
options={ATTR_DURATION: DEFAULT_TRIGGER_TIME_MINUTES},
)
@pytest.fixture(autouse=True)
async def add_config_entry(
hass: HomeAssistant, config_entry: MockConfigEntry | None
) -> None:
"""Fixture to add the config entry."""
if config_entry:
config_entry.add_to_hass(hass)
@pytest.fixture(autouse=True)
def setup_platforms(
hass: HomeAssistant,
platforms: list[str],
) -> None:
"""Fixture for setting up the default platforms."""
with patch(f"homeassistant.components.{DOMAIN}.PLATFORMS", platforms):
yield
def rainbird_json_response(result: dict[str, str]) -> bytes:
"""Create a fake API response."""
return encryption.encrypt(
'{"jsonrpc": "2.0", "result": %s, "id": 1} ' % json.dumps(result),
PASSWORD,
)
def mock_json_response(result: dict[str, str]) -> AiohttpClientMockResponse:
"""Create a fake AiohttpClientMockResponse."""
return AiohttpClientMockResponse(
"POST", URL, response=rainbird_json_response(result)
)
def mock_response(data: str) -> AiohttpClientMockResponse:
"""Create a fake AiohttpClientMockResponse."""
return mock_json_response({"data": data})
def mock_response_error(
status: HTTPStatus = HTTPStatus.SERVICE_UNAVAILABLE,
) -> AiohttpClientMockResponse:
"""Create a fake AiohttpClientMockResponse."""
return AiohttpClientMockResponse("POST", URL, status=status)
@pytest.fixture(name="stations_response")
def mock_station_response() -> str:
"""Mock response to return available stations."""
return AVAILABLE_STATIONS_RESPONSE
@pytest.fixture(name="zone_state_response")
def mock_zone_state_response() -> str:
"""Mock response to return zone states."""
return ZONE_STATE_OFF_RESPONSE
@pytest.fixture(name="rain_response")
def mock_rain_response() -> str:
"""Mock response to return rain sensor state."""
return RAIN_SENSOR_OFF
@pytest.fixture(name="rain_delay_response")
def mock_rain_delay_response() -> str:
"""Mock response to return rain delay state."""
return RAIN_DELAY_OFF
@pytest.fixture(name="model_and_version_response")
def mock_model_and_version_response() -> str:
"""Mock response to return rain delay state."""
return MODEL_AND_VERSION_RESPONSE
@pytest.fixture(name="api_responses")
def mock_api_responses(
model_and_version_response: str,
stations_response: str,
zone_state_response: str,
rain_response: str,
rain_delay_response: str,
) -> list[str]:
"""Fixture to set up a list of fake API responsees for tests to extend.
These are returned in the order they are requested by the update coordinator.
"""
return [
model_and_version_response,
stations_response,
zone_state_response,
rain_response,
rain_delay_response,
]
@pytest.fixture(name="responses")
def mock_responses(api_responses: list[str]) -> list[AiohttpClientMockResponse]:
"""Fixture to set up a list of fake API responsees for tests to extend."""
return [mock_response(api_response) for api_response in api_responses]
@pytest.fixture(autouse=True)
def handle_responses(
aioclient_mock: AiohttpClientMocker,
responses: list[AiohttpClientMockResponse],
) -> None:
"""Fixture for command mocking for fake responses to the API url."""
async def handle(method, url, data) -> AiohttpClientMockResponse:
return responses.pop(0)
aioclient_mock.post(URL, side_effect=handle)