diff --git a/homeassistant/components/canary/__init__.py b/homeassistant/components/canary/__init__.py index 165787a7f46..0a4ae770006 100644 --- a/homeassistant/components/canary/__init__.py +++ b/homeassistant/components/canary/__init__.py @@ -76,6 +76,10 @@ class CanaryData: @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self, **kwargs): + """Get the latest data from py-canary with a throttle.""" + self._update(**kwargs) + + def _update(self, **kwargs): """Get the latest data from py-canary.""" for location in self._api.get_locations(): location_id = location.location_id diff --git a/tests/components/canary/__init__.py b/tests/components/canary/__init__.py index cc85edd806a..2ad275165d6 100644 --- a/tests/components/canary/__init__.py +++ b/tests/components/canary/__init__.py @@ -1 +1,61 @@ """Tests for the canary component.""" +from unittest.mock import MagicMock, PropertyMock + +from canary.api import SensorType + +from homeassistant.components.homeassistant import ( + DOMAIN as HA_DOMAIN, + SERVICE_UPDATE_ENTITY, +) +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant + + +async def update_entity(hass: HomeAssistant, entity_id: str) -> None: + """Run an update action for an entity.""" + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + await hass.async_block_till_done() + + +def mock_device(device_id, name, is_online=True, device_type_name=None): + """Mock Canary Device class.""" + device = MagicMock() + type(device).device_id = PropertyMock(return_value=device_id) + type(device).name = PropertyMock(return_value=name) + type(device).is_online = PropertyMock(return_value=is_online) + type(device).device_type = PropertyMock( + return_value={"id": 1, "name": device_type_name} + ) + return device + + +def mock_location(location_id, name, is_celsius=True, devices=None): + """Mock Canary Location class.""" + location = MagicMock() + type(location).location_id = PropertyMock(return_value=location_id) + type(location).name = PropertyMock(return_value=name) + type(location).is_celsius = PropertyMock(return_value=is_celsius) + type(location).devices = PropertyMock(return_value=devices or []) + return location + + +def mock_mode(mode_id, name): + """Mock Canary Mode class.""" + mode = MagicMock() + type(mode).mode_id = PropertyMock(return_value=mode_id) + type(mode).name = PropertyMock(return_value=name) + type(mode).resource_url = PropertyMock(return_value=f"/v1/modes/{mode_id}") + return mode + + +def mock_reading(sensor_type, sensor_value): + """Mock Canary Reading class.""" + reading = MagicMock() + type(reading).sensor_type = SensorType(sensor_type) + type(reading).value = PropertyMock(return_value=sensor_value) + return reading diff --git a/tests/components/canary/conftest.py b/tests/components/canary/conftest.py new file mode 100644 index 00000000000..41873e0c25f --- /dev/null +++ b/tests/components/canary/conftest.py @@ -0,0 +1,34 @@ +"""Define fixtures available for all tests.""" +from canary.api import Api +from pytest import fixture + +from tests.async_mock import MagicMock, patch + + +def mock_canary_update(self, **kwargs): + """Get the latest data from py-canary.""" + self._update(**kwargs) + + +@fixture +def canary(hass): + """Mock the CanaryApi for easier testing.""" + with patch.object(Api, "login", return_value=True), patch( + "homeassistant.components.canary.CanaryData.update", mock_canary_update + ), patch("homeassistant.components.canary.Api") as mock_canary: + instance = mock_canary.return_value = Api( + "test-username", + "test-password", + 1, + ) + + instance.login = MagicMock(return_value=True) + instance.get_entries = MagicMock(return_value=[]) + instance.get_locations = MagicMock(return_value=[]) + instance.get_location = MagicMock(return_value=None) + instance.get_modes = MagicMock(return_value=[]) + instance.get_readings = MagicMock(return_value=[]) + instance.get_latest_readings = MagicMock(return_value=[]) + instance.set_location_mode = MagicMock(return_value=None) + + yield mock_canary diff --git a/tests/components/canary/test_init.py b/tests/components/canary/test_init.py index 0cfbfd56de6..ab0d8e5ab7a 100644 --- a/tests/components/canary/test_init.py +++ b/tests/components/canary/test_init.py @@ -1,73 +1,37 @@ """The tests for the Canary component.""" -import unittest +from requests import HTTPError -from homeassistant import setup -import homeassistant.components.canary as canary +from homeassistant.components.canary import DOMAIN +from homeassistant.setup import async_setup_component -from tests.async_mock import MagicMock, PropertyMock, patch -from tests.common import get_test_home_assistant +from tests.async_mock import patch -def mock_device(device_id, name, is_online=True, device_type_name=None): - """Mock Canary Device class.""" - device = MagicMock() - type(device).device_id = PropertyMock(return_value=device_id) - type(device).name = PropertyMock(return_value=name) - type(device).is_online = PropertyMock(return_value=is_online) - type(device).device_type = PropertyMock( - return_value={"id": 1, "name": device_type_name} - ) - return device +async def test_setup_with_valid_config(hass, canary) -> None: + """Test setup with valid YAML.""" + await async_setup_component(hass, "persistent_notification", {}) + config = {DOMAIN: {"username": "test-username", "password": "test-password"}} + + with patch( + "homeassistant.components.canary.alarm_control_panel.setup_platform", + return_value=True, + ), patch( + "homeassistant.components.canary.camera.setup_platform", + return_value=True, + ), patch( + "homeassistant.components.canary.sensor.setup_platform", + return_value=True, + ): + assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() -def mock_location(name, is_celsius=True, devices=None): - """Mock Canary Location class.""" - location = MagicMock() - type(location).name = PropertyMock(return_value=name) - type(location).is_celsius = PropertyMock(return_value=is_celsius) - type(location).devices = PropertyMock(return_value=devices or []) - return location +async def test_setup_with_http_error(hass, canary) -> None: + """Test setup with HTTP error.""" + await async_setup_component(hass, "persistent_notification", {}) + config = {DOMAIN: {"username": "test-username", "password": "test-password"}} + canary.side_effect = HTTPError() -def mock_reading(sensor_type, sensor_value): - """Mock Canary Reading class.""" - reading = MagicMock() - type(reading).sensor_type = PropertyMock(return_value=sensor_type) - type(reading).value = PropertyMock(return_value=sensor_value) - return reading - - -class TestCanary(unittest.TestCase): - """Tests the Canary component.""" - - def setUp(self): - """Initialize values for this test case class.""" - self.hass = get_test_home_assistant() - self.addCleanup(self.tear_down_cleanup) - - def tear_down_cleanup(self): - """Stop everything that was started.""" - self.hass.stop() - - @patch("homeassistant.components.canary.CanaryData.update") - @patch("canary.api.Api.login") - def test_setup_with_valid_config(self, mock_login, mock_update): - """Test setup component.""" - config = {"canary": {"username": "foo@bar.org", "password": "bar"}} - - assert setup.setup_component(self.hass, canary.DOMAIN, config) - - mock_update.assert_called_once_with() - mock_login.assert_called_once_with() - - def test_setup_with_missing_password(self): - """Test setup component.""" - config = {"canary": {"username": "foo@bar.org"}} - - assert not setup.setup_component(self.hass, canary.DOMAIN, config) - - def test_setup_with_missing_username(self): - """Test setup component.""" - config = {"canary": {"password": "bar"}} - - assert not setup.setup_component(self.hass, canary.DOMAIN, config) + assert not await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() diff --git a/tests/components/canary/test_sensor.py b/tests/components/canary/test_sensor.py index f328fb5a976..02d2dc5cc24 100644 --- a/tests/components/canary/test_sensor.py +++ b/tests/components/canary/test_sensor.py @@ -1,203 +1,175 @@ """The tests for the Canary sensor platform.""" -import copy -import unittest - -from homeassistant.components.canary import DATA_CANARY, sensor as canary +from homeassistant.components.canary import DOMAIN from homeassistant.components.canary.sensor import ( ATTR_AIR_QUALITY, - SENSOR_TYPES, STATE_AIR_QUALITY_ABNORMAL, STATE_AIR_QUALITY_NORMAL, STATE_AIR_QUALITY_VERY_ABNORMAL, - CanarySensor, ) -from homeassistant.const import PERCENTAGE, TEMP_CELSIUS +from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, PERCENTAGE, TEMP_CELSIUS +from homeassistant.setup import async_setup_component -from tests.async_mock import Mock -from tests.common import get_test_home_assistant -from tests.components.canary.test_init import mock_device, mock_location +from . import mock_device, mock_location, mock_reading -VALID_CONFIG = {"canary": {"username": "foo@bar.org", "password": "bar"}} +from tests.async_mock import patch +from tests.common import mock_registry -class TestCanarySensorSetup(unittest.TestCase): - """Test the Canary platform.""" +async def test_sensors_pro(hass, canary) -> None: + """Test the creation and values of the sensors for Canary Pro.""" + await async_setup_component(hass, "persistent_notification", {}) - DEVICES = [] + registry = mock_registry(hass) + online_device_at_home = mock_device(20, "Dining Room", True, "Canary Pro") - def add_entities(self, devices, action): - """Mock add devices.""" - for device in devices: - self.DEVICES.append(device) + instance = canary.return_value + instance.get_locations.return_value = [ + mock_location(100, "Home", True, devices=[online_device_at_home]), + ] - def setUp(self): - """Initialize values for this testcase class.""" - self.hass = get_test_home_assistant() - self.config = copy.deepcopy(VALID_CONFIG) - self.addCleanup(self.hass.stop) + instance.get_latest_readings.return_value = [ + mock_reading("temperature", "21.12"), + mock_reading("humidity", "50.46"), + mock_reading("air_quality", "0.59"), + ] - def test_setup_sensors(self): - """Test the sensor setup.""" - online_device_at_home = mock_device(20, "Dining Room", True, "Canary Pro") - offline_device_at_home = mock_device(21, "Front Yard", False, "Canary Pro") - online_device_at_work = mock_device(22, "Office", True, "Canary Pro") + config = {DOMAIN: {"username": "test-username", "password": "test-password"}} + with patch("homeassistant.components.canary.CANARY_COMPONENTS", ["sensor"]): + assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() - self.hass.data[DATA_CANARY] = Mock() - self.hass.data[DATA_CANARY].locations = [ - mock_location( - "Home", True, devices=[online_device_at_home, offline_device_at_home] - ), - mock_location("Work", True, devices=[online_device_at_work]), - ] + sensors = { + "home_dining_room_temperature": ( + "20_temperature", + "21.12", + TEMP_CELSIUS, + None, + "mdi:thermometer", + ), + "home_dining_room_humidity": ( + "20_humidity", + "50.46", + PERCENTAGE, + None, + "mdi:water-percent", + ), + "home_dining_room_air_quality": ( + "20_air_quality", + "0.59", + None, + None, + "mdi:weather-windy", + ), + } - canary.setup_platform(self.hass, self.config, self.add_entities, None) + for (sensor_id, data) in sensors.items(): + entity_entry = registry.async_get(f"sensor.{sensor_id}") + assert entity_entry + assert entity_entry.device_class == data[3] + assert entity_entry.unique_id == data[0] + assert entity_entry.original_icon == data[4] - assert len(self.DEVICES) == 6 + state = hass.states.get(f"sensor.{sensor_id}") + assert state + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == data[2] + assert state.state == data[1] - def test_temperature_sensor(self): - """Test temperature sensor with fahrenheit.""" - device = mock_device(10, "Family Room", "Canary Pro") - location = mock_location("Home", False) - data = Mock() - data.get_reading.return_value = 21.1234 +async def test_sensors_attributes_pro(hass, canary) -> None: + """Test the creation and values of the sensors attributes for Canary Pro.""" + await async_setup_component(hass, "persistent_notification", {}) - sensor = CanarySensor(data, SENSOR_TYPES[0], location, device) - sensor.update() + online_device_at_home = mock_device(20, "Dining Room", True, "Canary Pro") - assert sensor.name == "Home Family Room Temperature" - assert sensor.unit_of_measurement == TEMP_CELSIUS - assert sensor.state == 21.12 - assert sensor.icon == "mdi:thermometer" + instance = canary.return_value + instance.get_locations.return_value = [ + mock_location(100, "Home", True, devices=[online_device_at_home]), + ] - def test_temperature_sensor_with_none_sensor_value(self): - """Test temperature sensor with fahrenheit.""" - device = mock_device(10, "Family Room", "Canary Pro") - location = mock_location("Home", False) + instance.get_latest_readings.return_value = [ + mock_reading("temperature", "21.12"), + mock_reading("humidity", "50.46"), + mock_reading("air_quality", "0.59"), + ] - data = Mock() - data.get_reading.return_value = None + config = {DOMAIN: {"username": "test-username", "password": "test-password"}} + with patch("homeassistant.components.canary.CANARY_COMPONENTS", ["sensor"]): + assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() - sensor = CanarySensor(data, SENSOR_TYPES[0], location, device) - sensor.update() + entity_id = "sensor.home_dining_room_air_quality" + state = hass.states.get(entity_id) + assert state + assert state.attributes[ATTR_AIR_QUALITY] == STATE_AIR_QUALITY_ABNORMAL - assert sensor.state is None + instance.get_latest_readings.return_value = [ + mock_reading("temperature", "21.12"), + mock_reading("humidity", "50.46"), + mock_reading("air_quality", "0.4"), + ] - def test_humidity_sensor(self): - """Test humidity sensor.""" - device = mock_device(10, "Family Room", "Canary Pro") - location = mock_location("Home") + await hass.helpers.entity_component.async_update_entity(entity_id) + await hass.async_block_till_done() - data = Mock() - data.get_reading.return_value = 50.4567 + state = hass.states.get(entity_id) + assert state + assert state.attributes[ATTR_AIR_QUALITY] == STATE_AIR_QUALITY_VERY_ABNORMAL - sensor = CanarySensor(data, SENSOR_TYPES[1], location, device) - sensor.update() + instance.get_latest_readings.return_value = [ + mock_reading("temperature", "21.12"), + mock_reading("humidity", "50.46"), + mock_reading("air_quality", "1.0"), + ] - assert sensor.name == "Home Family Room Humidity" - assert sensor.unit_of_measurement == PERCENTAGE - assert sensor.state == 50.46 - assert sensor.icon == "mdi:water-percent" + await hass.helpers.entity_component.async_update_entity(entity_id) + await hass.async_block_till_done() - def test_air_quality_sensor_with_very_abnormal_reading(self): - """Test air quality sensor.""" - device = mock_device(10, "Family Room", "Canary Pro") - location = mock_location("Home") + state = hass.states.get(entity_id) + assert state + assert state.attributes[ATTR_AIR_QUALITY] == STATE_AIR_QUALITY_NORMAL - data = Mock() - data.get_reading.return_value = 0.4 - sensor = CanarySensor(data, SENSOR_TYPES[2], location, device) - sensor.update() +async def test_sensors_flex(hass, canary) -> None: + """Test the creation and values of the sensors for Canary Flex.""" + await async_setup_component(hass, "persistent_notification", {}) - assert sensor.name == "Home Family Room Air Quality" - assert sensor.unit_of_measurement is None - assert sensor.state == 0.4 - assert sensor.icon == "mdi:weather-windy" + registry = mock_registry(hass) + online_device_at_home = mock_device(20, "Dining Room", True, "Canary Flex") - air_quality = sensor.device_state_attributes[ATTR_AIR_QUALITY] - assert air_quality == STATE_AIR_QUALITY_VERY_ABNORMAL + instance = canary.return_value + instance.get_locations.return_value = [ + mock_location(100, "Home", True, devices=[online_device_at_home]), + ] - def test_air_quality_sensor_with_abnormal_reading(self): - """Test air quality sensor.""" - device = mock_device(10, "Family Room", "Canary Pro") - location = mock_location("Home") + instance.get_latest_readings.return_value = [ + mock_reading("battery", "70.4567"), + mock_reading("wifi", "-57"), + ] - data = Mock() - data.get_reading.return_value = 0.59 + config = {DOMAIN: {"username": "test-username", "password": "test-password"}} + with patch("homeassistant.components.canary.CANARY_COMPONENTS", ["sensor"]): + assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() - sensor = CanarySensor(data, SENSOR_TYPES[2], location, device) - sensor.update() + sensors = { + "home_dining_room_battery": ( + "20_battery", + "70.46", + PERCENTAGE, + None, + "mdi:battery-70", + ), + "home_dining_room_wifi": ("20_wifi", "-57.0", "dBm", None, "mdi:wifi"), + } - assert sensor.name == "Home Family Room Air Quality" - assert sensor.unit_of_measurement is None - assert sensor.state == 0.59 - assert sensor.icon == "mdi:weather-windy" + for (sensor_id, data) in sensors.items(): + entity_entry = registry.async_get(f"sensor.{sensor_id}") + assert entity_entry + assert entity_entry.device_class == data[3] + assert entity_entry.unique_id == data[0] + assert entity_entry.original_icon == data[4] - air_quality = sensor.device_state_attributes[ATTR_AIR_QUALITY] - assert air_quality == STATE_AIR_QUALITY_ABNORMAL - - def test_air_quality_sensor_with_normal_reading(self): - """Test air quality sensor.""" - device = mock_device(10, "Family Room", "Canary Pro") - location = mock_location("Home") - - data = Mock() - data.get_reading.return_value = 1.0 - - sensor = CanarySensor(data, SENSOR_TYPES[2], location, device) - sensor.update() - - assert sensor.name == "Home Family Room Air Quality" - assert sensor.unit_of_measurement is None - assert sensor.state == 1.0 - assert sensor.icon == "mdi:weather-windy" - - air_quality = sensor.device_state_attributes[ATTR_AIR_QUALITY] - assert air_quality == STATE_AIR_QUALITY_NORMAL - - def test_air_quality_sensor_with_none_sensor_value(self): - """Test air quality sensor.""" - device = mock_device(10, "Family Room", "Canary Pro") - location = mock_location("Home") - - data = Mock() - data.get_reading.return_value = None - - sensor = CanarySensor(data, SENSOR_TYPES[2], location, device) - sensor.update() - - assert sensor.state is None - assert sensor.device_state_attributes is None - - def test_battery_sensor(self): - """Test battery sensor.""" - device = mock_device(10, "Family Room", "Canary Flex") - location = mock_location("Home") - - data = Mock() - data.get_reading.return_value = 70.4567 - - sensor = CanarySensor(data, SENSOR_TYPES[4], location, device) - sensor.update() - - assert sensor.name == "Home Family Room Battery" - assert sensor.unit_of_measurement == PERCENTAGE - assert sensor.state == 70.46 - assert sensor.icon == "mdi:battery-70" - - def test_wifi_sensor(self): - """Test battery sensor.""" - device = mock_device(10, "Family Room", "Canary Flex") - location = mock_location("Home") - - data = Mock() - data.get_reading.return_value = -57 - - sensor = CanarySensor(data, SENSOR_TYPES[3], location, device) - sensor.update() - - assert sensor.name == "Home Family Room Wifi" - assert sensor.unit_of_measurement == "dBm" - assert sensor.state == -57 - assert sensor.icon == "mdi:wifi" + state = hass.states.get(f"sensor.{sensor_id}") + assert state + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == data[2] + assert state.state == data[1]