"""Test HomeKit util module.""" from unittest.mock import MagicMock, Mock, patch import pytest import voluptuous as vol from homeassistant.components.homekit.const import ( BRIDGE_NAME, CONF_FEATURE, CONF_FEATURE_LIST, CONF_LINKED_BATTERY_SENSOR, CONF_LOW_BATTERY_THRESHOLD, DEFAULT_CONFIG_FLOW_PORT, DOMAIN, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, HOMEKIT_PAIRING_QR, HOMEKIT_PAIRING_QR_SECRET, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER, TYPE_SWITCH, TYPE_VALVE, ) from homeassistant.components.homekit.util import ( accessory_friendly_name, async_dismiss_setup_message, async_find_next_available_port, async_port_is_available, async_show_setup_message, cleanup_name_for_homekit, convert_to_float, density_to_air_quality, format_version, state_needs_accessory_mode, temperature_to_homekit, temperature_to_states, validate_entity_config as vec, validate_media_player_features, ) from homeassistant.components.persistent_notification import async_create, async_dismiss from homeassistant.const import ( ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_NAME, CONF_PORT, CONF_TYPE, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT, ) from homeassistant.core import State from .util import async_init_integration from tests.common import MockConfigEntry def _mock_socket(failure_attempts: int = 0) -> MagicMock: """Mock a socket that fails to bind failure_attempts amount of times.""" mock_socket = MagicMock() attempts = 0 def _simulate_bind(*_): nonlocal attempts attempts += 1 if attempts <= failure_attempts: raise OSError return mock_socket.bind = Mock(side_effect=_simulate_bind) return mock_socket def test_validate_entity_config(): """Test validate entities.""" configs = [ None, [], "string", 12345, {"invalid_entity_id": {}}, {"demo.test": 1}, {"binary_sensor.demo": {CONF_LINKED_BATTERY_SENSOR: None}}, {"binary_sensor.demo": {CONF_LINKED_BATTERY_SENSOR: "switch.demo"}}, {"binary_sensor.demo": {CONF_LOW_BATTERY_THRESHOLD: "switch.demo"}}, {"binary_sensor.demo": {CONF_LOW_BATTERY_THRESHOLD: -10}}, {"demo.test": "test"}, {"demo.test": [1, 2]}, {"demo.test": None}, {"demo.test": {CONF_NAME: None}}, {"media_player.test": {CONF_FEATURE_LIST: [{CONF_FEATURE: "invalid_feature"}]}}, { "media_player.test": { CONF_FEATURE_LIST: [ {CONF_FEATURE: FEATURE_ON_OFF}, {CONF_FEATURE: FEATURE_ON_OFF}, ] } }, {"switch.test": {CONF_TYPE: "invalid_type"}}, ] for conf in configs: with pytest.raises(vol.Invalid): vec(conf) assert vec({}) == {} assert vec({"demo.test": {CONF_NAME: "Name"}}) == { "demo.test": {CONF_NAME: "Name", CONF_LOW_BATTERY_THRESHOLD: 20} } assert vec( {"binary_sensor.demo": {CONF_LINKED_BATTERY_SENSOR: "sensor.demo_battery"}} ) == { "binary_sensor.demo": { CONF_LINKED_BATTERY_SENSOR: "sensor.demo_battery", CONF_LOW_BATTERY_THRESHOLD: 20, } } assert vec({"binary_sensor.demo": {CONF_LOW_BATTERY_THRESHOLD: 50}}) == { "binary_sensor.demo": {CONF_LOW_BATTERY_THRESHOLD: 50} } assert vec({"alarm_control_panel.demo": {}}) == { "alarm_control_panel.demo": {ATTR_CODE: None, CONF_LOW_BATTERY_THRESHOLD: 20} } assert vec({"alarm_control_panel.demo": {ATTR_CODE: "1234"}}) == { "alarm_control_panel.demo": {ATTR_CODE: "1234", CONF_LOW_BATTERY_THRESHOLD: 20} } assert vec({"lock.demo": {}}) == { "lock.demo": {ATTR_CODE: None, CONF_LOW_BATTERY_THRESHOLD: 20} } assert vec({"lock.demo": {ATTR_CODE: "1234"}}) == { "lock.demo": {ATTR_CODE: "1234", CONF_LOW_BATTERY_THRESHOLD: 20} } assert vec({"media_player.demo": {}}) == { "media_player.demo": {CONF_FEATURE_LIST: {}, CONF_LOW_BATTERY_THRESHOLD: 20} } config = { CONF_FEATURE_LIST: [ {CONF_FEATURE: FEATURE_ON_OFF}, {CONF_FEATURE: FEATURE_PLAY_PAUSE}, ] } assert vec({"media_player.demo": config}) == { "media_player.demo": { CONF_FEATURE_LIST: {FEATURE_ON_OFF: {}, FEATURE_PLAY_PAUSE: {}}, CONF_LOW_BATTERY_THRESHOLD: 20, } } assert vec({"switch.demo": {CONF_TYPE: TYPE_FAUCET}}) == { "switch.demo": {CONF_TYPE: TYPE_FAUCET, CONF_LOW_BATTERY_THRESHOLD: 20} } assert vec({"switch.demo": {CONF_TYPE: TYPE_OUTLET}}) == { "switch.demo": {CONF_TYPE: TYPE_OUTLET, CONF_LOW_BATTERY_THRESHOLD: 20} } assert vec({"switch.demo": {CONF_TYPE: TYPE_SHOWER}}) == { "switch.demo": {CONF_TYPE: TYPE_SHOWER, CONF_LOW_BATTERY_THRESHOLD: 20} } assert vec({"switch.demo": {CONF_TYPE: TYPE_SPRINKLER}}) == { "switch.demo": {CONF_TYPE: TYPE_SPRINKLER, CONF_LOW_BATTERY_THRESHOLD: 20} } assert vec({"switch.demo": {CONF_TYPE: TYPE_SWITCH}}) == { "switch.demo": {CONF_TYPE: TYPE_SWITCH, CONF_LOW_BATTERY_THRESHOLD: 20} } assert vec({"switch.demo": {CONF_TYPE: TYPE_VALVE}}) == { "switch.demo": {CONF_TYPE: TYPE_VALVE, CONF_LOW_BATTERY_THRESHOLD: 20} } def test_validate_media_player_features(): """Test validate modes for media players.""" config = {} attrs = {ATTR_SUPPORTED_FEATURES: 20873} entity_state = State("media_player.demo", "on", attrs) assert validate_media_player_features(entity_state, config) is True config = {FEATURE_ON_OFF: None} assert validate_media_player_features(entity_state, config) is True entity_state = State("media_player.demo", "on") assert validate_media_player_features(entity_state, config) is False def test_convert_to_float(): """Test convert_to_float method.""" assert convert_to_float(12) == 12 assert convert_to_float(12.4) == 12.4 assert convert_to_float(STATE_UNKNOWN) is None assert convert_to_float(None) is None def test_cleanup_name_for_homekit(): """Ensure name sanitize works as expected.""" assert cleanup_name_for_homekit("abc") == "abc" assert cleanup_name_for_homekit("a b c") == "a b c" assert cleanup_name_for_homekit("ab_c") == "ab c" assert ( cleanup_name_for_homekit('ab!@#$%^&*()-=":.,>