"""The tests for the TCP sensor platform.""" from copy import copy from unittest.mock import call, patch import pytest import homeassistant.components.tcp.common as tcp 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: "{{ 'test_' + value }}", tcp.CONF_VALUE_ON: "test_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 = "value" @pytest.fixture(name="mock_socket") def mock_socket_fixture(mock_select): """Mock socket.""" with patch("homeassistant.components.tcp.common.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.common.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.common.ssl.create_default_context", ) as mock_ssl_context: mock_ssl_context.return_value.wrap_socket.return_value.recv.return_value = ( socket_test_value + "_ssl" ).encode() yield mock_ssl_context async def test_setup_platform_valid_config(hass, mock_socket): """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, mock_socket): """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, mock_socket, mock_select): """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 == "test_value" 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, mock_socket): """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 == "value" 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, mock_socket, sock_attr): """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, mock_socket, mock_select): """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, mock_socket): """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, mock_socket, mock_select, mock_ssl_context): """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 == "test_value_ssl" 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, mock_socket, mock_select, mock_ssl_context): """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 == "test_value_ssl" 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"])