Add Motionblinds Bluetooth full test coverage (#121878)

* Add tests

* Fix entity test

* Format

* Fix sensor tests

* Fix sensor tests

* Fix sensor tests

* Add init tests

* Change service info

* Rename test_sensor parameters

* Removce ConfigEntryState.LOADED assertion

* Remove platforms parameter from setup_platform

* Rename setup_platform to setup_integration

* Fixture for blind_type and mock_config_entry

* Use mock for MotionDevice

* Use mock for MotionDevice

* Add type hint

* Use Mock instead of patch

* Use mock_config_entry fixture

* Move constants to init

* Fix entity_id name

* Use fixture

* Use fixtures instead of constants

* Use display_name fixture

* Rename mac to mac_code

* Remove one patch

* Use fixtures for mock_config_entry

* Apply suggestion

* Replace patch with mock

* Replace patch with mock

* Replace patch with mock

* Fix

* Use pytest.mark.usefixtures if parameter not used

* Base mac code on address

* Remove if statement from entity test

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
pull/123613/head^2
Lenn 2024-08-15 15:39:47 +02:00 committed by GitHub
parent f72d9a2c02
commit c674a25eba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 677 additions and 91 deletions

View File

@ -1 +1,16 @@
"""Tests for the Motionblinds Bluetooth integration.""" """Tests for the Motionblinds Bluetooth integration."""
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
async def setup_integration(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Mock a fully setup config entry."""
mock_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()

View File

@ -3,21 +3,140 @@
from collections.abc import Generator from collections.abc import Generator
from unittest.mock import AsyncMock, Mock, patch from unittest.mock import AsyncMock, Mock, patch
from motionblindsble.const import MotionBlindType
import pytest import pytest
TEST_MAC = "abcd" from homeassistant.components.bluetooth.models import BluetoothServiceInfoBleak
TEST_NAME = f"MOTION_{TEST_MAC.upper()}" from homeassistant.components.motionblinds_ble.const import (
TEST_ADDRESS = "test_adress" CONF_BLIND_TYPE,
CONF_LOCAL_NAME,
CONF_MAC_CODE,
DOMAIN,
)
from homeassistant.const import CONF_ADDRESS
from tests.common import MockConfigEntry
from tests.components.bluetooth import generate_advertisement_data, generate_ble_device
@pytest.fixture(name="motionblinds_ble_connect", autouse=True) @pytest.fixture
def motion_blinds_connect_fixture( def address() -> str:
enable_bluetooth: None, """Address fixture."""
return "cc:cc:cc:cc:cc:cc"
@pytest.fixture
def mac_code(address: str) -> str:
"""MAC code fixture."""
return "".join(address.split(":")[-3:-1]).upper()
@pytest.fixture
def display_name(mac_code: str) -> str:
"""Display name fixture."""
return f"Motionblind {mac_code.upper()}"
@pytest.fixture
def name(display_name: str) -> str:
"""Name fixture."""
return display_name.lower().replace(" ", "_")
@pytest.fixture
def local_name(mac_code: str) -> str:
"""Local name fixture."""
return f"MOTION_{mac_code.upper()}"
@pytest.fixture
def blind_type() -> MotionBlindType:
"""Blind type fixture."""
return MotionBlindType.ROLLER
@pytest.fixture
def service_info(local_name: str, address: str) -> BluetoothServiceInfoBleak:
"""Service info fixture."""
return BluetoothServiceInfoBleak(
name=local_name,
address=address,
device=generate_ble_device(
address=address,
name=local_name,
),
rssi=-61,
manufacturer_data={000: b"test"},
service_data={
"test": bytearray(b"0000"),
},
service_uuids=[
"test",
],
source="local",
advertisement=generate_advertisement_data(
manufacturer_data={000: b"test"},
service_uuids=["test"],
),
connectable=True,
time=0,
tx_power=-127,
)
@pytest.fixture
def mock_motion_device(
blind_type: MotionBlindType, display_name: str
) -> Generator[AsyncMock]:
"""Mock a MotionDevice."""
with patch(
"homeassistant.components.motionblinds_ble.MotionDevice",
autospec=True,
) as mock_device:
device = mock_device.return_value
device.ble_device = Mock()
device.display_name = display_name
device.blind_type = blind_type
yield device
@pytest.fixture
def mock_config_entry(
blind_type: MotionBlindType, address: str, display_name: str, mac_code: str
) -> MockConfigEntry:
"""Config entry fixture."""
return MockConfigEntry(
title="mock_title",
domain=DOMAIN,
unique_id=address,
data={
CONF_ADDRESS: address,
CONF_LOCAL_NAME: display_name,
CONF_MAC_CODE: mac_code,
CONF_BLIND_TYPE: blind_type.name.lower(),
},
)
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.motionblinds_ble.async_setup_entry",
return_value=True,
) as mock_setup_entry:
yield mock_setup_entry
@pytest.fixture
def motionblinds_ble_connect(
enable_bluetooth: None, local_name: str, address: str
) -> Generator[tuple[AsyncMock, Mock]]: ) -> Generator[tuple[AsyncMock, Mock]]:
"""Mock motion blinds ble connection and entry setup.""" """Mock motion blinds ble connection and entry setup."""
device = Mock() device = Mock()
device.name = TEST_NAME device.name = local_name
device.address = TEST_ADDRESS device.address = address
bleak_scanner = AsyncMock() bleak_scanner = AsyncMock()
bleak_scanner.discover.return_value = [device] bleak_scanner.discover.return_value = [device]
@ -31,9 +150,5 @@ def motion_blinds_connect_fixture(
"homeassistant.components.motionblinds_ble.config_flow.bluetooth.async_get_scanner", "homeassistant.components.motionblinds_ble.config_flow.bluetooth.async_get_scanner",
return_value=bleak_scanner, return_value=bleak_scanner,
), ),
patch(
"homeassistant.components.motionblinds_ble.async_setup_entry",
return_value=True,
),
): ):
yield bleak_scanner, device yield bleak_scanner, device

View File

@ -0,0 +1,47 @@
"""Tests for Motionblinds BLE buttons."""
from unittest.mock import Mock
import pytest
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
from homeassistant.components.motionblinds_ble.const import (
ATTR_CONNECT,
ATTR_DISCONNECT,
ATTR_FAVORITE,
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from . import setup_integration
from tests.common import MockConfigEntry
@pytest.mark.usefixtures("motionblinds_ble_connect")
@pytest.mark.parametrize(
("button"),
[
ATTR_CONNECT,
ATTR_DISCONNECT,
ATTR_FAVORITE,
],
)
async def test_button(
mock_config_entry: MockConfigEntry,
mock_motion_device: Mock,
name: str,
hass: HomeAssistant,
button: str,
) -> None:
"""Test states of the button."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: f"button.{name}_{button}"},
blocking=True,
)
getattr(mock_motion_device, button).assert_called_once()

View File

@ -12,41 +12,19 @@ from homeassistant.const import CONF_ADDRESS
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from .conftest import TEST_ADDRESS, TEST_MAC, TEST_NAME
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.bluetooth import generate_advertisement_data, generate_ble_device
TEST_BLIND_TYPE = MotionBlindType.ROLLER.name.lower()
BLIND_SERVICE_INFO = BluetoothServiceInfoBleak(
name=TEST_NAME,
address=TEST_ADDRESS,
device=generate_ble_device(
address="cc:cc:cc:cc:cc:cc",
name=TEST_NAME,
),
rssi=-61,
manufacturer_data={000: b"test"},
service_data={
"test": bytearray(b"0000"),
},
service_uuids=[
"test",
],
source="local",
advertisement=generate_advertisement_data(
manufacturer_data={000: b"test"},
service_uuids=["test"],
),
connectable=True,
time=0,
tx_power=-127,
)
@pytest.mark.usefixtures("motionblinds_ble_connect") @pytest.mark.usefixtures("motionblinds_ble_connect")
async def test_config_flow_manual_success(hass: HomeAssistant) -> None: @pytest.mark.usefixtures("mock_setup_entry")
async def test_config_flow_manual_success(
hass: HomeAssistant,
blind_type: MotionBlindType,
mac_code: str,
address: str,
local_name: str,
display_name: str,
) -> None:
"""Successful flow manually initialized by the user.""" """Successful flow manually initialized by the user."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
const.DOMAIN, context={"source": config_entries.SOURCE_USER} const.DOMAIN, context={"source": config_entries.SOURCE_USER}
@ -57,28 +35,36 @@ async def test_config_flow_manual_success(hass: HomeAssistant) -> None:
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{const.CONF_MAC_CODE: TEST_MAC}, {const.CONF_MAC_CODE: mac_code},
) )
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm" assert result["step_id"] == "confirm"
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{const.CONF_BLIND_TYPE: MotionBlindType.ROLLER.name.lower()}, {const.CONF_BLIND_TYPE: blind_type.name.lower()},
) )
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == f"Motionblind {TEST_MAC.upper()}" assert result["title"] == display_name
assert result["data"] == { assert result["data"] == {
CONF_ADDRESS: TEST_ADDRESS, CONF_ADDRESS: address,
const.CONF_LOCAL_NAME: TEST_NAME, const.CONF_LOCAL_NAME: local_name,
const.CONF_MAC_CODE: TEST_MAC.upper(), const.CONF_MAC_CODE: mac_code,
const.CONF_BLIND_TYPE: TEST_BLIND_TYPE, const.CONF_BLIND_TYPE: blind_type.name.lower(),
} }
assert result["options"] == {} assert result["options"] == {}
@pytest.mark.usefixtures("motionblinds_ble_connect") @pytest.mark.usefixtures("motionblinds_ble_connect")
async def test_config_flow_manual_error_invalid_mac(hass: HomeAssistant) -> None: @pytest.mark.usefixtures("mock_setup_entry")
async def test_config_flow_manual_error_invalid_mac(
hass: HomeAssistant,
mac_code: str,
address: str,
local_name: str,
display_name: str,
blind_type: MotionBlindType,
) -> None:
"""Invalid MAC code error flow manually initialized by the user.""" """Invalid MAC code error flow manually initialized by the user."""
# Initialize # Initialize
@ -101,7 +87,7 @@ async def test_config_flow_manual_error_invalid_mac(hass: HomeAssistant) -> None
# Recover # Recover
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{const.CONF_MAC_CODE: TEST_MAC}, {const.CONF_MAC_CODE: mac_code},
) )
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm" assert result["step_id"] == "confirm"
@ -109,15 +95,15 @@ async def test_config_flow_manual_error_invalid_mac(hass: HomeAssistant) -> None
# Finish flow # Finish flow
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{const.CONF_BLIND_TYPE: MotionBlindType.ROLLER.name.lower()}, {const.CONF_BLIND_TYPE: blind_type.name.lower()},
) )
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == f"Motionblind {TEST_MAC.upper()}" assert result["title"] == display_name
assert result["data"] == { assert result["data"] == {
CONF_ADDRESS: TEST_ADDRESS, CONF_ADDRESS: address,
const.CONF_LOCAL_NAME: TEST_NAME, const.CONF_LOCAL_NAME: local_name,
const.CONF_MAC_CODE: TEST_MAC.upper(), const.CONF_MAC_CODE: mac_code,
const.CONF_BLIND_TYPE: TEST_BLIND_TYPE, const.CONF_BLIND_TYPE: blind_type.name.lower(),
} }
assert result["options"] == {} assert result["options"] == {}
@ -125,6 +111,7 @@ async def test_config_flow_manual_error_invalid_mac(hass: HomeAssistant) -> None
@pytest.mark.usefixtures("motionblinds_ble_connect") @pytest.mark.usefixtures("motionblinds_ble_connect")
async def test_config_flow_manual_error_no_bluetooth_adapter( async def test_config_flow_manual_error_no_bluetooth_adapter(
hass: HomeAssistant, hass: HomeAssistant,
mac_code: str,
) -> None: ) -> None:
"""No Bluetooth adapter error flow manually initialized by the user.""" """No Bluetooth adapter error flow manually initialized by the user."""
@ -153,14 +140,21 @@ async def test_config_flow_manual_error_no_bluetooth_adapter(
): ):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{const.CONF_MAC_CODE: TEST_MAC}, {const.CONF_MAC_CODE: mac_code},
) )
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result["reason"] == const.ERROR_NO_BLUETOOTH_ADAPTER assert result["reason"] == const.ERROR_NO_BLUETOOTH_ADAPTER
@pytest.mark.usefixtures("mock_setup_entry")
async def test_config_flow_manual_error_could_not_find_motor( async def test_config_flow_manual_error_could_not_find_motor(
hass: HomeAssistant, motionblinds_ble_connect: tuple[AsyncMock, Mock] hass: HomeAssistant,
motionblinds_ble_connect: tuple[AsyncMock, Mock],
mac_code: str,
local_name: str,
display_name: str,
address: str,
blind_type: MotionBlindType,
) -> None: ) -> None:
"""Could not find motor error flow manually initialized by the user.""" """Could not find motor error flow manually initialized by the user."""
@ -176,17 +170,17 @@ async def test_config_flow_manual_error_could_not_find_motor(
motionblinds_ble_connect[1].name = "WRONG_NAME" motionblinds_ble_connect[1].name = "WRONG_NAME"
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{const.CONF_MAC_CODE: TEST_MAC}, {const.CONF_MAC_CODE: mac_code},
) )
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user" assert result["step_id"] == "user"
assert result["errors"] == {"base": const.ERROR_COULD_NOT_FIND_MOTOR} assert result["errors"] == {"base": const.ERROR_COULD_NOT_FIND_MOTOR}
# Recover # Recover
motionblinds_ble_connect[1].name = TEST_NAME motionblinds_ble_connect[1].name = local_name
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{const.CONF_MAC_CODE: TEST_MAC}, {const.CONF_MAC_CODE: mac_code},
) )
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm" assert result["step_id"] == "confirm"
@ -194,21 +188,23 @@ async def test_config_flow_manual_error_could_not_find_motor(
# Finish flow # Finish flow
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{const.CONF_BLIND_TYPE: MotionBlindType.ROLLER.name.lower()}, {const.CONF_BLIND_TYPE: blind_type.name.lower()},
) )
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == f"Motionblind {TEST_MAC.upper()}" assert result["title"] == display_name
assert result["data"] == { assert result["data"] == {
CONF_ADDRESS: TEST_ADDRESS, CONF_ADDRESS: address,
const.CONF_LOCAL_NAME: TEST_NAME, const.CONF_LOCAL_NAME: local_name,
const.CONF_MAC_CODE: TEST_MAC.upper(), const.CONF_MAC_CODE: mac_code,
const.CONF_BLIND_TYPE: TEST_BLIND_TYPE, const.CONF_BLIND_TYPE: blind_type.name.lower(),
} }
assert result["options"] == {} assert result["options"] == {}
async def test_config_flow_manual_error_no_devices_found( async def test_config_flow_manual_error_no_devices_found(
hass: HomeAssistant, motionblinds_ble_connect: tuple[AsyncMock, Mock] hass: HomeAssistant,
motionblinds_ble_connect: tuple[AsyncMock, Mock],
mac_code: str,
) -> None: ) -> None:
"""No devices found error flow manually initialized by the user.""" """No devices found error flow manually initialized by the user."""
@ -224,19 +220,27 @@ async def test_config_flow_manual_error_no_devices_found(
motionblinds_ble_connect[0].discover.return_value = [] motionblinds_ble_connect[0].discover.return_value = []
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{const.CONF_MAC_CODE: TEST_MAC}, {const.CONF_MAC_CODE: mac_code},
) )
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result["reason"] == const.ERROR_NO_DEVICES_FOUND assert result["reason"] == const.ERROR_NO_DEVICES_FOUND
@pytest.mark.usefixtures("motionblinds_ble_connect") @pytest.mark.usefixtures("motionblinds_ble_connect")
async def test_config_flow_bluetooth_success(hass: HomeAssistant) -> None: async def test_config_flow_bluetooth_success(
hass: HomeAssistant,
mac_code: str,
service_info: BluetoothServiceInfoBleak,
address: str,
local_name: str,
display_name: str,
blind_type: MotionBlindType,
) -> None:
"""Successful bluetooth discovery flow.""" """Successful bluetooth discovery flow."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
const.DOMAIN, const.DOMAIN,
context={"source": config_entries.SOURCE_BLUETOOTH}, context={"source": config_entries.SOURCE_BLUETOOTH},
data=BLIND_SERVICE_INFO, data=service_info,
) )
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
@ -244,36 +248,32 @@ async def test_config_flow_bluetooth_success(hass: HomeAssistant) -> None:
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{const.CONF_BLIND_TYPE: MotionBlindType.ROLLER.name.lower()}, {const.CONF_BLIND_TYPE: blind_type.name.lower()},
) )
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == f"Motionblind {TEST_MAC.upper()}" assert result["title"] == display_name
assert result["data"] == { assert result["data"] == {
CONF_ADDRESS: TEST_ADDRESS, CONF_ADDRESS: address,
const.CONF_LOCAL_NAME: TEST_NAME, const.CONF_LOCAL_NAME: local_name,
const.CONF_MAC_CODE: TEST_MAC.upper(), const.CONF_MAC_CODE: mac_code,
const.CONF_BLIND_TYPE: TEST_BLIND_TYPE, const.CONF_BLIND_TYPE: blind_type.name.lower(),
} }
assert result["options"] == {} assert result["options"] == {}
async def test_options_flow(hass: HomeAssistant) -> None: @pytest.mark.usefixtures("mock_setup_entry")
async def test_options_flow(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the options flow.""" """Test the options flow."""
entry = MockConfigEntry( mock_config_entry.add_to_hass(hass)
domain=const.DOMAIN,
unique_id="0123456789",
data={
const.CONF_BLIND_TYPE: MotionBlindType.ROLLER,
},
)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
result = await hass.config_entries.options.async_init(entry.entry_id) result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "init" assert result["step_id"] == "init"

View File

@ -0,0 +1,124 @@
"""Tests for Motionblinds BLE covers."""
from typing import Any
from unittest.mock import Mock
from motionblindsble.const import MotionBlindType, MotionRunningType
import pytest
from homeassistant.components.cover import (
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,
STATE_CLOSED,
STATE_CLOSING,
STATE_OPEN,
STATE_OPENING,
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from . import setup_integration
from tests.common import MockConfigEntry
@pytest.mark.parametrize("blind_type", [MotionBlindType.VENETIAN])
@pytest.mark.parametrize(
("service", "method", "kwargs"),
[
(SERVICE_OPEN_COVER, "open", {}),
(SERVICE_CLOSE_COVER, "close", {}),
(SERVICE_OPEN_COVER_TILT, "open_tilt", {}),
(SERVICE_CLOSE_COVER_TILT, "close_tilt", {}),
(SERVICE_SET_COVER_POSITION, "position", {ATTR_POSITION: 5}),
(SERVICE_SET_COVER_TILT_POSITION, "tilt", {ATTR_TILT_POSITION: 10}),
(SERVICE_STOP_COVER, "stop", {}),
(SERVICE_STOP_COVER_TILT, "stop", {}),
],
)
async def test_cover_service(
mock_config_entry: MockConfigEntry,
mock_motion_device: Mock,
name: str,
hass: HomeAssistant,
service: str,
method: str,
kwargs: dict[str, Any],
) -> None:
"""Test cover service."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
COVER_DOMAIN,
service,
{ATTR_ENTITY_ID: f"cover.{name}", **kwargs},
blocking=True,
)
getattr(mock_motion_device, method).assert_called_once()
@pytest.mark.parametrize(
("running_type", "state"),
[
(None, "unknown"),
(MotionRunningType.STILL, "unknown"),
(MotionRunningType.OPENING, STATE_OPENING),
(MotionRunningType.CLOSING, STATE_CLOSING),
],
)
async def test_cover_update_running(
mock_config_entry: MockConfigEntry,
mock_motion_device: Mock,
name: str,
hass: HomeAssistant,
running_type: str | None,
state: str,
) -> None:
"""Test updating running status."""
await setup_integration(hass, mock_config_entry)
async_update_running = mock_motion_device.register_running_callback.call_args[0][0]
async_update_running(running_type)
assert hass.states.get(f"cover.{name}").state == state
@pytest.mark.parametrize(
("position", "tilt", "state"),
[
(None, None, "unknown"),
(0, 0, STATE_OPEN),
(50, 90, STATE_OPEN),
(100, 180, STATE_CLOSED),
],
)
async def test_cover_update_position(
mock_config_entry: MockConfigEntry,
mock_motion_device: Mock,
name: str,
hass: HomeAssistant,
position: int,
tilt: int,
state: str,
) -> None:
"""Test updating cover position and tilt."""
await setup_integration(hass, mock_config_entry)
async_update_position = mock_motion_device.register_position_callback.call_args[0][
0
]
async_update_position(position, tilt)
assert hass.states.get(f"cover.{name}").state == state

View File

@ -0,0 +1,54 @@
"""Tests for Motionblinds BLE entities."""
from unittest.mock import Mock
import pytest
from homeassistant.components.homeassistant import (
DOMAIN as HA_DOMAIN,
SERVICE_UPDATE_ENTITY,
)
from homeassistant.components.motionblinds_ble.const import (
ATTR_CONNECT,
ATTR_DISCONNECT,
ATTR_FAVORITE,
ATTR_SPEED,
)
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from . import setup_integration
from tests.common import MockConfigEntry
@pytest.mark.parametrize(
("platform", "entity"),
[
(Platform.BUTTON, ATTR_CONNECT),
(Platform.BUTTON, ATTR_DISCONNECT),
(Platform.BUTTON, ATTR_FAVORITE),
(Platform.SELECT, ATTR_SPEED),
],
)
async def test_entity_update(
mock_config_entry: MockConfigEntry,
mock_motion_device: Mock,
name: str,
hass: HomeAssistant,
platform: Platform,
entity: str,
) -> None:
"""Test updating entity using homeassistant.update_entity."""
await async_setup_component(hass, HA_DOMAIN, {})
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
HA_DOMAIN,
SERVICE_UPDATE_ENTITY,
{ATTR_ENTITY_ID: f"{platform.name.lower()}.{name}_{entity}"},
blocking=True,
)
getattr(mock_motion_device, "status_query").assert_called_once_with()

View File

@ -0,0 +1,48 @@
"""Tests for Motionblinds BLE init."""
from unittest.mock import patch
from homeassistant.components.bluetooth.models import BluetoothServiceInfoBleak
from homeassistant.components.motionblinds_ble import options_update_listener
from homeassistant.core import HomeAssistant
from . import setup_integration
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_options_update_listener(
mock_config_entry: MockConfigEntry, hass: HomeAssistant
) -> None:
"""Test options_update_listener."""
await setup_integration(hass, mock_config_entry)
with (
patch(
"homeassistant.components.motionblinds_ble.MotionDevice.set_custom_disconnect_time"
) as mock_set_custom_disconnect_time,
patch(
"homeassistant.components.motionblinds_ble.MotionDevice.set_permanent_connection"
) as set_permanent_connection,
):
await options_update_listener(hass, mock_config_entry)
mock_set_custom_disconnect_time.assert_called_once()
set_permanent_connection.assert_called_once()
async def test_update_ble_device(
mock_config_entry: MockConfigEntry,
hass: HomeAssistant,
service_info: BluetoothServiceInfoBleak,
) -> None:
"""Test async_update_ble_device."""
await setup_integration(hass, mock_config_entry)
with patch(
"homeassistant.components.motionblinds_ble.MotionDevice.set_ble_device"
) as mock_set_ble_device:
inject_bluetooth_service_info(hass, service_info)
mock_set_ble_device.assert_called_once()

View File

@ -0,0 +1,76 @@
"""Tests for Motionblinds BLE selects."""
from collections.abc import Callable
from enum import Enum
from typing import Any
from unittest.mock import Mock
from motionblindsble.const import MotionSpeedLevel
from motionblindsble.device import MotionDevice
import pytest
from homeassistant.components.motionblinds_ble.const import ATTR_SPEED
from homeassistant.components.select import (
DOMAIN as SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
)
from homeassistant.const import ATTR_ENTITY_ID, ATTR_OPTION
from homeassistant.core import HomeAssistant
from . import setup_integration
from tests.common import MockConfigEntry
@pytest.mark.parametrize(("select", "args"), [(ATTR_SPEED, MotionSpeedLevel.HIGH)])
async def test_select(
mock_config_entry: MockConfigEntry,
mock_motion_device: Mock,
name: str,
hass: HomeAssistant,
select: str,
args: Any,
) -> None:
"""Test select."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: f"select.{name}_{select}",
ATTR_OPTION: MotionSpeedLevel.HIGH.value,
},
blocking=True,
)
getattr(mock_motion_device, select).assert_called_once_with(args)
@pytest.mark.parametrize(
("select", "register_callback", "value"),
[
(
ATTR_SPEED,
lambda device: device.register_speed_callback,
MotionSpeedLevel.HIGH,
)
],
)
async def test_select_update(
mock_config_entry: MockConfigEntry,
mock_motion_device: Mock,
name: str,
hass: HomeAssistant,
select: str,
register_callback: Callable[[MotionDevice], Callable[..., None]],
value: type[Enum],
) -> None:
"""Test select state update."""
await setup_integration(hass, mock_config_entry)
update_func = register_callback(mock_motion_device).call_args[0][0]
update_func(value)
assert hass.states.get(f"select.{name}_{select}").state == str(value.value)

View File

@ -0,0 +1,107 @@
"""Tests for Motionblinds BLE sensors."""
from collections.abc import Callable
from typing import Any
from unittest.mock import Mock
from motionblindsble.const import (
MotionBlindType,
MotionCalibrationType,
MotionConnectionType,
)
from motionblindsble.device import MotionDevice
import pytest
from homeassistant.components.motionblinds_ble.const import (
ATTR_BATTERY,
ATTR_SIGNAL_STRENGTH,
)
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.core import HomeAssistant
from . import setup_integration
from tests.common import MockConfigEntry
@pytest.mark.parametrize("blind_type", [MotionBlindType.CURTAIN])
@pytest.mark.parametrize(
("sensor", "register_callback", "initial_value", "args", "expected_value"),
[
(
"connection_status",
lambda device: device.register_connection_callback,
MotionConnectionType.DISCONNECTED.value,
[MotionConnectionType.CONNECTING],
MotionConnectionType.CONNECTING.value,
),
(
ATTR_BATTERY,
lambda device: device.register_battery_callback,
"unknown",
[25, True, False],
"25",
),
( # Battery unknown
ATTR_BATTERY,
lambda device: device.register_battery_callback,
"unknown",
[None, False, False],
"unknown",
),
( # Wired
ATTR_BATTERY,
lambda device: device.register_battery_callback,
"unknown",
[255, False, True],
"255",
),
( # Almost full
ATTR_BATTERY,
lambda device: device.register_battery_callback,
"unknown",
[99, False, False],
"99",
),
( # Almost empty
ATTR_BATTERY,
lambda device: device.register_battery_callback,
"unknown",
[1, False, False],
"1",
),
(
"calibration_status",
lambda device: device.register_calibration_callback,
"unknown",
[MotionCalibrationType.CALIBRATING],
MotionCalibrationType.CALIBRATING.value,
),
(
ATTR_SIGNAL_STRENGTH,
lambda device: device.register_signal_strength_callback,
"unknown",
[-50],
"-50",
),
],
)
async def test_sensor(
mock_config_entry: MockConfigEntry,
mock_motion_device: Mock,
name: str,
hass: HomeAssistant,
sensor: str,
register_callback: Callable[[MotionDevice], Callable[..., None]],
initial_value: str,
args: list[Any],
expected_value: str,
) -> None:
"""Test sensors."""
await setup_integration(hass, mock_config_entry)
assert hass.states.get(f"{SENSOR_DOMAIN}.{name}_{sensor}").state == initial_value
update_func = register_callback(mock_motion_device).call_args[0][0]
update_func(*args)
assert hass.states.get(f"{SENSOR_DOMAIN}.{name}_{sensor}").state == expected_value