1
0
mirror of https://github.com/home-assistant/core.git synced 2026-07-13 17:44:45 +01:00

Add first cover entity tests to Overkiz (#165670)

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Mick Vleeshouwer
2026-04-20 19:30:26 +02:00
committed by GitHub
parent e889541d2e
commit a92dcaaf5f
15 changed files with 42393 additions and 23 deletions
+7 -5
View File
@@ -1,14 +1,16 @@
"""Tests for the overkiz component."""
"""Tests for the Overkiz component."""
import humps
from pyoverkiz.models import Setup
from homeassistant.components.overkiz.const import DOMAIN
from tests.common import load_json_object_fixture
DEFAULT_SETUP_FIXTURE = "setup/cloud_somfy_tahoma_switch_europe.json"
def load_setup_fixture(
fixture: str = "overkiz/setup/setup_tahoma_switch.json",
) -> Setup:
def load_setup_fixture(fixture: str = DEFAULT_SETUP_FIXTURE) -> Setup:
"""Return setup from fixture."""
setup_json = load_json_object_fixture(fixture)
setup_json = load_json_object_fixture(fixture, DOMAIN)
return Setup(**humps.decamelize(setup_json))
+118 -18
View File
@@ -1,19 +1,92 @@
"""Configuration for overkiz tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, Mock, patch
from collections.abc import Awaitable, Callable, Generator
from dataclasses import dataclass, field
from unittest.mock import AsyncMock, patch
from pyoverkiz.client import OverkizClient
from pyoverkiz.enums import APIType
from pyoverkiz.models import Event, OverkizServer, Setup
import pytest
from homeassistant.components.overkiz.const import DOMAIN
from homeassistant.core import HomeAssistant
from . import load_setup_fixture
from . import DEFAULT_SETUP_FIXTURE, load_setup_fixture
from .test_config_flow import TEST_EMAIL, TEST_GATEWAY_ID, TEST_PASSWORD, TEST_SERVER
from tests.common import MockConfigEntry
MOCK_SETUP_RESPONSE = Mock(devices=[], gateways=[])
type SetupOverkizIntegration = Callable[..., Awaitable[MockConfigEntry]]
@dataclass
class MockOverkizClient(OverkizClient):
"""Mock Overkiz client used by integration tests."""
setup: Setup = field(default_factory=load_setup_fixture)
event_batches: list[list[Event]] = field(default_factory=list)
server: OverkizServer = field(
default_factory=lambda: OverkizServer(
name="Somfy",
endpoint="https://example.test/enduser-mobile-web/enduserAPI",
manufacturer="Somfy",
configuration_url=None,
)
)
def __post_init__(self) -> None:
"""Initialize async client methods."""
self._execution_id = 0
self.api_type = APIType.CLOUD
self.login = AsyncMock(return_value=True)
self.get_setup = AsyncMock(side_effect=self._async_get_setup)
self.get_devices = AsyncMock(side_effect=self._async_get_devices)
self.get_scenarios = AsyncMock(return_value=[])
self.fetch_events = AsyncMock(side_effect=self._async_fetch_events)
self.get_current_executions = AsyncMock(return_value=[])
self.cancel_command = AsyncMock(return_value=None)
self.execute_command = AsyncMock(side_effect=self._async_execute_command)
def set_setup_fixture(self, fixture: str) -> None:
"""Load a setup fixture for the next integration setup."""
self.setup = load_setup_fixture(fixture)
self.event_batches.clear()
self.reset_mock()
def queue_events(self, *batches: list[Event]) -> None:
"""Queue batches of events returned by fetch_events."""
self.event_batches.extend(batches)
def reset_mock(self) -> None:
"""Reset call history while keeping configured behavior."""
self.login.reset_mock()
self.get_setup.reset_mock()
self.get_devices.reset_mock()
self.get_scenarios.reset_mock()
self.fetch_events.reset_mock()
self.get_current_executions.reset_mock()
self.cancel_command.reset_mock()
self.execute_command.reset_mock()
async def _async_get_setup(self) -> Setup:
"""Return the configured setup."""
return self.setup
async def _async_get_devices(self, refresh: bool = False) -> list:
"""Return the configured devices."""
return self.setup.devices
async def _async_fetch_events(self) -> list[Event]:
"""Return queued event batches one refresh at a time."""
if self.event_batches:
return self.event_batches.pop(0)
return []
async def _async_execute_command(self, *args, **kwargs) -> str:
"""Return a unique execution id for each command."""
self._execution_id += 1
return f"exec-{self._execution_id}"
@pytest.fixture
@@ -37,21 +110,48 @@ def mock_setup_entry() -> Generator[AsyncMock]:
@pytest.fixture
async def init_integration(
def mock_client() -> MockOverkizClient:
"""Return a configurable mock Overkiz client."""
return MockOverkizClient()
@pytest.fixture
def setup_overkiz_integration(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_client: MockOverkizClient,
) -> SetupOverkizIntegration:
"""Return a helper to set up the Overkiz integration from a chosen fixture."""
async def _setup(
*,
fixture: str = DEFAULT_SETUP_FIXTURE,
) -> MockConfigEntry:
mock_config_entry.add_to_hass(hass)
mock_client.set_setup_fixture(fixture)
with (
patch(
"homeassistant.components.overkiz.create_cloud_client",
return_value=mock_client,
),
patch(
"homeassistant.components.overkiz.create_local_client",
return_value=mock_client,
),
):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
return mock_config_entry
return _setup
@pytest.fixture
async def init_integration(
setup_overkiz_integration: SetupOverkizIntegration,
) -> MockConfigEntry:
"""Set up the Overkiz integration for testing."""
mock_config_entry.add_to_hass(hass)
with patch.multiple(
"pyoverkiz.client.OverkizClient",
login=AsyncMock(return_value=True),
get_setup=AsyncMock(return_value=load_setup_fixture()),
get_scenarios=AsyncMock(return_value=[]),
fetch_events=AsyncMock(return_value=[]),
):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
return mock_config_entry
return await setup_overkiz_integration()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,345 @@
{
"creationTime": 1613674982000,
"lastUpdateTime": 1613674982000,
"id": "SETUP-1234-1234-6362",
"location": {
"creationTime": 1613674982000,
"lastUpdateTime": 1632527573000,
"city": "*",
"country": "*",
"postalCode": "*/*",
"addressLine1": "*",
"addressLine2": "",
"timezone": "Asia/Bangkok",
"longitude": "*",
"latitude": "*",
"twilightMode": 2,
"twilightAngle": "CIVIL",
"twilightCity": "paris",
"summerSolsticeDuskMinutes": 1290,
"winterSolsticeDuskMinutes": 990,
"twilightOffsetEnabled": false,
"dawnOffset": 0,
"duskOffset": 0,
"countryCode": "TH"
},
"gateways": [
{
"gatewayId": "1234-1234-6362",
"type": 53,
"subType": 1,
"placeOID": "6133b4a0-f514-4553-b635-d1b7beb7e7b2",
"alive": true,
"timeReliable": true,
"connectivity": {
"status": "OK",
"protocolVersion": "2021.2.4"
},
"upToDate": true,
"updateStatus": "UP_TO_DATE",
"syncInProgress": false,
"functions": "INTERNET_AUTHORIZATION,SCENARIO_DOWNLOAD,SCENARIO_AUTO_LAUNCHING,SCENARIO_TELECO_LAUNCHING,INTERNET_UPLOAD,INTERNET_UPDATE,TRIGGERS_SENSORS",
"mode": "ACTIVE"
}
],
"devices": [
{
"creationTime": 1613675393000,
"lastUpdateTime": 1613675393000,
"label": "*",
"deviceURL": "internal://1234-1234-6362/pod/0",
"shortcut": false,
"controllableName": "internal:PodMiniComponent",
"definition": {
"commands": [
{
"commandName": "getName",
"nparams": 0
},
{
"commandName": "update",
"nparams": 0
},
{
"commandName": "setCountryCode",
"nparams": 1
},
{
"commandName": "activateCalendar",
"nparams": 0
},
{
"commandName": "deactivateCalendar",
"nparams": 0
},
{
"commandName": "refreshPodMode",
"nparams": 0
},
{
"commandName": "refreshUpdateStatus",
"nparams": 0
},
{
"commandName": "setCalendar",
"nparams": 1
},
{
"commandName": "setLightingLedPodMode",
"nparams": 1
},
{
"commandName": "setPodLedOff",
"nparams": 0
},
{
"commandName": "setPodLedOn",
"nparams": 0
}
],
"states": [
{
"type": "DiscreteState",
"values": ["offline", "online"],
"qualifiedName": "core:ConnectivityState"
},
{
"type": "DataState",
"qualifiedName": "core:CountryCodeState"
},
{
"type": "DataState",
"qualifiedName": "core:LocalIPv4AddressState"
},
{
"type": "DataState",
"qualifiedName": "core:NameState"
},
{
"type": "DiscreteState",
"values": [
"doublePress",
"longPress",
"simplePress",
"triplePress",
"veryLongPress"
],
"qualifiedName": "internal:LastActionConfigButtonState"
},
{
"type": "ContinuousState",
"qualifiedName": "internal:LightingLedPodModeState"
}
],
"dataProperties": [],
"widgetName": "Pod",
"uiProfiles": ["UpdatableComponent"],
"uiClass": "Pod",
"qualifiedName": "internal:PodMiniComponent",
"type": "ACTUATOR"
},
"states": [
{
"name": "core:NameState",
"type": 3,
"value": "*"
},
{
"name": "internal:LightingLedPodModeState",
"type": 2,
"value": 1.0
},
{
"name": "core:CountryCodeState",
"type": 3,
"value": "TH"
},
{
"name": "core:LocalIPv4AddressState",
"type": 3,
"value": "N/A"
}
],
"available": true,
"enabled": true,
"placeOID": "6133b4a0-f514-4553-b635-d1b7beb7e7b2",
"widget": "Pod",
"type": 1,
"oid": "2dfeb3c8-a71d-4552-9e9b-875baf906a6c",
"uiClass": "Pod"
},
{
"creationTime": 1613676516000,
"lastUpdateTime": 1613676516000,
"label": "Patio Shutter",
"deviceURL": "rts://1234-1234-6362/16730022",
"shortcut": false,
"controllableName": "rts:RollerShutterRTSComponent",
"definition": {
"commands": [
{
"commandName": "close",
"nparams": 1
},
{
"commandName": "down",
"nparams": 1
},
{
"commandName": "identify",
"nparams": 0
},
{
"commandName": "my",
"nparams": 1
},
{
"commandName": "open",
"nparams": 1
},
{
"commandName": "rest",
"nparams": 1
},
{
"commandName": "stop",
"nparams": 1
},
{
"commandName": "test",
"nparams": 0
},
{
"commandName": "up",
"nparams": 1
},
{
"commandName": "openConfiguration",
"nparams": 1
}
],
"states": [],
"dataProperties": [
{
"value": "0",
"qualifiedName": "core:identifyInterval"
}
],
"widgetName": "UpDownRollerShutter",
"uiProfiles": ["OpenCloseShutter", "OpenClose"],
"uiClass": "RollerShutter",
"qualifiedName": "rts:RollerShutterRTSComponent",
"type": "ACTUATOR"
},
"attributes": [
{
"name": "rts:diy",
"type": 6,
"value": false
}
],
"available": true,
"enabled": true,
"placeOID": "6133b4a0-f514-4553-b635-d1b7beb7e7b2",
"widget": "UpDownRollerShutter",
"type": 1,
"oid": "d4da34c9-5cbf-4278-b8ec-ae66428b9522",
"uiClass": "RollerShutter"
},
{
"creationTime": 1613676640000,
"lastUpdateTime": 1613676640000,
"label": "Kitchen Shutter",
"deviceURL": "rts://1234-1234-6362/16749917",
"shortcut": false,
"controllableName": "rts:RollerShutterRTSComponent",
"definition": {
"commands": [
{
"commandName": "close",
"nparams": 1
},
{
"commandName": "down",
"nparams": 1
},
{
"commandName": "identify",
"nparams": 0
},
{
"commandName": "my",
"nparams": 1
},
{
"commandName": "open",
"nparams": 1
},
{
"commandName": "rest",
"nparams": 1
},
{
"commandName": "stop",
"nparams": 1
},
{
"commandName": "test",
"nparams": 0
},
{
"commandName": "up",
"nparams": 1
},
{
"commandName": "openConfiguration",
"nparams": 1
}
],
"states": [],
"dataProperties": [
{
"value": "0",
"qualifiedName": "core:identifyInterval"
}
],
"widgetName": "UpDownRollerShutter",
"uiProfiles": ["OpenCloseShutter", "OpenClose"],
"uiClass": "RollerShutter",
"qualifiedName": "rts:RollerShutterRTSComponent",
"type": "ACTUATOR"
},
"attributes": [
{
"name": "rts:diy",
"type": 6,
"value": false
}
],
"available": true,
"enabled": true,
"placeOID": "6133b4a0-f514-4553-b635-d1b7beb7e7b2",
"widget": "UpDownRollerShutter",
"type": 1,
"oid": "ef870fe7-daf3-4f33-b91e-62b2a809ef4e",
"uiClass": "RollerShutter"
}
],
"zones": [],
"resellerDelegationType": "NEVER",
"oid": "fee37cf6-1d39-49fc-8d67-af71dfe20c4d",
"rootPlace": {
"creationTime": 1613674982000,
"lastUpdateTime": 1613674982000,
"label": "Palm Court",
"type": 0,
"oid": "6133b4a0-f514-4553-b635-d1b7beb7e7b2",
"subPlaces": []
},
"features": [
{
"name": "connexoon-rts-window",
"source": "GATEWAY_TYPE"
}
]
}
@@ -0,0 +1,891 @@
{
"creationTime": 1665238624000,
"lastUpdateTime": 1665238624000,
"id": "SETUP-1234-5678-6867",
"location": {
"creationTime": 1665238624000,
"lastUpdateTime": 1667054735000,
"city": "**",
"country": "**",
"postalCode": "**",
"addressLine1": "**",
"addressLine2": "*",
"timezone": "Europe/Amsterdam",
"longitude": "**",
"latitude": "**",
"twilightMode": 2,
"twilightAngle": "SOLAR",
"twilightCity": "amsterdam",
"summerSolsticeDuskMinutes": 1290,
"winterSolsticeDuskMinutes": 990,
"twilightOffsetEnabled": false,
"dawnOffset": 0,
"duskOffset": 0,
"countryCode": "NL"
},
"gateways": [
{
"gatewayId": "1234-5678-6867",
"type": 98,
"subType": 1,
"placeOID": "41d63e43-bfa8-4e24-8c16-4faae0448cab",
"autoUpdateEnabled": true,
"alive": true,
"timeReliable": true,
"connectivity": {
"status": "OK",
"protocolVersion": "2023.4.4"
},
"upToDate": true,
"updateStatus": "UP_TO_DATE",
"syncInProgress": false,
"mode": "ACTIVE",
"functions": "INTERNET_AUTHORIZATION,SCENARIO_DOWNLOAD,SCENARIO_AUTO_LAUNCHING,SCENARIO_TELECO_LAUNCHING,INTERNET_UPLOAD,INTERNET_UPDATE,TRIGGERS_SENSORS"
}
],
"devices": [
{
"creationTime": 1665238630000,
"lastUpdateTime": 1665238630000,
"label": "Study Protocol Gateway",
"deviceURL": "homekit://1234-5678-6867/stack",
"shortcut": false,
"controllableName": "homekit:StackComponent",
"definition": {
"commands": [
{
"commandName": "deleteControllers",
"nparams": 0
}
],
"states": [],
"dataProperties": [],
"widgetName": "HomekitStack",
"uiProfiles": ["Specific"],
"uiClass": "ProtocolGateway",
"qualifiedName": "homekit:StackComponent",
"type": "PROTOCOL_GATEWAY"
},
"attributes": [
{
"name": "homekit:SetupPayload",
"type": 3,
"value": "X-HM://0024YABC1234"
},
{
"name": "homekit:SetupCode",
"type": 3,
"value": "012-34-567"
}
],
"available": true,
"enabled": true,
"placeOID": "41d63e43-bfa8-4e24-8c16-4faae0448cab",
"type": 5,
"widget": "HomekitStack",
"oid": "ab964849-56ca-4e9c-a58c-33ce5e341b68",
"uiClass": "ProtocolGateway"
},
{
"creationTime": 1665238630000,
"lastUpdateTime": 1665238630000,
"label": "Nursery Hub",
"deviceURL": "internal://1234-5678-6867/pod/0",
"shortcut": false,
"controllableName": "internal:PodV3Component",
"definition": {
"commands": [
{
"commandName": "getName",
"nparams": 0
},
{
"commandName": "update",
"nparams": 0
},
{
"commandName": "setCountryCode",
"nparams": 1
},
{
"commandName": "activateCalendar",
"nparams": 0
},
{
"commandName": "deactivateCalendar",
"nparams": 0
},
{
"commandName": "refreshPodMode",
"nparams": 0
},
{
"commandName": "refreshUpdateStatus",
"nparams": 0
},
{
"commandName": "setCalendar",
"nparams": 1
},
{
"commandName": "setLightingLedPodMode",
"nparams": 1
},
{
"commandName": "setPodLedOff",
"nparams": 0
},
{
"commandName": "setPodLedOn",
"nparams": 0
}
],
"states": [
{
"type": "DiscreteState",
"values": ["offline", "online"],
"qualifiedName": "core:ConnectivityState"
},
{
"type": "DataState",
"qualifiedName": "core:CountryCodeState"
},
{
"eventBased": true,
"type": "DataState",
"qualifiedName": "core:LocalAccessProofState"
},
{
"type": "DataState",
"qualifiedName": "core:LocalIPv4AddressState"
},
{
"type": "DataState",
"qualifiedName": "core:NameState"
},
{
"eventBased": true,
"type": "DiscreteState",
"values": ["pressed", "stop"],
"qualifiedName": "internal:Button1State"
},
{
"eventBased": true,
"type": "DiscreteState",
"values": ["pressed", "stop"],
"qualifiedName": "internal:Button2State"
},
{
"eventBased": true,
"type": "DiscreteState",
"values": ["pressed", "stop"],
"qualifiedName": "internal:Button3State"
},
{
"type": "ContinuousState",
"qualifiedName": "internal:LightingLedPodModeState"
}
],
"dataProperties": [],
"widgetName": "Pod",
"uiProfiles": ["UpdatableComponent"],
"uiClass": "Pod",
"qualifiedName": "internal:PodV3Component",
"type": "ACTUATOR"
},
"states": [
{
"name": "internal:LightingLedPodModeState",
"type": 2,
"value": 1
},
{
"name": "core:CountryCodeState",
"type": 3,
"value": "NL"
},
{
"name": "internal:Button1State",
"type": 3,
"value": "pressed"
},
{
"name": "internal:Button3State",
"type": 3,
"value": "stop"
},
{
"name": "core:LocalAccessProofState",
"type": 3,
"value": "localAccessProof"
},
{
"name": "core:LocalIPv4AddressState",
"type": 3,
"value": "192.168.1.42"
},
{
"name": "core:NameState",
"type": 3,
"value": "Nursery Hub"
}
],
"available": true,
"enabled": true,
"placeOID": "41d63e43-bfa8-4e24-8c16-4faae0448cab",
"type": 1,
"widget": "Pod",
"oid": "c79a8bf6-59d6-434e-8cfd-97193541fa17",
"uiClass": "Pod"
},
{
"creationTime": 1665238630000,
"lastUpdateTime": 1665238630000,
"label": "Terrace Wifi",
"deviceURL": "internal://1234-5678-6867/wifi/0",
"shortcut": false,
"controllableName": "internal:WifiComponent",
"definition": {
"commands": [
{
"commandName": "clearCredentials",
"nparams": 0
},
{
"commandName": "setTargetInfraConfig",
"nparams": 2
},
{
"commandName": "setWifiMode",
"nparams": 1
}
],
"states": [
{
"type": "DataState",
"qualifiedName": "internal:CurrentInfraConfigState"
},
{
"type": "ContinuousState",
"qualifiedName": "internal:SignalStrengthState"
},
{
"type": "DataState",
"qualifiedName": "internal:WifiModeState"
}
],
"dataProperties": [],
"widgetName": "Wifi",
"uiProfiles": ["Specific"],
"uiClass": "Wifi",
"qualifiedName": "internal:WifiComponent",
"type": "ACTUATOR"
},
"states": [
{
"name": "internal:WifiModeState",
"type": 3,
"value": "infrastructure"
},
{
"name": "internal:CurrentInfraConfigState",
"type": 3,
"value": "AM"
},
{
"name": "internal:SignalStrengthState",
"type": 1,
"value": 69
}
],
"available": true,
"enabled": true,
"placeOID": "41d63e43-bfa8-4e24-8c16-4faae0448cab",
"type": 1,
"widget": "Wifi",
"oid": "4272c61b-5493-453c-8d87-a58e45ef60f8",
"uiClass": "Wifi"
},
{
"creationTime": 1665238924000,
"lastUpdateTime": 1665238924000,
"label": "Bathroom Bridge",
"deviceURL": "io://1234-5678-6867/4167385",
"shortcut": false,
"controllableName": "io:StackComponent",
"definition": {
"commands": [
{
"commandName": "advancedSomfyDiscover",
"nparams": 1
},
{
"commandName": "discover1WayController",
"nparams": 2
},
{
"commandName": "discoverActuators",
"nparams": 1
},
{
"commandName": "discoverSensors",
"nparams": 1
},
{
"commandName": "discoverSomfyUnsetActuators",
"nparams": 0
},
{
"commandName": "joinNetwork",
"nparams": 0
},
{
"commandName": "resetNetworkSecurity",
"nparams": 0
},
{
"commandName": "shareNetwork",
"nparams": 0
}
],
"states": [],
"dataProperties": [],
"widgetName": "IOStack",
"uiProfiles": ["Specific"],
"uiClass": "ProtocolGateway",
"qualifiedName": "io:StackComponent",
"type": "PROTOCOL_GATEWAY"
},
"available": true,
"enabled": true,
"placeOID": "41d63e43-bfa8-4e24-8c16-4faae0448cab",
"type": 5,
"widget": "IOStack",
"oid": "bb301e56-0957-417f-ba37-26efc11659aa",
"uiClass": "ProtocolGateway"
},
{
"creationTime": 1665238637000,
"lastUpdateTime": 1665238637000,
"label": "Garage Protocol Gateway",
"deviceURL": "ogp://1234-5678-6867/00000BE8",
"shortcut": false,
"controllableName": "ogp:Bridge",
"definition": {
"commands": [
{
"commandName": "identify",
"nparams": 0
},
{
"commandName": "sendPrivate",
"nparams": 1
},
{
"commandName": "setName",
"nparams": 1
}
],
"states": [
{
"type": "DiscreteState",
"values": ["available", "unavailable"],
"qualifiedName": "core:AvailabilityState"
},
{
"type": "DataState",
"qualifiedName": "core:NameState"
},
{
"type": "DataState",
"qualifiedName": "core:Private10State"
},
{
"type": "DataState",
"qualifiedName": "core:Private1State"
},
{
"type": "DataState",
"qualifiedName": "core:Private2State"
},
{
"type": "DataState",
"qualifiedName": "core:Private3State"
},
{
"type": "DataState",
"qualifiedName": "core:Private4State"
},
{
"type": "DataState",
"qualifiedName": "core:Private5State"
},
{
"type": "DataState",
"qualifiedName": "core:Private6State"
},
{
"type": "DataState",
"qualifiedName": "core:Private7State"
},
{
"type": "DataState",
"qualifiedName": "core:Private8State"
},
{
"type": "DataState",
"qualifiedName": "core:Private9State"
},
{
"type": "DataState",
"qualifiedName": "core:RemovableState"
}
],
"dataProperties": [],
"widgetName": "DynamicBridge",
"uiProfiles": ["Specific"],
"uiClass": "ProtocolGateway",
"qualifiedName": "ogp:Bridge",
"type": "ACTUATOR"
},
"states": [
{
"name": "core:NameState",
"type": 3,
"value": "Garage Protocol Gateway"
}
],
"attributes": [
{
"name": "core:Technology",
"type": 3,
"value": "KNX"
},
{
"name": "core:ManufacturerReference",
"type": 3,
"value": "OGP KNX Bridge"
},
{
"name": "ogp:Features",
"type": 10,
"value": [
{
"name": "private"
},
{
"name": "identification"
}
]
},
{
"name": "core:Manufacturer",
"type": 3,
"value": "Overkiz"
}
],
"available": true,
"enabled": true,
"placeOID": "41d63e43-bfa8-4e24-8c16-4faae0448cab",
"type": 1,
"widget": "DynamicBridge",
"oid": "e88717c3-02a9-49b6-a5a5-5adca558afe9",
"uiClass": "ProtocolGateway"
},
{
"creationTime": 1665238799000,
"lastUpdateTime": 1665238799000,
"label": "Dining Room Protocol Gateway",
"deviceURL": "ogp://1234-5678-6867/0003FEF3",
"shortcut": false,
"controllableName": "ogp:Bridge",
"definition": {
"commands": [
{
"commandName": "discover",
"nparams": 0
},
{
"commandName": "reset",
"nparams": 0
}
],
"states": [
{
"type": "DiscreteState",
"values": ["available", "unavailable"],
"qualifiedName": "core:AvailabilityState"
},
{
"type": "DataState",
"qualifiedName": "core:NameState"
},
{
"type": "DataState",
"qualifiedName": "core:RemovableState"
}
],
"dataProperties": [],
"widgetName": "DynamicBridge",
"uiProfiles": ["Specific"],
"uiClass": "ProtocolGateway",
"qualifiedName": "ogp:Bridge",
"type": "ACTUATOR"
},
"states": [
{
"name": "core:NameState",
"type": 3,
"value": "Dining Room Protocol Gateway"
}
],
"attributes": [
{
"name": "core:ManufacturerReference",
"type": 3,
"value": "OGP Sonos Bridge"
},
{
"name": "core:Manufacturer",
"type": 3,
"value": "Overkiz"
},
{
"name": "ogp:Features",
"type": 10,
"value": [
{
"name": "identification",
"commandLess": true
},
{
"name": "discovery"
},
{
"name": "reset"
}
]
},
{
"name": "core:Technology",
"type": 3,
"value": "Sonos"
}
],
"available": true,
"enabled": true,
"placeOID": "41d63e43-bfa8-4e24-8c16-4faae0448cab",
"type": 1,
"widget": "DynamicBridge",
"oid": "4031915f-df40-4a70-a97f-64031182a507",
"uiClass": "ProtocolGateway"
},
{
"creationTime": 1665238637000,
"lastUpdateTime": 1665238637000,
"label": "Living Room Bridge",
"deviceURL": "ogp://1234-5678-6867/039575E9",
"shortcut": false,
"controllableName": "ogp:Bridge",
"definition": {
"commands": [
{
"commandName": "discover",
"nparams": 0
},
{
"commandName": "identify",
"nparams": 0
},
{
"commandName": "setName",
"nparams": 1
}
],
"states": [
{
"type": "DiscreteState",
"values": ["available", "unavailable"],
"qualifiedName": "core:AvailabilityState"
},
{
"type": "DataState",
"qualifiedName": "core:NameState"
},
{
"type": "DataState",
"qualifiedName": "core:RemovableState"
}
],
"dataProperties": [],
"widgetName": "DynamicBridge",
"uiProfiles": ["Specific"],
"uiClass": "ProtocolGateway",
"qualifiedName": "ogp:Bridge",
"type": "ACTUATOR"
},
"states": [
{
"name": "core:NameState",
"type": 3,
"value": "Living Room Bridge"
}
],
"attributes": [
{
"name": "core:Manufacturer",
"type": 3,
"value": "Overkiz"
},
{
"name": "ogp:Features",
"type": 10,
"value": [
{
"name": "discovery"
},
{
"name": "identification"
}
]
},
{
"name": "core:ManufacturerReference",
"type": 3,
"value": "OGP Siegenia Bridge"
},
{
"name": "core:Technology",
"type": 3,
"value": "Siegenia"
}
],
"available": true,
"enabled": true,
"placeOID": "41d63e43-bfa8-4e24-8c16-4faae0448cab",
"type": 1,
"widget": "DynamicBridge",
"oid": "5cdf0023-2d7e-4e8e-bfb0-74ebb6ebe0eb",
"uiClass": "ProtocolGateway"
},
{
"creationTime": 1665238637000,
"lastUpdateTime": 1665238637000,
"label": "Patio Protocol Gateway",
"deviceURL": "ogp://1234-5678-6867/09E45393",
"shortcut": false,
"controllableName": "ogp:Bridge",
"definition": {
"commands": [
{
"commandName": "discover",
"nparams": 0
},
{
"commandName": "identify",
"nparams": 0
},
{
"commandName": "setName",
"nparams": 1
}
],
"states": [
{
"type": "DiscreteState",
"values": ["available", "unavailable"],
"qualifiedName": "core:AvailabilityState"
},
{
"type": "DataState",
"qualifiedName": "core:NameState"
},
{
"type": "DataState",
"qualifiedName": "core:RemovableState"
}
],
"dataProperties": [],
"widgetName": "DynamicBridge",
"uiProfiles": ["Specific"],
"uiClass": "ProtocolGateway",
"qualifiedName": "ogp:Bridge",
"type": "ACTUATOR"
},
"states": [
{
"name": "core:NameState",
"type": 3,
"value": "Patio Protocol Gateway"
}
],
"attributes": [
{
"name": "core:ManufacturerReference",
"type": 3,
"value": "OGP Intesis Bridge"
},
{
"name": "ogp:Features",
"type": 10,
"value": [
{
"name": "discovery"
},
{
"name": "identification"
}
]
},
{
"name": "core:Manufacturer",
"type": 3,
"value": "Overkiz"
},
{
"name": "core:Technology",
"type": 3,
"value": "Intesis"
}
],
"available": true,
"enabled": true,
"placeOID": "41d63e43-bfa8-4e24-8c16-4faae0448cab",
"type": 1,
"widget": "DynamicBridge",
"oid": "b1f5dc24-058e-4cb2-a052-7b2d5a7de7a5",
"uiClass": "ProtocolGateway"
},
{
"creationTime": 1667840384000,
"lastUpdateTime": 1667840384000,
"label": "Living Room Shutter",
"deviceURL": "rts://1234-5678-6867/16756006",
"shortcut": false,
"controllableName": "rts:RollerShutterRTSComponent",
"definition": {
"commands": [
{
"commandName": "close",
"nparams": 1
},
{
"commandName": "down",
"nparams": 1
},
{
"commandName": "identify",
"nparams": 0
},
{
"commandName": "my",
"nparams": 1
},
{
"commandName": "open",
"nparams": 1
},
{
"commandName": "rest",
"nparams": 1
},
{
"commandName": "stop",
"nparams": 1
},
{
"commandName": "test",
"nparams": 0
},
{
"commandName": "up",
"nparams": 1
},
{
"commandName": "openConfiguration",
"nparams": 1
}
],
"states": [],
"dataProperties": [
{
"value": "0",
"qualifiedName": "core:identifyInterval"
}
],
"widgetName": "UpDownRollerShutter",
"uiProfiles": ["OpenCloseShutter", "OpenClose"],
"uiClass": "RollerShutter",
"qualifiedName": "rts:RollerShutterRTSComponent",
"type": "ACTUATOR"
},
"attributes": [
{
"name": "rts:diy",
"type": 6,
"value": true
}
],
"available": true,
"enabled": true,
"placeOID": "9e3d6899-50bb-4869-9c5e-46c2b57f7c9e",
"type": 1,
"widget": "UpDownRollerShutter",
"oid": "1a10d6f6-9bc3-40f3-a33c-e383fd41d3e8",
"uiClass": "RollerShutter"
},
{
"creationTime": 1665238630000,
"lastUpdateTime": 1665238630000,
"label": "Attic Side Bridge",
"deviceURL": "zigbee://1234-5678-6867/65535",
"shortcut": false,
"controllableName": "zigbee:TransceiverV3_0Component",
"definition": {
"commands": [],
"states": [],
"dataProperties": [],
"widgetName": "ZigbeeStack",
"uiProfiles": ["Specific"],
"uiClass": "ProtocolGateway",
"qualifiedName": "zigbee:TransceiverV3_0Component",
"type": "PROTOCOL_GATEWAY"
},
"available": true,
"enabled": true,
"placeOID": "41d63e43-bfa8-4e24-8c16-4faae0448cab",
"type": 5,
"widget": "ZigbeeStack",
"oid": "1629c223-d115-4aad-808a-373f428d9c27",
"uiClass": "ProtocolGateway"
}
],
"zones": [],
"resellerDelegationType": "NEVER",
"disconnectionConfiguration": {
"notificationTitle": "[User] : Your Somfy box is disconnected",
"notificationText": "Your Somfy box is disconnected",
"targetPushSubscriptions": ["8849df9a-b61a-498f-ab81-67a767adba31"],
"notificationType": "PUSH"
},
"oid": "15eaf55a-8af9-483b-ae4a-ffd4254fd762",
"rootPlace": {
"creationTime": 1665238624000,
"lastUpdateTime": 1665238630000,
"label": "My House",
"type": 200,
"oid": "41d63e43-bfa8-4e24-8c16-4faae0448cab",
"subPlaces": [
{
"creationTime": 1667840432000,
"lastUpdateTime": 1667840432000,
"label": "Living Room",
"type": 108,
"metadata": "{\"color\":\"#08C27F\"}",
"oid": "9e3d6899-50bb-4869-9c5e-46c2b57f7c9e",
"subPlaces": []
}
]
},
"features": []
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,441 @@
{
"gateways": [
{
"connectivity": {
"status": "OK",
"protocolVersion": "2021.2.4-17"
},
"gatewayId": "1234-1234-1234"
}
],
"devices": [
{
"deviceURL": "internal://1234-1234-1234/pod/0",
"available": true,
"synced": true,
"type": 1,
"states": [
{
"type": 3,
"name": "core:CountryCodeState",
"value": "NL"
},
{
"type": 1,
"name": "internal:LightingLedPodModeState",
"value": 1
},
{
"type": 3,
"name": "core:NameState",
"value": "*"
},
{
"type": 3,
"name": "core:ConnectivityState",
"value": "online"
},
{
"type": 3,
"name": "core:LocalIPv4AddressState",
"value": "192.168.150.8"
}
],
"label": "*",
"subsystemId": 0,
"attributes": [],
"enabled": true,
"controllableName": "internal:PodMiniComponent",
"definition": {
"states": [
{
"name": "core:ConnectivityState"
},
{
"name": "core:CountryCodeState"
},
{
"name": "core:LocalIPv4AddressState"
},
{
"name": "core:NameState"
},
{
"name": "internal:LastActionConfigButtonState"
},
{
"name": "internal:LightingLedPodModeState"
}
],
"widgetName": "Pod",
"type": "ACTUATOR",
"commands": [
{
"commandName": "activateCalendar",
"nparams": 0
},
{
"commandName": "deactivateCalendar",
"nparams": 0
},
{
"commandName": "getName",
"nparams": 0
},
{
"commandName": "refreshPodMode",
"nparams": 0
},
{
"commandName": "refreshUpdateStatus",
"nparams": 0
},
{
"nparams": 1,
"commandName": "setCalendar",
"paramsSig": "p1"
},
{
"nparams": 1,
"commandName": "setCountryCode",
"paramsSig": "p1"
},
{
"nparams": 1,
"commandName": "setLightingLedPodMode",
"paramsSig": "p1"
},
{
"commandName": "setPodLedOff",
"nparams": 0
},
{
"commandName": "setPodLedOn",
"nparams": 0
},
{
"commandName": "update",
"nparams": 0
}
],
"uiClass": "Pod"
}
},
{
"deviceURL": "io://1234-1234-1234/5928357",
"available": true,
"synced": true,
"type": 1,
"states": [
{
"type": 3,
"name": "core:StatusState",
"value": "available"
},
{
"type": 3,
"name": "core:DiscreteRSSILevelState",
"value": "good"
},
{
"type": 1,
"name": "core:RSSILevelState",
"value": 88
},
{
"type": 1,
"name": "core:DeploymentState",
"value": 0
},
{
"type": 11,
"name": "core:ManufacturerSettingsState",
"value": {
"currentPosition": 0
}
},
{
"type": 3,
"name": "core:OpenClosedState",
"value": "closed"
},
{
"type": 1,
"name": "core:TargetClosureState",
"value": 0
},
{
"type": 6,
"name": "core:MovingState",
"value": false
},
{
"type": 3,
"name": "core:NameState",
"value": "Terrace Awning"
},
{
"type": 1,
"name": "core:Memorized1PositionState",
"value": 105
}
],
"label": "Terrace Awning",
"subsystemId": 0,
"attributes": [
{
"type": 3,
"name": "core:Manufacturer",
"value": "Somfy"
},
{
"type": 3,
"name": "core:FirmwareRevision",
"value": "5071665X10\u0003"
}
],
"enabled": true,
"controllableName": "io:HorizontalAwningIOComponent",
"definition": {
"states": [
{
"name": "core:AdditionalStatusState"
},
{
"name": "core:DeploymentState"
},
{
"name": "core:DiscreteRSSILevelState"
},
{
"name": "core:ManufacturerSettingsState"
},
{
"name": "core:Memorized1PositionState"
},
{
"name": "core:MovingState"
},
{
"name": "core:NameState"
},
{
"name": "core:OpenClosedState"
},
{
"name": "core:RSSILevelState"
},
{
"name": "core:SecuredPositionState"
},
{
"name": "core:StatusState"
},
{
"name": "core:TargetClosureState"
}
],
"widgetName": "PositionableHorizontalAwning",
"type": "ACTUATOR",
"commands": [
{
"nparams": 1,
"commandName": "advancedRefresh",
"paramsSig": "p1"
},
{
"commandName": "close",
"nparams": 0
},
{
"nparams": 1,
"commandName": "delayedStopIdentify",
"paramsSig": "p1"
},
{
"commandName": "deploy",
"nparams": 0
},
{
"commandName": "down",
"nparams": 0
},
{
"commandName": "getName",
"nparams": 0
},
{
"commandName": "identify",
"nparams": 0
},
{
"commandName": "keepOneWayControllersAndDeleteNode",
"nparams": 0
},
{
"commandName": "my",
"nparams": 0
},
{
"commandName": "open",
"nparams": 0
},
{
"nparams": 1,
"commandName": "pairOneWayController",
"paramsSig": "p1,*p2"
},
{
"commandName": "refreshMemorized1Position",
"nparams": 0
},
{
"nparams": 2,
"commandName": "runManufacturerSettingsCommand",
"paramsSig": "p1,p2"
},
{
"commandName": "sendIOKey",
"nparams": 0
},
{
"nparams": 1,
"commandName": "setClosure",
"paramsSig": "p1"
},
{
"nparams": 1,
"commandName": "setConfigState",
"paramsSig": "p1"
},
{
"nparams": 1,
"commandName": "setDeployment",
"paramsSig": "p1"
},
{
"nparams": 1,
"commandName": "setMemorized1Position",
"paramsSig": "p1"
},
{
"nparams": 1,
"commandName": "setName",
"paramsSig": "p1"
},
{
"nparams": 1,
"commandName": "setPosition",
"paramsSig": "p1"
},
{
"nparams": 1,
"commandName": "setSecuredPosition",
"paramsSig": "p1"
},
{
"commandName": "startIdentify",
"nparams": 0
},
{
"commandName": "stop",
"nparams": 0
},
{
"commandName": "stopIdentify",
"nparams": 0
},
{
"commandName": "undeploy",
"nparams": 0
},
{
"commandName": "unpairAllOneWayControllers",
"nparams": 0
},
{
"commandName": "unpairAllOneWayControllersAndDeleteNode",
"nparams": 0
},
{
"nparams": 1,
"commandName": "unpairOneWayController",
"paramsSig": "p1,*p2"
},
{
"commandName": "up",
"nparams": 0
},
{
"nparams": 1,
"commandName": "wink",
"paramsSig": "p1"
}
],
"uiClass": "Awning"
}
},
{
"deviceURL": "io://1234-1234-1234/16516299",
"available": true,
"synced": true,
"type": 5,
"states": [],
"label": "* (*)",
"subsystemId": 0,
"attributes": [
{
"type": 3,
"name": "core:Manufacturer",
"value": "Somfy"
}
],
"enabled": true,
"controllableName": "io:StackComponent",
"definition": {
"states": [],
"widgetName": "IOStack",
"type": "PROTOCOL_GATEWAY",
"commands": [
{
"nparams": 1,
"commandName": "advancedSomfyDiscover",
"paramsSig": "p1"
},
{
"nparams": 0,
"commandName": "discover1WayController",
"paramsSig": "*p1,*p2"
},
{
"nparams": 1,
"commandName": "discoverActuators",
"paramsSig": "p1"
},
{
"nparams": 1,
"commandName": "discoverSensors",
"paramsSig": "p1"
},
{
"commandName": "discoverSomfyUnsetActuators",
"nparams": 0
},
{
"commandName": "joinNetwork",
"nparams": 0
},
{
"commandName": "resetNetworkSecurity",
"nparams": 0
},
{
"commandName": "shareNetwork",
"nparams": 0
}
],
"uiClass": "ProtocolGateway"
}
}
]
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+64
View File
@@ -0,0 +1,64 @@
"""Shared helpers for Overkiz integration tests."""
from __future__ import annotations
from datetime import timedelta
from typing import Any
from freezegun.api import FrozenDateTimeFactory
from pyoverkiz.models import Event
from homeassistant.components.overkiz.const import UPDATE_INTERVAL
from homeassistant.core import HomeAssistant
from .conftest import MockOverkizClient
from tests.common import async_fire_time_changed
def assert_command_call(
mock_client: MockOverkizClient,
*,
device_url: str,
command_name: str,
parameters: list[Any] | None = None,
) -> None:
"""Assert the latest command sent through the mocked Overkiz client."""
assert mock_client.execute_command.await_count == 1
args = mock_client.execute_command.await_args.args
assert args[0] == device_url
assert args[1].name == command_name
assert args[1].parameters == (parameters or [])
assert args[2] == "Home Assistant"
def build_event(
name: str,
*,
device_url: str,
device_states: list[dict[str, Any]] | None = None,
exec_id: str | None = None,
new_state: str | None = None,
) -> Event:
"""Create a pyoverkiz event object with a test-friendly interface."""
return Event(
name=name,
device_url=device_url,
device_states=device_states,
exec_id=exec_id,
new_state=new_state,
)
async def async_deliver_events(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
mock_client: MockOverkizClient,
*event_batches: list[Event],
update_interval: timedelta = UPDATE_INTERVAL,
) -> None:
"""Queue event batches and advance time to trigger a coordinator refresh."""
mock_client.queue_events(*event_batches)
freezer.tick(update_interval)
async_fire_time_changed(hass)
await hass.async_block_till_done()
File diff suppressed because it is too large Load Diff
+679
View File
@@ -0,0 +1,679 @@
"""Tests for the Overkiz cover platform."""
from __future__ import annotations
from collections.abc import Generator
from pathlib import Path
from typing import Any, NamedTuple
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
from pyoverkiz.enums import EventName, ExecutionState, OverkizCommandParam, OverkizState
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.cover import (
ATTR_CURRENT_POSITION,
ATTR_CURRENT_TILT_POSITION,
ATTR_POSITION,
ATTR_TILT_POSITION,
DOMAIN as COVER_DOMAIN,
SERVICE_CLOSE_COVER,
SERVICE_CLOSE_COVER_TILT,
SERVICE_OPEN_COVER,
SERVICE_OPEN_COVER_TILT,
SERVICE_SET_COVER_POSITION,
SERVICE_SET_COVER_TILT_POSITION,
SERVICE_STOP_COVER,
SERVICE_STOP_COVER_TILT,
CoverEntityFeature,
CoverState,
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .conftest import MockOverkizClient, SetupOverkizIntegration
from .helpers import assert_command_call, async_deliver_events, build_event
from tests.common import snapshot_platform
class FixtureDevice(NamedTuple):
"""Test device binding a fixture file to a device URL and entity id."""
fixture: str
device_url: str
entity_id: str
AWNING = FixtureDevice(
"setup/local_somfy_connexoon_europe.json",
"io://1234-1234-1234/5928357",
"cover.terrace_awning",
)
LOW_SPEED = FixtureDevice(
"setup/cloud_nexity_rail_din_europe.json",
"io://1234-5678-1698/141613",
"cover.nursery_shutter",
)
PERGOLA = FixtureDevice(
"setup/local_somfy_tahoma_v2_europe.json",
"io://1234-5678-3293/7614902",
"cover.garden_pergola",
)
RTS = FixtureDevice(
"setup/cloud_somfy_connexoon_rts_asia.json",
"rts://1234-1234-6362/16730022",
"cover.patio_shutter",
)
SHUTTER = FixtureDevice(
"setup/cloud_somfy_tahoma_v2_europe.json",
"io://1234-1234-6233/12184029",
"cover.garden_house_shutter",
)
GARAGE = FixtureDevice(
"setup/cloud_somfy_tahoma_v2_europe.json",
"io://1234-1234-6233/1166863",
"cover.main_garage_door",
)
TILTED_WINDOW = FixtureDevice(
"setup/local_somfy_tahoma_switch_europe_3.json",
"io://1234-5678-9373/10202865",
"cover.bedroom_blinds",
)
# Device with ClosureState=108
DYNAMIC_EXTERIOR_VENETIAN_BLIND = FixtureDevice(
"setup/local_somfy_tahoma_switch_europe.json",
"io://1234-5678-6508/4877511",
"cover.dining_room_blinds",
)
# Device with ClosureState=124
POSITIONABLE_ROLLER_SHUTTER_UNO = FixtureDevice(
"setup/local_somfy_tahoma_switch_europe_2.json",
"io://1234-5678-1516/3656107",
"cover.hallway_shutter",
)
POSITIONABLE_DUAL_ROLLER_SHUTTER = FixtureDevice(
"setup/cloud_somfy_tahoma_switch_sc_europe.json",
"io://1234-5678-5010/12931361",
"cover.basement_roller_shutter",
)
SNAPSHOT_FIXTURES = [
AWNING,
LOW_SPEED,
PERGOLA,
RTS,
SHUTTER,
TILTED_WINDOW,
DYNAMIC_EXTERIOR_VENETIAN_BLIND,
POSITIONABLE_ROLLER_SHUTTER_UNO,
POSITIONABLE_DUAL_ROLLER_SHUTTER,
]
@pytest.fixture(autouse=True)
def fixture_platforms() -> Generator[None]:
"""Limit platforms to cover only."""
with patch("homeassistant.components.overkiz.PLATFORMS", [Platform.COVER]):
yield
@pytest.mark.parametrize(
"device",
SNAPSHOT_FIXTURES,
ids=[Path(device.fixture).name for device in SNAPSHOT_FIXTURES],
)
async def test_cover_entities_snapshot(
hass: HomeAssistant,
setup_overkiz_integration: SetupOverkizIntegration,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
device: FixtureDevice,
) -> None:
"""Test representative real setups via snapshot."""
config_entry = await setup_overkiz_integration(fixture=device.fixture)
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
@pytest.mark.parametrize(
("device", "service", "command_name", "expected_state"),
[
(SHUTTER, SERVICE_OPEN_COVER, "open", CoverState.OPENING),
pytest.param(
AWNING,
SERVICE_OPEN_COVER,
"deploy",
CoverState.OPENING,
marks=pytest.mark.xfail(reason="Awning deploy not mapped to opening state"),
),
(GARAGE, SERVICE_OPEN_COVER, "open", CoverState.OPENING),
(SHUTTER, SERVICE_CLOSE_COVER, "close", CoverState.CLOSING),
pytest.param(
AWNING,
SERVICE_CLOSE_COVER,
"undeploy",
CoverState.CLOSING,
marks=pytest.mark.xfail(
reason="Awning undeploy not mapped to closing state"
),
),
(GARAGE, SERVICE_CLOSE_COVER, "close", CoverState.CLOSING),
(SHUTTER, SERVICE_STOP_COVER, "stop", CoverState.CLOSED),
(AWNING, SERVICE_STOP_COVER, "stop", CoverState.CLOSED),
(GARAGE, SERVICE_STOP_COVER, "stop", CoverState.CLOSED),
],
ids=[
"open-roller-shutter",
"open-awning",
"open-garage-door",
"close-roller-shutter",
"close-awning",
"close-garage-door",
"stop-roller-shutter",
"stop-awning",
"stop-garage-door",
],
)
async def test_cover_service_actions(
hass: HomeAssistant,
setup_overkiz_integration: SetupOverkizIntegration,
mock_client: MockOverkizClient,
device: FixtureDevice,
service: str,
command_name: str,
expected_state: CoverState,
) -> None:
"""Test open, close, and stop cover services."""
await setup_overkiz_integration(fixture=device.fixture)
await hass.services.async_call(
COVER_DOMAIN,
service,
{ATTR_ENTITY_ID: device.entity_id},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get(device.entity_id).state == expected_state
assert_command_call(
mock_client,
device_url=device.device_url,
command_name=command_name,
)
@pytest.mark.parametrize(
(
"device",
"entity_id",
"command_name",
"parameters",
"position",
),
[
(SHUTTER, SHUTTER.entity_id, "setClosure", [75], 25),
(AWNING, AWNING.entity_id, "setDeployment", [80], 80),
(
LOW_SPEED,
"cover.nursery_shutter_low_speed",
"setClosureAndLinearSpeed",
[65, OverkizCommandParam.LOWSPEED],
35,
),
],
ids=["roller-shutter", "awning", "low-speed"],
)
async def test_cover_set_position(
hass: HomeAssistant,
setup_overkiz_integration: SetupOverkizIntegration,
mock_client: MockOverkizClient,
device: FixtureDevice,
entity_id: str,
command_name: str,
parameters: list[Any],
position: int,
) -> None:
"""Test cover position services and mapping."""
await setup_overkiz_integration(fixture=device.fixture)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: entity_id, ATTR_POSITION: position},
blocking=True,
)
assert_command_call(
mock_client,
device_url=device.device_url,
command_name=command_name,
parameters=parameters,
)
async def test_cover_tilt_services(
hass: HomeAssistant,
setup_overkiz_integration: SetupOverkizIntegration,
mock_client: MockOverkizClient,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test tilt services for a pergola from a full user setup."""
await setup_overkiz_integration(fixture=PERGOLA.fixture)
state = hass.states.get(PERGOLA.entity_id)
assert state
assert state.attributes[ATTR_CURRENT_TILT_POSITION] == 0
assert ATTR_CURRENT_POSITION not in state.attributes
assert state.attributes["supported_features"] == (
CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.STOP_TILT
| CoverEntityFeature.SET_TILT_POSITION
)
mock_client.execute_command.reset_mock()
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER_TILT,
{ATTR_ENTITY_ID: PERGOLA.entity_id},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get(PERGOLA.entity_id).state == CoverState.OPENING
assert_command_call(
mock_client,
device_url=PERGOLA.device_url,
command_name="openSlats",
)
await async_deliver_events(
hass,
freezer,
mock_client,
[
build_event(
EventName.EXECUTION_STATE_CHANGED.value,
device_url=PERGOLA.device_url,
exec_id="exec-1",
new_state=ExecutionState.COMPLETED.value,
)
],
)
assert hass.states.get(PERGOLA.entity_id).state == CoverState.CLOSED
mock_client.execute_command.reset_mock()
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER_TILT,
{ATTR_ENTITY_ID: PERGOLA.entity_id},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get(PERGOLA.entity_id).state == CoverState.CLOSING
assert_command_call(
mock_client,
device_url=PERGOLA.device_url,
command_name="closeSlats",
)
mock_client.execute_command.reset_mock()
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_STOP_COVER_TILT,
{ATTR_ENTITY_ID: PERGOLA.entity_id},
blocking=True,
)
assert_command_call(
mock_client,
device_url=PERGOLA.device_url,
command_name="stop",
)
mock_client.execute_command.reset_mock()
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_TILT_POSITION,
{ATTR_ENTITY_ID: PERGOLA.entity_id, ATTR_TILT_POSITION: 40},
blocking=True,
)
assert_command_call(
mock_client,
device_url=PERGOLA.device_url,
command_name="setOrientation",
parameters=[60],
)
async def test_cover_state_updates(
hass: HomeAssistant,
setup_overkiz_integration: SetupOverkizIntegration,
mock_client: MockOverkizClient,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test cover state updates via events and execution tracking."""
await setup_overkiz_integration(fixture=SHUTTER.fixture)
assert hass.states.get(SHUTTER.entity_id).attributes[ATTR_CURRENT_POSITION] == 0
# Position update via device state change event
await async_deliver_events(
hass,
freezer,
mock_client,
[
build_event(
EventName.DEVICE_STATE_CHANGED.value,
device_url=SHUTTER.device_url,
device_states=[
{
"name": OverkizState.CORE_CLOSURE.value,
"type": 1,
"value": 0,
},
{
"name": OverkizState.CORE_TARGET_CLOSURE.value,
"type": 1,
"value": 0,
},
{
"name": OverkizState.CORE_MOVING.value,
"type": 6,
"value": False,
},
{
"name": OverkizState.CORE_OPEN_CLOSED.value,
"type": 3,
"value": OverkizCommandParam.OPEN.value,
},
],
)
],
)
state = hass.states.get(SHUTTER.entity_id)
assert state.attributes[ATTR_CURRENT_POSITION] == 100
assert state.state == CoverState.OPEN
# Position update to closed
await async_deliver_events(
hass,
freezer,
mock_client,
[
build_event(
EventName.DEVICE_STATE_CHANGED.value,
device_url=SHUTTER.device_url,
device_states=[
{
"name": OverkizState.CORE_CLOSURE.value,
"type": 1,
"value": 100,
},
{
"name": OverkizState.CORE_TARGET_CLOSURE.value,
"type": 1,
"value": 100,
},
{
"name": OverkizState.CORE_MOVING.value,
"type": 6,
"value": False,
},
{
"name": OverkizState.CORE_OPEN_CLOSED.value,
"type": 3,
"value": OverkizCommandParam.CLOSED.value,
},
],
)
],
)
state = hass.states.get(SHUTTER.entity_id)
assert state.attributes[ATTR_CURRENT_POSITION] == 0
assert state.state == CoverState.CLOSED
# Execution tracking: state stays OPENING until execution completes
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: SHUTTER.entity_id},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get(SHUTTER.entity_id).state == CoverState.OPENING
await async_deliver_events(
hass,
freezer,
mock_client,
[
build_event(
EventName.DEVICE_STATE_CHANGED.value,
device_url=SHUTTER.device_url,
device_states=[
{
"name": OverkizState.CORE_CLOSURE.value,
"type": 1,
"value": 0,
},
{
"name": OverkizState.CORE_TARGET_CLOSURE.value,
"type": 1,
"value": 0,
},
{
"name": OverkizState.CORE_MOVING.value,
"type": 6,
"value": False,
},
{
"name": OverkizState.CORE_OPEN_CLOSED.value,
"type": 3,
"value": OverkizCommandParam.OPEN.value,
},
],
)
],
)
assert hass.states.get(SHUTTER.entity_id).state == CoverState.OPENING
await async_deliver_events(
hass,
freezer,
mock_client,
[
build_event(
EventName.EXECUTION_STATE_CHANGED.value,
device_url=SHUTTER.device_url,
exec_id="exec-1",
new_state=ExecutionState.COMPLETED.value,
)
],
)
assert hass.states.get(SHUTTER.entity_id).state == CoverState.OPEN
# Unavailability propagates to entity state
await async_deliver_events(
hass,
freezer,
mock_client,
[
build_event(
EventName.DEVICE_UNAVAILABLE.value, device_url=SHUTTER.device_url
)
],
)
assert hass.states.get(SHUTTER.entity_id).state == STATE_UNAVAILABLE
@pytest.mark.parametrize(
("device_states", "expected_state"),
[
(
[
{"name": OverkizState.CORE_MOVING.value, "type": 6, "value": True},
{"name": OverkizState.CORE_CLOSURE.value, "type": 1, "value": 75},
{
"name": OverkizState.CORE_TARGET_CLOSURE.value,
"type": 1,
"value": 20,
},
],
CoverState.OPENING,
),
(
[
{"name": OverkizState.CORE_MOVING.value, "type": 6, "value": True},
{"name": OverkizState.CORE_CLOSURE.value, "type": 1, "value": 20},
{
"name": OverkizState.CORE_TARGET_CLOSURE.value,
"type": 1,
"value": 75,
},
],
CoverState.CLOSING,
),
],
ids=["opening", "closing"],
)
async def test_vertical_cover_moving_direction(
hass: HomeAssistant,
setup_overkiz_integration: SetupOverkizIntegration,
mock_client: MockOverkizClient,
freezer: FrozenDateTimeFactory,
device_states: list[dict[str, Any]],
expected_state: CoverState,
) -> None:
"""Test moving direction detection for vertical covers based on current vs target position."""
await setup_overkiz_integration(fixture=SHUTTER.fixture)
await async_deliver_events(
hass,
freezer,
mock_client,
[
build_event(
EventName.DEVICE_STATE_CHANGED.value,
device_url=SHUTTER.device_url,
device_states=device_states,
)
],
)
assert hass.states.get(SHUTTER.entity_id).state == expected_state
@pytest.mark.parametrize(
("device_states", "expected_state"),
[
(
[
{"name": OverkizState.CORE_MOVING.value, "type": 6, "value": True},
{
"name": OverkizState.CORE_DEPLOYMENT.value,
"type": 1,
"value": 20,
},
{
"name": OverkizState.CORE_TARGET_CLOSURE.value,
"type": 1,
"value": 80,
},
],
CoverState.OPENING,
),
(
[
{"name": OverkizState.CORE_MOVING.value, "type": 6, "value": True},
{
"name": OverkizState.CORE_DEPLOYMENT.value,
"type": 1,
"value": 80,
},
{
"name": OverkizState.CORE_TARGET_CLOSURE.value,
"type": 1,
"value": 20,
},
],
CoverState.CLOSING,
),
],
ids=["opening", "closing"],
)
async def test_awning_moving_direction(
hass: HomeAssistant,
setup_overkiz_integration: SetupOverkizIntegration,
mock_client: MockOverkizClient,
freezer: FrozenDateTimeFactory,
device_states: list[dict[str, Any]],
expected_state: CoverState,
) -> None:
"""Test moving direction detection for awnings based on current vs target position."""
await setup_overkiz_integration(fixture=AWNING.fixture)
await async_deliver_events(
hass,
freezer,
mock_client,
[
build_event(
EventName.DEVICE_STATE_CHANGED.value,
device_url=AWNING.device_url,
device_states=device_states,
)
],
)
assert hass.states.get(AWNING.entity_id).state == expected_state
async def test_awning_direct_position_mapping(
hass: HomeAssistant,
setup_overkiz_integration: SetupOverkizIntegration,
mock_client: MockOverkizClient,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test awning deployment uses direct mapping while vertical covers invert."""
await setup_overkiz_integration(fixture=AWNING.fixture)
assert hass.states.get(AWNING.entity_id).attributes[ATTR_CURRENT_POSITION] == 0
mock_client.execute_command.reset_mock()
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: AWNING.entity_id, ATTR_POSITION: 35},
blocking=True,
)
assert_command_call(
mock_client,
device_url=AWNING.device_url,
command_name="setDeployment",
parameters=[35],
)
await async_deliver_events(
hass,
freezer,
mock_client,
[
build_event(
EventName.DEVICE_STATE_CHANGED.value,
device_url=AWNING.device_url,
device_states=[
{
"name": OverkizState.CORE_DEPLOYMENT.value,
"type": 1,
"value": 100,
}
],
)
],
)
assert hass.states.get(AWNING.entity_id).attributes[ATTR_CURRENT_POSITION] == 100