From 226406b8537121a250212639f2b2afd59017e18d Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 3 Sep 2020 09:27:21 +0200 Subject: [PATCH] Improve tests for GIOS integration (#39514) --- .coveragerc | 2 - homeassistant/components/gios/air_quality.py | 2 + homeassistant/components/gios/manifest.json | 3 +- tests/components/gios/__init__.py | 46 +++++++ tests/components/gios/test_air_quality.py | 123 +++++++++++++++++++ tests/components/gios/test_config_flow.py | 54 ++++---- tests/components/gios/test_init.py | 54 ++++++++ tests/fixtures/gios/indexes.json | 29 +++++ tests/fixtures/gios/sensors.json | 51 ++++++++ tests/fixtures/gios/station.json | 72 +++++++++++ 10 files changed, 406 insertions(+), 30 deletions(-) create mode 100644 tests/components/gios/test_air_quality.py create mode 100644 tests/components/gios/test_init.py create mode 100644 tests/fixtures/gios/indexes.json create mode 100644 tests/fixtures/gios/sensors.json create mode 100644 tests/fixtures/gios/station.json diff --git a/.coveragerc b/.coveragerc index 2ec685df4ce..fdfb25be56b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -306,8 +306,6 @@ omit = homeassistant/components/gc100/* homeassistant/components/geniushub/* homeassistant/components/geizhals/sensor.py - homeassistant/components/gios/__init__.py - homeassistant/components/gios/air_quality.py homeassistant/components/github/sensor.py homeassistant/components/gitlab_ci/sensor.py homeassistant/components/gitter/sensor.py diff --git a/homeassistant/components/gios/air_quality.py b/homeassistant/components/gios/air_quality.py index 8fcd84a4622..2853570ce58 100644 --- a/homeassistant/components/gios/air_quality.py +++ b/homeassistant/components/gios/air_quality.py @@ -24,6 +24,8 @@ SENSOR_MAP = { "SO2": ATTR_SO2, } +PARALLEL_UPDATES = 1 + async def async_setup_entry(hass, config_entry, async_add_entities): """Add a GIOS entities from a config_entry.""" diff --git a/homeassistant/components/gios/manifest.json b/homeassistant/components/gios/manifest.json index da4ceddbeb5..0fc544c9d1e 100644 --- a/homeassistant/components/gios/manifest.json +++ b/homeassistant/components/gios/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/gios", "codeowners": ["@bieniu"], "requirements": ["gios==0.1.3"], - "config_flow": true + "config_flow": true, + "quality_scale": "platinum" } diff --git a/tests/components/gios/__init__.py b/tests/components/gios/__init__.py index 98528fda9f9..920461a6ae1 100644 --- a/tests/components/gios/__init__.py +++ b/tests/components/gios/__init__.py @@ -1 +1,47 @@ """Tests for GIOS.""" +import json + +from homeassistant.components.gios.const import DOMAIN + +from tests.async_mock import patch +from tests.common import MockConfigEntry, load_fixture + +STATIONS = [ + {"id": 123, "stationName": "Test Name 1", "gegrLat": "99.99", "gegrLon": "88.88"}, + {"id": 321, "stationName": "Test Name 2", "gegrLat": "77.77", "gegrLon": "66.66"}, +] + + +async def init_integration(hass, incomplete_data=False) -> MockConfigEntry: + """Set up the GIOS integration in Home Assistant.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="Home", + unique_id=123, + data={"station_id": 123, "name": "Home"}, + ) + + indexes = json.loads(load_fixture("gios/indexes.json")) + station = json.loads(load_fixture("gios/station.json")) + sensors = json.loads(load_fixture("gios/sensors.json")) + if incomplete_data: + indexes["stIndexLevel"]["indexLevelName"] = "foo" + sensors["PM10"]["values"][0]["value"] = None + sensors["PM10"]["values"][1]["value"] = None + + with patch( + "homeassistant.components.gios.Gios._get_stations", return_value=STATIONS + ), patch( + "homeassistant.components.gios.Gios._get_station", + return_value=station, + ), patch( + "homeassistant.components.gios.Gios._get_all_sensors", + return_value=sensors, + ), patch( + "homeassistant.components.gios.Gios._get_indexes", return_value=indexes + ): + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + return entry diff --git a/tests/components/gios/test_air_quality.py b/tests/components/gios/test_air_quality.py new file mode 100644 index 00000000000..9a5b1be6a20 --- /dev/null +++ b/tests/components/gios/test_air_quality.py @@ -0,0 +1,123 @@ +"""Test air_quality of GIOS integration.""" +from datetime import timedelta +import json + +from gios import ApiError + +from homeassistant.components.air_quality import ( + ATTR_AQI, + ATTR_CO, + ATTR_NO2, + ATTR_OZONE, + ATTR_PM_2_5, + ATTR_PM_10, + ATTR_SO2, +) +from homeassistant.components.gios.air_quality import ATTRIBUTION +from homeassistant.components.gios.const import AQI_GOOD +from homeassistant.const import ( + ATTR_ATTRIBUTION, + ATTR_ICON, + ATTR_UNIT_OF_MEASUREMENT, + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + STATE_UNAVAILABLE, +) +from homeassistant.util.dt import utcnow + +from tests.async_mock import patch +from tests.common import async_fire_time_changed, load_fixture +from tests.components.gios import init_integration + + +async def test_air_quality(hass): + """Test states of the air_quality.""" + await init_integration(hass) + registry = await hass.helpers.entity_registry.async_get_registry() + + state = hass.states.get("air_quality.home") + assert state + assert state.state == "4" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_AQI) == AQI_GOOD + assert state.attributes.get(ATTR_PM_10) == 17 + assert state.attributes.get(ATTR_PM_2_5) == 4 + assert state.attributes.get(ATTR_CO) == 252 + assert state.attributes.get(ATTR_SO2) == 4 + assert state.attributes.get(ATTR_NO2) == 7 + assert state.attributes.get(ATTR_OZONE) == 96 + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + ) + assert state.attributes.get(ATTR_ICON) == "mdi:emoticon-happy" + assert state.attributes.get("station") == "Test Name 1" + + entry = registry.async_get("air_quality.home") + assert entry + assert entry.unique_id == 123 + + +async def test_air_quality_with_incomplete_data(hass): + """Test states of the air_quality with incomplete data from measuring station.""" + await init_integration(hass, incomplete_data=True) + registry = await hass.helpers.entity_registry.async_get_registry() + + state = hass.states.get("air_quality.home") + assert state + assert state.state == "4" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_AQI) == "foo" + assert state.attributes.get(ATTR_PM_10) is None + assert state.attributes.get(ATTR_PM_2_5) == 4 + assert state.attributes.get(ATTR_CO) == 252 + assert state.attributes.get(ATTR_SO2) == 4 + assert state.attributes.get(ATTR_NO2) == 7 + assert state.attributes.get(ATTR_OZONE) == 96 + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + ) + assert state.attributes.get(ATTR_ICON) == "mdi:blur" + assert state.attributes.get("station") == "Test Name 1" + + entry = registry.async_get("air_quality.home") + assert entry + assert entry.unique_id == 123 + + +async def test_availability(hass): + """Ensure that we mark the entities unavailable correctly when service causes an error.""" + await init_integration(hass) + + state = hass.states.get("air_quality.home") + assert state + assert state.state != STATE_UNAVAILABLE + assert state.state == "4" + + future = utcnow() + timedelta(minutes=60) + with patch( + "homeassistant.components.gios.Gios._get_all_sensors", + side_effect=ApiError("Unexpected error"), + ): + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + state = hass.states.get("air_quality.home") + assert state + assert state.state == STATE_UNAVAILABLE + + future = utcnow() + timedelta(minutes=120) + with patch( + "homeassistant.components.gios.Gios._get_all_sensors", + return_value=json.loads(load_fixture("gios/sensors.json")), + ), patch( + "homeassistant.components.gios.Gios._get_indexes", + return_value=json.loads(load_fixture("gios/indexes.json")), + ): + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + state = hass.states.get("air_quality.home") + assert state + assert state.state != STATE_UNAVAILABLE + assert state.state == "4" diff --git a/tests/components/gios/test_config_flow.py b/tests/components/gios/test_config_flow.py index b2f7ceec9e4..24ada20aded 100644 --- a/tests/components/gios/test_config_flow.py +++ b/tests/components/gios/test_config_flow.py @@ -1,4 +1,6 @@ """Define tests for the GIOS config flow.""" +import json + from gios import ApiError from homeassistant import data_entry_flow @@ -7,28 +9,14 @@ from homeassistant.components.gios.const import CONF_STATION_ID from homeassistant.const import CONF_NAME from tests.async_mock import patch +from tests.common import load_fixture +from tests.components.gios import STATIONS CONFIG = { CONF_NAME: "Foo", CONF_STATION_ID: 123, } -VALID_STATIONS = [ - {"id": 123, "stationName": "Test Name 1", "gegrLat": "99.99", "gegrLon": "88.88"}, - {"id": 321, "stationName": "Test Name 2", "gegrLat": "77.77", "gegrLon": "66.66"}, -] - -VALID_STATION = [ - {"id": 3764, "param": {"paramName": "particulate matter PM10", "paramCode": "PM10"}} -] - -VALID_INDEXES = { - "stIndexLevel": {"id": 1, "indexLevelName": "Good"}, - "pm10IndexLevel": {"id": 0, "indexLevelName": "Very good"}, -} - -VALID_SENSOR = {"key": "PM10", "values": [{"value": 11.11}]} - async def test_show_form(hass): """Test that the form is served with no input.""" @@ -43,7 +31,9 @@ async def test_show_form(hass): async def test_invalid_station_id(hass): """Test that errors are shown when measuring station ID is invalid.""" - with patch("gios.Gios._get_stations", return_value=VALID_STATIONS): + with patch( + "homeassistant.components.gios.Gios._get_stations", return_value=STATIONS + ): flow = config_flow.GiosFlowHandler() flow.hass = hass flow.context = {} @@ -57,10 +47,13 @@ async def test_invalid_station_id(hass): async def test_invalid_sensor_data(hass): """Test that errors are shown when sensor data is invalid.""" - with patch("gios.Gios._get_stations", return_value=VALID_STATIONS), patch( - "gios.Gios._get_station", return_value=VALID_STATION - ), patch("gios.Gios._get_station", return_value=VALID_STATION), patch( - "gios.Gios._get_sensor", return_value={} + with patch( + "homeassistant.components.gios.Gios._get_stations", return_value=STATIONS + ), patch( + "homeassistant.components.gios.Gios._get_station", + return_value=json.loads(load_fixture("gios/station.json")), + ), patch( + "homeassistant.components.gios.Gios._get_sensor", return_value={} ): flow = config_flow.GiosFlowHandler() flow.hass = hass @@ -73,7 +66,9 @@ async def test_invalid_sensor_data(hass): async def test_cannot_connect(hass): """Test that errors are shown when cannot connect to GIOS server.""" - with patch("gios.Gios._async_get", side_effect=ApiError("error")): + with patch( + "homeassistant.components.gios.Gios._async_get", side_effect=ApiError("error") + ): flow = config_flow.GiosFlowHandler() flow.hass = hass flow.context = {} @@ -85,12 +80,17 @@ async def test_cannot_connect(hass): async def test_create_entry(hass): """Test that the user step works.""" - with patch("gios.Gios._get_stations", return_value=VALID_STATIONS), patch( - "gios.Gios._get_station", return_value=VALID_STATION - ), patch("gios.Gios._get_station", return_value=VALID_STATION), patch( - "gios.Gios._get_sensor", return_value=VALID_SENSOR + with patch( + "homeassistant.components.gios.Gios._get_stations", return_value=STATIONS ), patch( - "gios.Gios._get_indexes", return_value=VALID_INDEXES + "homeassistant.components.gios.Gios._get_station", + return_value=json.loads(load_fixture("gios/station.json")), + ), patch( + "homeassistant.components.gios.Gios._get_all_sensors", + return_value=json.loads(load_fixture("gios/sensors.json")), + ), patch( + "homeassistant.components.gios.Gios._get_indexes", + return_value=json.loads(load_fixture("gios/indexes.json")), ): flow = config_flow.GiosFlowHandler() flow.hass = hass diff --git a/tests/components/gios/test_init.py b/tests/components/gios/test_init.py new file mode 100644 index 00000000000..0846ddfb4ca --- /dev/null +++ b/tests/components/gios/test_init.py @@ -0,0 +1,54 @@ +"""Test init of GIOS integration.""" +from homeassistant.components.gios.const import DOMAIN +from homeassistant.config_entries import ( + ENTRY_STATE_LOADED, + ENTRY_STATE_NOT_LOADED, + ENTRY_STATE_SETUP_RETRY, +) +from homeassistant.const import STATE_UNAVAILABLE + +from tests.async_mock import patch +from tests.common import MockConfigEntry +from tests.components.gios import init_integration + + +async def test_async_setup_entry(hass): + """Test a successful setup entry.""" + await init_integration(hass) + + state = hass.states.get("air_quality.home") + assert state is not None + assert state.state != STATE_UNAVAILABLE + assert state.state == "4" + + +async def test_config_not_ready(hass): + """Test for setup failure if connection to GIOS is missing.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="Home", + unique_id=123, + data={"station_id": 123, "name": "Home"}, + ) + + with patch( + "homeassistant.components.gios.Gios._get_stations", + side_effect=ConnectionError(), + ): + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + assert entry.state == ENTRY_STATE_SETUP_RETRY + + +async def test_unload_entry(hass): + """Test successful unload of entry.""" + entry = await init_integration(hass) + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert entry.state == ENTRY_STATE_LOADED + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_NOT_LOADED + assert not hass.data.get(DOMAIN) diff --git a/tests/fixtures/gios/indexes.json b/tests/fixtures/gios/indexes.json new file mode 100644 index 00000000000..4fe4293706c --- /dev/null +++ b/tests/fixtures/gios/indexes.json @@ -0,0 +1,29 @@ +{ + "id": 123, + "stCalcDate": "2020-07-31 15:10:17", + "stIndexLevel": { "id": 1, "indexLevelName": "dobry" }, + "stSourceDataDate": "2020-07-31 14:00:00", + "so2CalcDate": "2020-07-31 15:10:17", + "so2IndexLevel": { "id": 0, "indexLevelName": "bardzo dobry" }, + "so2SourceDataDate": "2020-07-31 14:00:00", + "no2CalcDate": 1596201017000, + "no2IndexLevel": { "id": 0, "indexLevelName": "dobry" }, + "no2SourceDataDate": "2020-07-31 14:00:00", + "coCalcDate": "2020-07-31 15:10:17", + "coIndexLevel": { "id": 0, "indexLevelName": "dobry" }, + "coSourceDataDate": "2020-07-31 14:00:00", + "pm10CalcDate": "2020-07-31 15:10:17", + "pm10IndexLevel": { "id": 0, "indexLevelName": "dobry" }, + "pm10SourceDataDate": "2020-07-31 14:00:00", + "pm25CalcDate": "2020-07-31 15:10:17", + "pm25IndexLevel": { "id": 0, "indexLevelName": "dobry" }, + "pm25SourceDataDate": "2020-07-31 14:00:00", + "o3CalcDate": "2020-07-31 15:10:17", + "o3IndexLevel": { "id": 1, "indexLevelName": "dobry" }, + "o3SourceDataDate": "2020-07-31 14:00:00", + "c6h6CalcDate": "2020-07-31 15:10:17", + "c6h6IndexLevel": { "id": 0, "indexLevelName": "bardzo dobry" }, + "c6h6SourceDataDate": "2020-07-31 14:00:00", + "stIndexStatus": true, + "stIndexCrParam": "OZON" + } \ No newline at end of file diff --git a/tests/fixtures/gios/sensors.json b/tests/fixtures/gios/sensors.json new file mode 100644 index 00000000000..3103a2bf16e --- /dev/null +++ b/tests/fixtures/gios/sensors.json @@ -0,0 +1,51 @@ +{ + "SO2": { + "values": [ + { "date": "2020-07-31 15:00:00", "value": 4.35478 }, + { "date": "2020-07-31 14:00:00", "value": 4.25478 }, + { "date": "2020-07-31 13:00:00", "value": 4.34309 } + ] + }, + "C6H6": { + "values": [ + { "date": "2020-07-31 15:00:00", "value": 0.23789 }, + { "date": "2020-07-31 14:00:00", "value": 0.22789 }, + { "date": "2020-07-31 13:00:00", "value": 0.21315 } + ] + }, + "CO": { + "values": [ + { "date": "2020-07-31 15:00:00", "value": 251.874 }, + { "date": "2020-07-31 14:00:00", "value": 250.874 }, + { "date": "2020-07-31 13:00:00", "value": 251.097 } + ] + }, + "NO2": { + "values": [ + { "date": "2020-07-31 15:00:00", "value": 7.13411 }, + { "date": "2020-07-31 14:00:00", "value": 7.33411 }, + { "date": "2020-07-31 13:00:00", "value": 9.32578 } + ] + }, + "O3": { + "values": [ + { "date": "2020-07-31 15:00:00", "value": 95.7768 }, + { "date": "2020-07-31 14:00:00", "value": 93.7768 }, + { "date": "2020-07-31 13:00:00", "value": 89.4232 } + ] + }, + "PM2.5": { + "values": [ + { "date": "2020-07-31 15:00:00", "value": 4 }, + { "date": "2020-07-31 14:00:00", "value": 4 }, + { "date": "2020-07-31 13:00:00", "value": 5 } + ] + }, + "PM10": { + "values": [ + { "date": "2020-07-31 15:00:00", "value": 16.8344 }, + { "date": "2020-07-31 14:00:00", "value": 17.8344 }, + { "date": "2020-07-31 13:00:00", "value": 20.8094 } + ] + } + } \ No newline at end of file diff --git a/tests/fixtures/gios/station.json b/tests/fixtures/gios/station.json new file mode 100644 index 00000000000..0eaa98a1d3c --- /dev/null +++ b/tests/fixtures/gios/station.json @@ -0,0 +1,72 @@ +[ + { + "id": 672, + "stationId": 117, + "param": { + "paramName": "dwutlenek siarki", + "paramFormula": "SO2", + "paramCode": "SO2", + "idParam": 1 + } + }, + { + "id": 658, + "stationId": 117, + "param": { + "paramName": "benzen", + "paramFormula": "C6H6", + "paramCode": "C6H6", + "idParam": 10 + } + }, + { + "id": 660, + "stationId": 117, + "param": { + "paramName": "tlenek węgla", + "paramFormula": "CO", + "paramCode": "CO", + "idParam": 8 + } + }, + { + "id": 665, + "stationId": 117, + "param": { + "paramName": "dwutlenek azotu", + "paramFormula": "NO2", + "paramCode": "NO2", + "idParam": 6 + } + }, + { + "id": 667, + "stationId": 117, + "param": { + "paramName": "ozon", + "paramFormula": "O3", + "paramCode": "O3", + "idParam": 5 + } + }, + { + "id": 670, + "stationId": 117, + "param": { + "paramName": "pył zawieszony PM2.5", + "paramFormula": "PM2.5", + "paramCode": "PM2.5", + "idParam": 69 + } + }, + { + "id": 14395, + "stationId": 117, + "param": { + "paramName": "pył zawieszony PM10", + "paramFormula": "PM10", + "paramCode": "PM10", + "idParam": 3 + } + } + ] \ No newline at end of file