"""The tests for the TCP sensor platform.""" from copy import copy from unittest.mock import call, patch import pytest from homeassistant.components.tcp import common as tcp from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component from tests.common import assert_setup_component TEST_CONFIG = { "sensor": { "platform": "tcp", tcp.CONF_NAME: "test_name", tcp.CONF_HOST: "test_host", tcp.CONF_PORT: 12345, tcp.CONF_TIMEOUT: tcp.DEFAULT_TIMEOUT + 1, tcp.CONF_PAYLOAD: "test_payload", tcp.CONF_UNIT_OF_MEASUREMENT: "test_unit", tcp.CONF_VALUE_TEMPLATE: "{{ '7.' + value }}", tcp.CONF_VALUE_ON: "7.on", tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE + 1, } } SENSOR_TEST_CONFIG = TEST_CONFIG["sensor"] TEST_ENTITY = "sensor.test_name" KEYS_AND_DEFAULTS = { tcp.CONF_NAME: tcp.DEFAULT_NAME, tcp.CONF_TIMEOUT: tcp.DEFAULT_TIMEOUT, tcp.CONF_UNIT_OF_MEASUREMENT: None, tcp.CONF_VALUE_TEMPLATE: None, tcp.CONF_VALUE_ON: None, tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE, } socket_test_value = "123" @pytest.fixture(name="mock_socket") def mock_socket_fixture(mock_select): """Mock socket.""" with patch("homeassistant.components.tcp.entity.socket.socket") as mock_socket: socket_instance = mock_socket.return_value.__enter__.return_value socket_instance.recv.return_value = socket_test_value.encode() yield socket_instance @pytest.fixture(name="mock_select") def mock_select_fixture(): """Mock select.""" with patch( "homeassistant.components.tcp.entity.select.select", return_value=(True, False, False), ) as mock_select: yield mock_select @pytest.fixture(name="mock_ssl_context") def mock_ssl_context_fixture(): """Mock select.""" with patch( "homeassistant.components.tcp.entity.ssl.create_default_context", ) as mock_ssl_context: mock_ssl_context.return_value.wrap_socket.return_value.recv.return_value = ( socket_test_value + "567" ).encode() yield mock_ssl_context async def test_setup_platform_valid_config(hass: HomeAssistant, mock_socket) -> None: """Check a valid configuration and call add_entities with sensor.""" with assert_setup_component(1, "sensor"): assert await async_setup_component(hass, "sensor", TEST_CONFIG) await hass.async_block_till_done() async def test_setup_platform_invalid_config(hass: HomeAssistant, mock_socket) -> None: """Check an invalid configuration.""" with assert_setup_component(0): assert await async_setup_component( hass, "sensor", {"sensor": {"platform": "tcp", "porrt": 1234}} ) await hass.async_block_till_done() async def test_state(hass: HomeAssistant, mock_socket, mock_select) -> None: """Return the contents of _state.""" assert await async_setup_component(hass, "sensor", TEST_CONFIG) await hass.async_block_till_done() state = hass.states.get(TEST_ENTITY) assert state assert state.state == "7.123" assert ( state.attributes["unit_of_measurement"] == SENSOR_TEST_CONFIG[tcp.CONF_UNIT_OF_MEASUREMENT] ) assert mock_socket.connect.called assert mock_socket.connect.call_args == call( (SENSOR_TEST_CONFIG["host"], SENSOR_TEST_CONFIG["port"]) ) assert mock_socket.send.called assert mock_socket.send.call_args == call(SENSOR_TEST_CONFIG["payload"].encode()) assert mock_select.call_args == call( [mock_socket], [], [], SENSOR_TEST_CONFIG[tcp.CONF_TIMEOUT] ) assert mock_socket.recv.called assert mock_socket.recv.call_args == call(SENSOR_TEST_CONFIG["buffer_size"]) async def test_config_uses_defaults(hass: HomeAssistant, mock_socket) -> None: """Check if defaults were set.""" config = copy(SENSOR_TEST_CONFIG) for key in KEYS_AND_DEFAULTS: del config[key] with assert_setup_component(1) as result_config: assert await async_setup_component(hass, "sensor", {"sensor": config}) await hass.async_block_till_done() state = hass.states.get("sensor.tcp_sensor") assert state assert state.state == "123" for key, default in KEYS_AND_DEFAULTS.items(): assert result_config["sensor"][0].get(key) == default @pytest.mark.parametrize("sock_attr", ["connect", "send"]) async def test_update_socket_error(hass: HomeAssistant, mock_socket, sock_attr) -> None: """Test socket errors during update.""" socket_method = getattr(mock_socket, sock_attr) socket_method.side_effect = OSError("Boom") assert await async_setup_component(hass, "sensor", TEST_CONFIG) await hass.async_block_till_done() state = hass.states.get(TEST_ENTITY) assert state assert state.state == "unknown" async def test_update_select_fails( hass: HomeAssistant, mock_socket, mock_select ) -> None: """Test select fails to return a socket for reading.""" mock_select.return_value = (False, False, False) assert await async_setup_component(hass, "sensor", TEST_CONFIG) await hass.async_block_till_done() state = hass.states.get(TEST_ENTITY) assert state assert state.state == "unknown" async def test_update_returns_if_template_render_fails( hass: HomeAssistant, mock_socket ) -> None: """Return None if rendering the template fails.""" config = copy(SENSOR_TEST_CONFIG) config[tcp.CONF_VALUE_TEMPLATE] = "{{ value / 0 }}" assert await async_setup_component(hass, "sensor", {"sensor": config}) await hass.async_block_till_done() state = hass.states.get(TEST_ENTITY) assert state assert state.state == "unknown" async def test_ssl_state( hass: HomeAssistant, mock_socket, mock_select, mock_ssl_context ) -> None: """Return the contents of _state, updated over SSL.""" config = copy(SENSOR_TEST_CONFIG) config[tcp.CONF_SSL] = "on" assert await async_setup_component(hass, "sensor", {"sensor": config}) await hass.async_block_till_done() state = hass.states.get(TEST_ENTITY) assert state assert state.state == "7.123567" assert mock_socket.connect.called assert mock_socket.connect.call_args == call( (SENSOR_TEST_CONFIG["host"], SENSOR_TEST_CONFIG["port"]) ) assert not mock_socket.send.called assert mock_ssl_context.called assert mock_ssl_context.return_value.check_hostname mock_ssl_socket = mock_ssl_context.return_value.wrap_socket.return_value assert mock_ssl_socket.send.called assert mock_ssl_socket.send.call_args == call( SENSOR_TEST_CONFIG["payload"].encode() ) assert mock_select.call_args == call( [mock_ssl_socket], [], [], SENSOR_TEST_CONFIG[tcp.CONF_TIMEOUT] ) assert mock_ssl_socket.recv.called assert mock_ssl_socket.recv.call_args == call(SENSOR_TEST_CONFIG["buffer_size"]) async def test_ssl_state_verify_off( hass: HomeAssistant, mock_socket, mock_select, mock_ssl_context ) -> None: """Return the contents of _state, updated over SSL (verify_ssl disabled).""" config = copy(SENSOR_TEST_CONFIG) config[tcp.CONF_SSL] = "on" config[tcp.CONF_VERIFY_SSL] = "off" assert await async_setup_component(hass, "sensor", {"sensor": config}) await hass.async_block_till_done() state = hass.states.get(TEST_ENTITY) assert state assert state.state == "7.123567" assert mock_socket.connect.called assert mock_socket.connect.call_args == call( (SENSOR_TEST_CONFIG["host"], SENSOR_TEST_CONFIG["port"]) ) assert not mock_socket.send.called assert mock_ssl_context.called assert not mock_ssl_context.return_value.check_hostname mock_ssl_socket = mock_ssl_context.return_value.wrap_socket.return_value assert mock_ssl_socket.send.called assert mock_ssl_socket.send.call_args == call( SENSOR_TEST_CONFIG["payload"].encode() ) assert mock_select.call_args == call( [mock_ssl_socket], [], [], SENSOR_TEST_CONFIG[tcp.CONF_TIMEOUT] ) assert mock_ssl_socket.recv.called assert mock_ssl_socket.recv.call_args == call(SENSOR_TEST_CONFIG["buffer_size"])