""" tests.components.sensor.tcp ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests TCP sensor. """ import socket from copy import copy from unittest.mock import patch from homeassistant.components.sensor import tcp from tests.common import get_test_home_assistant TEST_CONFIG = { 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: "test_unit", tcp.CONF_VALUE_TEMPLATE: "test_template", tcp.CONF_VALUE_ON: "test_on", tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE + 1 } KEYS_AND_DEFAULTS = { tcp.CONF_NAME: None, tcp.CONF_TIMEOUT: tcp.DEFAULT_TIMEOUT, tcp.CONF_UNIT: None, tcp.CONF_VALUE_TEMPLATE: None, tcp.CONF_VALUE_ON: None, tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE } # class TestTCPSensor(unittest.TestCase): class TestTCPSensor(): """ Test the TCP Sensor. """ def setup_class(cls): cls.hass = get_test_home_assistant() def teardown_class(cls): cls.hass.stop() @patch("homeassistant.components.sensor.tcp.Sensor.update") def test_config_valid_keys(self, *args): """ Should store valid keys in _config. """ sensor = tcp.Sensor(self.hass, TEST_CONFIG) for key in TEST_CONFIG: assert key in sensor._config def test_validate_config_valid_keys(self): """ Should return True when provided with the correct keys. """ assert tcp.Sensor.validate_config(TEST_CONFIG) @patch("homeassistant.components.sensor.tcp.Sensor.update") def test_config_invalid_keys(self, *args): """ Shouldn't store invalid keys in _config. """ config = copy(TEST_CONFIG) config.update({ "a": "test_a", "b": "test_b", "c": "test_c" }) sensor = tcp.Sensor(self.hass, config) for invalid_key in tuple("abc"): assert invalid_key not in sensor._config @patch("homeassistant.components.sensor.tcp.Sensor.update") def test_validate_config_invalid_keys(self, *args): """ Should return True when provided with the correct keys plus some extra. """ config = copy(TEST_CONFIG) config.update({ "a": "test_a", "b": "test_b", "c": "test_c" }) assert tcp.Sensor.validate_config(config) @patch("homeassistant.components.sensor.tcp.Sensor.update") def test_config_uses_defaults(self, *args): """ Should use defaults where appropriate. """ config = copy(TEST_CONFIG) for key in KEYS_AND_DEFAULTS.keys(): del config[key] sensor = tcp.Sensor(self.hass, config) for key, default in KEYS_AND_DEFAULTS.items(): assert sensor._config[key] == default def test_validate_config_missing_defaults(self): """ Should return True when defaulted keys are not provided. """ config = copy(TEST_CONFIG) for key in KEYS_AND_DEFAULTS.keys(): del config[key] assert tcp.Sensor.validate_config(config) def test_validate_config_missing_required(self): """ Should return False when required config items are missing. """ for key in TEST_CONFIG: if key in KEYS_AND_DEFAULTS: continue config = copy(TEST_CONFIG) del config[key] assert not tcp.Sensor.validate_config(config), ( "validate_config() should have returned False since %r was not" "provided." % key) @patch("homeassistant.components.sensor.tcp.Sensor.update") def test_init_calls_update(self, mock_update): """ Should call update() method during __init__(). """ tcp.Sensor(self.hass, TEST_CONFIG) assert mock_update.called @patch("socket.socket") @patch("select.select", return_value=(True, False, False)) def test_update_connects_to_host_and_port(self, mock_select, mock_socket): """ Should connect to the configured host and port. """ tcp.Sensor(self.hass, TEST_CONFIG) mock_socket = mock_socket().__enter__() mock_socket.connect.assert_called_with(( TEST_CONFIG[tcp.CONF_HOST], TEST_CONFIG[tcp.CONF_PORT])) @patch("socket.socket.connect", side_effect=socket.error()) def test_update_returns_if_connecting_fails(self, mock_socket): """ Should return if connecting to host fails. """ with patch("homeassistant.components.sensor.tcp.Sensor.update"): sensor = tcp.Sensor(self.hass, TEST_CONFIG) assert sensor.update() is None @patch("socket.socket") @patch("select.select", return_value=(True, False, False)) def test_update_sends_payload(self, mock_select, mock_socket): """ Should send the configured payload as bytes. """ tcp.Sensor(self.hass, TEST_CONFIG) mock_socket = mock_socket().__enter__() mock_socket.send.assert_called_with( TEST_CONFIG[tcp.CONF_PAYLOAD].encode() ) @patch("socket.socket") @patch("select.select", return_value=(True, False, False)) def test_update_calls_select_with_timeout(self, mock_select, mock_socket): """ Should provide the timeout argument to select. """ tcp.Sensor(self.hass, TEST_CONFIG) mock_socket = mock_socket().__enter__() mock_select.assert_called_with( [mock_socket], [], [], TEST_CONFIG[tcp.CONF_TIMEOUT]) @patch("socket.socket") @patch("select.select", return_value=(True, False, False)) def test_update_receives_packet_and_sets_as_state( self, mock_select, mock_socket): """ Should receive the response from the socket and set it as the state. """ test_value = "test_value" mock_socket = mock_socket().__enter__() mock_socket.recv.return_value = test_value.encode() config = copy(TEST_CONFIG) del config[tcp.CONF_VALUE_TEMPLATE] sensor = tcp.Sensor(self.hass, config) assert sensor._state == test_value @patch("socket.socket") @patch("select.select", return_value=(True, False, False)) def test_update_renders_value_in_template(self, mock_select, mock_socket): """ Should render the value in the provided template. """ test_value = "test_value" mock_socket = mock_socket().__enter__() mock_socket.recv.return_value = test_value.encode() config = copy(TEST_CONFIG) config[tcp.CONF_VALUE_TEMPLATE] = "{{ value }} {{ 1+1 }}" sensor = tcp.Sensor(self.hass, config) assert sensor._state == "%s 2" % test_value