From e5c6ac5ba8ed21dc42a1684ffbe7e83db68a6618 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 9 Jun 2021 20:23:16 +0200 Subject: [PATCH] Add 100% test coverage to Ambee integration (#51670) * Add 100% test coverage to Ambee integration * Add tests for device and entity registry --- .coveragerc | 2 - tests/components/ambee/conftest.py | 50 ++++++++++ tests/components/ambee/test_init.py | 46 +++++++++ tests/components/ambee/test_sensor.py | 130 ++++++++++++++++++++++++++ tests/fixtures/ambee/air_quality.json | 28 ++++++ 5 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 tests/components/ambee/conftest.py create mode 100644 tests/components/ambee/test_init.py create mode 100644 tests/components/ambee/test_sensor.py create mode 100644 tests/fixtures/ambee/air_quality.json diff --git a/.coveragerc b/.coveragerc index 1d2c6275fc38..9437f8943a30 100644 --- a/.coveragerc +++ b/.coveragerc @@ -45,8 +45,6 @@ omit = homeassistant/components/alarmdecoder/sensor.py homeassistant/components/alpha_vantage/sensor.py homeassistant/components/amazon_polly/* - homeassistant/components/ambee/__init__.py - homeassistant/components/ambee/sensor.py homeassistant/components/ambiclimate/climate.py homeassistant/components/ambient_station/* homeassistant/components/amcrest/* diff --git a/tests/components/ambee/conftest.py b/tests/components/ambee/conftest.py new file mode 100644 index 000000000000..de88e28e1d1a --- /dev/null +++ b/tests/components/ambee/conftest.py @@ -0,0 +1,50 @@ +"""Fixtures for Ambee integration tests.""" +import json +from unittest.mock import AsyncMock, MagicMock, patch + +from ambee import AirQuality +import pytest + +from homeassistant.components.ambee.const import DOMAIN +from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, load_fixture +from tests.test_util.aiohttp import AiohttpClientMocker + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry( + title="Home Sweet Home", + domain=DOMAIN, + data={CONF_LATITUDE: 52.42, CONF_LONGITUDE: 4.44, CONF_API_KEY: "example"}, + unique_id="unique_thingy", + ) + + +@pytest.fixture +def mock_ambee(aioclient_mock: AiohttpClientMocker): + """Return a mocked Ambee client.""" + with patch("homeassistant.components.ambee.Ambee") as ambee_mock: + client = ambee_mock.return_value + client.air_quality = AsyncMock( + return_value=AirQuality.from_dict( + json.loads(load_fixture("ambee/air_quality.json")) + ) + ) + yield ambee_mock + + +@pytest.fixture +async def init_integration( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_ambee: MagicMock +) -> MockConfigEntry: + """Set up the Ambee integration for testing.""" + mock_config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + return mock_config_entry diff --git a/tests/components/ambee/test_init.py b/tests/components/ambee/test_init.py new file mode 100644 index 000000000000..c58e7cfef0d1 --- /dev/null +++ b/tests/components/ambee/test_init.py @@ -0,0 +1,46 @@ +"""Tests for the Ambee integration.""" +from unittest.mock import AsyncMock, MagicMock, patch + +from ambee import AmbeeConnectionError + +from homeassistant.components.ambee.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def test_load_unload_config_entry( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_ambee: AsyncMock, +) -> None: + """Test the Ambee configuration entry loading/unloading.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.LOADED + + await hass.config_entries.async_unload(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert not hass.data.get(DOMAIN) + + +@patch( + "homeassistant.components.ambee.Ambee.air_quality", + side_effect=AmbeeConnectionError, +) +async def test_config_entry_not_ready( + mock_air_quality: MagicMock, + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the Ambee configuration entry not ready.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_air_quality.call_count == 1 + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY diff --git a/tests/components/ambee/test_sensor.py b/tests/components/ambee/test_sensor.py new file mode 100644 index 000000000000..a754256ff0a4 --- /dev/null +++ b/tests/components/ambee/test_sensor.py @@ -0,0 +1,130 @@ +"""Tests for the sensors provided by the Ambee integration.""" +from homeassistant.components.ambee.const import DOMAIN +from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_FRIENDLY_NAME, + ATTR_UNIT_OF_MEASUREMENT, + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + CONCENTRATION_PARTS_PER_BILLION, + CONCENTRATION_PARTS_PER_MILLION, + DEVICE_CLASS_CO, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er + +from tests.common import MockConfigEntry + + +async def test_air_quality( + hass: HomeAssistant, + init_integration: MockConfigEntry, +) -> None: + """Test the Ambee Air Quality sensors.""" + entry_id = init_integration.entry_id + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) + + state = hass.states.get("sensor.particulate_matter_2_5_mm") + entry = entity_registry.async_get("sensor.particulate_matter_2_5_mm") + assert entry + assert state + assert entry.unique_id == f"{entry_id}_particulate_matter_2_5" + assert state.state == "3.14" + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Particulate Matter < 2.5 μm" + assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + ) + assert ATTR_DEVICE_CLASS not in state.attributes + + state = hass.states.get("sensor.particulate_matter_10_mm") + entry = entity_registry.async_get("sensor.particulate_matter_10_mm") + assert entry + assert state + assert entry.unique_id == f"{entry_id}_particulate_matter_10" + assert state.state == "5.24" + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Particulate Matter < 10 μm" + assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + ) + assert ATTR_DEVICE_CLASS not in state.attributes + + state = hass.states.get("sensor.sulphur_dioxide_so2") + entry = entity_registry.async_get("sensor.sulphur_dioxide_so2") + assert entry + assert state + assert entry.unique_id == f"{entry_id}_sulphur_dioxide" + assert state.state == "0.031" + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Sulphur Dioxide (SO2)" + assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_PARTS_PER_BILLION + ) + assert ATTR_DEVICE_CLASS not in state.attributes + + state = hass.states.get("sensor.nitrogen_dioxide_no2") + entry = entity_registry.async_get("sensor.nitrogen_dioxide_no2") + assert entry + assert state + assert entry.unique_id == f"{entry_id}_nitrogen_dioxide" + assert state.state == "0.66" + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Nitrogen Dioxide (NO2)" + assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_PARTS_PER_BILLION + ) + assert ATTR_DEVICE_CLASS not in state.attributes + + state = hass.states.get("sensor.ozone") + entry = entity_registry.async_get("sensor.ozone") + assert entry + assert state + assert entry.unique_id == f"{entry_id}_ozone" + assert state.state == "17.067" + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Ozone" + assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_PARTS_PER_BILLION + ) + assert ATTR_DEVICE_CLASS not in state.attributes + + state = hass.states.get("sensor.carbon_monoxide_co") + entry = entity_registry.async_get("sensor.carbon_monoxide_co") + assert entry + assert state + assert entry.unique_id == f"{entry_id}_carbon_monoxide" + assert state.state == "0.105" + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_CO + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Carbon Monoxide (CO)" + assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_PARTS_PER_MILLION + ) + + state = hass.states.get("sensor.air_quality_index_aqi") + entry = entity_registry.async_get("sensor.air_quality_index_aqi") + assert entry + assert state + assert entry.unique_id == f"{entry_id}_air_quality_index" + assert state.state == "13" + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Air Quality Index (AQI)" + assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT + assert ATTR_DEVICE_CLASS not in state.attributes + assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes + + assert entry.device_id + device_entry = device_registry.async_get(entry.device_id) + assert device_entry + assert device_entry.identifiers == {(DOMAIN, f"{entry_id}_air_quality")} + assert device_entry.manufacturer == "Ambee" + assert device_entry.name == "Air Quality" + assert not device_entry.model + assert not device_entry.sw_version diff --git a/tests/fixtures/ambee/air_quality.json b/tests/fixtures/ambee/air_quality.json new file mode 100644 index 000000000000..2844e38168bf --- /dev/null +++ b/tests/fixtures/ambee/air_quality.json @@ -0,0 +1,28 @@ +{ + "message": "success", + "stations": [ + { + "CO": 0.105, + "NO2": 0.66, + "OZONE": 17.067, + "PM10": 5.24, + "PM25": 3.14, + "SO2": 0.031, + "city": "Hellendoorn", + "countryCode": "NL", + "division": "", + "lat": 52.3981, + "lng": 6.4493, + "placeName": "Hellendoorn", + "postalCode": "7447", + "state": "Overijssel", + "updatedAt": "2021-05-29T14:00:00.000Z", + "AQI": 13, + "aqiInfo": { + "pollutant": "PM2.5", + "concentration": 3.14, + "category": "Good" + } + } + ] +}