From de313dcc3fa6d796852169d3356b0bff9916dca8 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 14 Nov 2022 11:13:20 +0100 Subject: [PATCH] Improve config tests (#81898) * Improve config tests * Fix stale docstring * Use os.path.basename --- tests/components/config/conftest.py | 73 +++++++ tests/components/config/test_automation.py | 167 ++++++++-------- tests/components/config/test_scene.py | 216 +++++++++------------ tests/components/config/test_script.py | 29 ++- 4 files changed, 259 insertions(+), 226 deletions(-) create mode 100644 tests/components/config/conftest.py diff --git a/tests/components/config/conftest.py b/tests/components/config/conftest.py new file mode 100644 index 00000000000..e6f1532428e --- /dev/null +++ b/tests/components/config/conftest.py @@ -0,0 +1,73 @@ +"""Test fixtures for the config integration.""" +from contextlib import contextmanager +from copy import deepcopy +import json +import logging +from os.path import basename +from unittest.mock import patch + +import pytest + +from homeassistant.core import HomeAssistant + +from tests.common import raise_contains_mocks + +_LOGGER = logging.getLogger(__name__) + + +@contextmanager +def mock_config_store(data=None): + """Mock config yaml store. + + Data is a dict {'key': {'version': version, 'data': data}} + + Written data will be converted to JSON to ensure JSON parsing works. + """ + if data is None: + data = {} + + def mock_read(path): + """Mock version of load.""" + file_name = basename(path) + _LOGGER.info("Reading data from %s: %s", file_name, data.get(file_name)) + return deepcopy(data.get(file_name)) + + def mock_write(path, data_to_write): + """Mock version of write.""" + file_name = basename(path) + _LOGGER.info("Writing data to %s: %s", file_name, data_to_write) + raise_contains_mocks(data_to_write) + # To ensure that the data can be serialized + data[file_name] = json.loads(json.dumps(data_to_write)) + + async def mock_async_hass_config_yaml(hass: HomeAssistant) -> dict: + """Mock version of async_hass_config_yaml.""" + result = {} + # Return a configuration.yaml with "automation" mapped to the contents of + # automations.yaml and so on. + for key, value in data.items(): + result[key.partition(".")[0][0:-1]] = deepcopy(value) + _LOGGER.info("Reading data from configuration.yaml: %s", result) + return result + + with patch( + "homeassistant.components.config._read", + side_effect=mock_read, + autospec=True, + ), patch( + "homeassistant.components.config._write", + side_effect=mock_write, + autospec=True, + ), patch( + "homeassistant.config.async_hass_config_yaml", + side_effect=mock_async_hass_config_yaml, + autospec=True, + ): + yield data + + +@pytest.fixture +def hass_config_store(): + """Fixture to mock config yaml store.""" + with mock_config_store() as stored_data: + yield stored_data diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 6f782fdbbff..0a5a79c7d15 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -7,6 +7,7 @@ import pytest from homeassistant.bootstrap import async_setup_component from homeassistant.components import config +from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @@ -23,19 +24,18 @@ async def setup_automation( @pytest.mark.parametrize("automation_config", ({},)) -async def test_get_device_config(hass, hass_client, setup_automation): - """Test getting device config.""" +async def test_get_automation_config( + hass: HomeAssistant, hass_client, hass_config_store, setup_automation +): + """Test getting automation config.""" with patch.object(config, "SECTIONS", ["automation"]): await async_setup_component(hass, "config", {}) client = await hass_client() - def mock_read(path): - """Mock reading data.""" - return [{"id": "sun"}, {"id": "moon"}] + hass_config_store["automations.yaml"] = [{"id": "sun"}, {"id": "moon"}] - with patch("homeassistant.components.config._read", mock_read): - resp = await client.get("/api/config/automation/config/moon") + resp = await client.get("/api/config/automation/config/moon") assert resp.status == HTTPStatus.OK result = await resp.json() @@ -44,85 +44,81 @@ async def test_get_device_config(hass, hass_client, setup_automation): @pytest.mark.parametrize("automation_config", ({},)) -async def test_update_device_config(hass, hass_client, setup_automation): - """Test updating device config.""" +async def test_update_automation_config( + hass: HomeAssistant, hass_client, hass_config_store, setup_automation +): + """Test updating automation config.""" with patch.object(config, "SECTIONS", ["automation"]): await async_setup_component(hass, "config", {}) + assert sorted(hass.states.async_entity_ids("automation")) == [] + client = await hass_client() orig_data = [{"id": "sun"}, {"id": "moon"}] + hass_config_store["automations.yaml"] = orig_data - def mock_read(path): - """Mock reading data.""" - return orig_data - - written = [] - - def mock_write(path, data): - """Mock writing data.""" - written.append(data) - - with patch("homeassistant.components.config._read", mock_read), patch( - "homeassistant.components.config._write", mock_write - ), patch("homeassistant.config.async_hass_config_yaml", return_value={}): - resp = await client.post( - "/api/config/automation/config/moon", - data=json.dumps({"trigger": [], "action": [], "condition": []}), - ) + resp = await client.post( + "/api/config/automation/config/moon", + data=json.dumps({"trigger": [], "action": [], "condition": []}), + ) + await hass.async_block_till_done() + assert sorted(hass.states.async_entity_ids("automation")) == [ + "automation.automation_0" + ] assert resp.status == HTTPStatus.OK result = await resp.json() assert result == {"result": "ok"} - assert list(orig_data[1]) == ["id", "trigger", "condition", "action"] - assert orig_data[1] == {"id": "moon", "trigger": [], "condition": [], "action": []} - assert written[0] == orig_data + new_data = hass_config_store["automations.yaml"] + assert list(new_data[1]) == ["id", "trigger", "condition", "action"] + assert new_data[1] == {"id": "moon", "trigger": [], "condition": [], "action": []} @pytest.mark.parametrize("automation_config", ({},)) -async def test_update_remove_key_device_config(hass, hass_client, setup_automation): - """Test updating device config while removing a key.""" +async def test_update_remove_key_automation_config( + hass: HomeAssistant, hass_client, hass_config_store, setup_automation +): + """Test updating automation config while removing a key.""" with patch.object(config, "SECTIONS", ["automation"]): await async_setup_component(hass, "config", {}) + assert sorted(hass.states.async_entity_ids("automation")) == [] + client = await hass_client() orig_data = [{"id": "sun", "key": "value"}, {"id": "moon", "key": "value"}] + hass_config_store["automations.yaml"] = orig_data - def mock_read(path): - """Mock reading data.""" - return orig_data - - written = [] - - def mock_write(path, data): - """Mock writing data.""" - written.append(data) - - with patch("homeassistant.components.config._read", mock_read), patch( - "homeassistant.components.config._write", mock_write - ), patch("homeassistant.config.async_hass_config_yaml", return_value={}): - resp = await client.post( - "/api/config/automation/config/moon", - data=json.dumps({"trigger": [], "action": [], "condition": []}), - ) + resp = await client.post( + "/api/config/automation/config/moon", + data=json.dumps({"trigger": [], "action": [], "condition": []}), + ) + await hass.async_block_till_done() + assert sorted(hass.states.async_entity_ids("automation")) == [ + "automation.automation_0" + ] assert resp.status == HTTPStatus.OK result = await resp.json() assert result == {"result": "ok"} - assert list(orig_data[1]) == ["id", "trigger", "condition", "action"] - assert orig_data[1] == {"id": "moon", "trigger": [], "condition": [], "action": []} - assert written[0] == orig_data + new_data = hass_config_store["automations.yaml"] + assert list(new_data[1]) == ["id", "trigger", "condition", "action"] + assert new_data[1] == {"id": "moon", "trigger": [], "condition": [], "action": []} @pytest.mark.parametrize("automation_config", ({},)) -async def test_bad_formatted_automations(hass, hass_client, setup_automation): +async def test_bad_formatted_automations( + hass: HomeAssistant, hass_client, hass_config_store, setup_automation +): """Test that we handle automations without ID.""" with patch.object(config, "SECTIONS", ["automation"]): await async_setup_component(hass, "config", {}) + assert sorted(hass.states.async_entity_ids("automation")) == [] + client = await hass_client() orig_data = [ @@ -132,34 +128,25 @@ async def test_bad_formatted_automations(hass, hass_client, setup_automation): }, {"id": "moon"}, ] + hass_config_store["automations.yaml"] = orig_data - def mock_read(path): - """Mock reading data.""" - return orig_data - - written = [] - - def mock_write(path, data): - """Mock writing data.""" - written.append(data) - - with patch("homeassistant.components.config._read", mock_read), patch( - "homeassistant.components.config._write", mock_write - ), patch("homeassistant.config.async_hass_config_yaml", return_value={}): - resp = await client.post( - "/api/config/automation/config/moon", - data=json.dumps({"trigger": [], "action": [], "condition": []}), - ) - await hass.async_block_till_done() + resp = await client.post( + "/api/config/automation/config/moon", + data=json.dumps({"trigger": [], "action": [], "condition": []}), + ) + await hass.async_block_till_done() + assert sorted(hass.states.async_entity_ids("automation")) == [ + "automation.automation_0" + ] assert resp.status == HTTPStatus.OK result = await resp.json() assert result == {"result": "ok"} - # Verify ID added to orig_data - assert "id" in orig_data[0] - - assert orig_data[1] == {"id": "moon", "trigger": [], "condition": [], "action": []} + # Verify ID added + new_data = hass_config_store["automations.yaml"] + assert "id" in new_data[0] + assert new_data[1] == {"id": "moon", "trigger": [], "condition": [], "action": []} @pytest.mark.parametrize( @@ -179,7 +166,9 @@ async def test_bad_formatted_automations(hass, hass_client, setup_automation): ], ), ) -async def test_delete_automation(hass, hass_client, setup_automation): +async def test_delete_automation( + hass: HomeAssistant, hass_client, hass_config_store, setup_automation +): """Test deleting an automation.""" ent_reg = er.async_get(hass) @@ -188,31 +177,27 @@ async def test_delete_automation(hass, hass_client, setup_automation): with patch.object(config, "SECTIONS", ["automation"]): assert await async_setup_component(hass, "config", {}) + assert sorted(hass.states.async_entity_ids("automation")) == [ + "automation.automation_0", + "automation.automation_1", + ] + client = await hass_client() orig_data = [{"id": "sun"}, {"id": "moon"}] + hass_config_store["automations.yaml"] = orig_data - def mock_read(path): - """Mock reading data.""" - return orig_data + resp = await client.delete("/api/config/automation/config/sun") + await hass.async_block_till_done() - written = [] - - def mock_write(path, data): - """Mock writing data.""" - written.append(data) - - with patch("homeassistant.components.config._read", mock_read), patch( - "homeassistant.components.config._write", mock_write - ), patch("homeassistant.config.async_hass_config_yaml", return_value={}): - resp = await client.delete("/api/config/automation/config/sun") - await hass.async_block_till_done() + assert sorted(hass.states.async_entity_ids("automation")) == [ + "automation.automation_1", + ] assert resp.status == HTTPStatus.OK result = await resp.json() assert result == {"result": "ok"} - assert len(written) == 1 - assert written[0][0]["id"] == "moon" + assert hass_config_store["automations.yaml"] == [{"id": "moon"}] assert len(ent_reg.entities) == 1 diff --git a/tests/components/config/test_scene.py b/tests/components/config/test_scene.py index 69f75cc5895..781e4000c25 100644 --- a/tests/components/config/test_scene.py +++ b/tests/components/config/test_scene.py @@ -1,14 +1,13 @@ """Test Automation config panel.""" from http import HTTPStatus import json -from unittest.mock import patch +from unittest.mock import ANY, patch import pytest from homeassistant.bootstrap import async_setup_component from homeassistant.components import config from homeassistant.helpers import entity_registry as er -from homeassistant.util.yaml import dump @pytest.fixture @@ -18,114 +17,98 @@ async def setup_scene(hass, scene_config): @pytest.mark.parametrize("scene_config", ({},)) -async def test_create_scene(hass, hass_client, setup_scene): +async def test_create_scene(hass, hass_client, hass_config_store, setup_scene): """Test creating a scene.""" with patch.object(config, "SECTIONS", ["scene"]): await async_setup_component(hass, "config", {}) + assert sorted(hass.states.async_entity_ids("scene")) == [] + client = await hass_client() - def mock_read(path): - """Mock reading data.""" - return None + orig_data = {} + hass_config_store["scenes.yaml"] = orig_data - written = [] + resp = await client.post( + "/api/config/scene/config/light_off", + data=json.dumps( + { + # "id": "light_off", # The id should be added when writing + "name": "Lights off", + "entities": {"light.bedroom": {"state": "off"}}, + } + ), + ) + await hass.async_block_till_done() - def mock_write(path, data): - """Mock writing data.""" - data = dump(data) - written.append(data) - - with patch("homeassistant.components.config._read", mock_read), patch( - "homeassistant.components.config._write", mock_write - ), patch("homeassistant.config.async_hass_config_yaml", return_value={}): - resp = await client.post( - "/api/config/scene/config/light_off", - data=json.dumps( - { - # "id": "light_off", - "name": "Lights off", - "entities": {"light.bedroom": {"state": "off"}}, - } - ), - ) + assert sorted(hass.states.async_entity_ids("scene")) == [ + "scene.lights_off", + ] assert resp.status == HTTPStatus.OK result = await resp.json() assert result == {"result": "ok"} - assert len(written) == 1 - written_yaml = written[0] - assert ( - written_yaml - == """- id: light_off - name: Lights off - entities: - light.bedroom: - state: 'off' -""" - ) + assert hass_config_store["scenes.yaml"] == [ + { + "id": "light_off", + "name": "Lights off", + "entities": {"light.bedroom": {"state": "off"}}, + } + ] @pytest.mark.parametrize("scene_config", ({},)) -async def test_update_scene(hass, hass_client, setup_scene): +async def test_update_scene(hass, hass_client, hass_config_store, setup_scene): """Test updating a scene.""" with patch.object(config, "SECTIONS", ["scene"]): await async_setup_component(hass, "config", {}) + assert sorted(hass.states.async_entity_ids("scene")) == [] + client = await hass_client() orig_data = [{"id": "light_on"}, {"id": "light_off"}] + hass_config_store["scenes.yaml"] = orig_data - def mock_read(path): - """Mock reading data.""" - return orig_data + resp = await client.post( + "/api/config/scene/config/light_off", + data=json.dumps( + { + "id": "light_off", + "name": "Lights off", + "entities": {"light.bedroom": {"state": "off"}}, + } + ), + ) + await hass.async_block_till_done() - written = [] - - def mock_write(path, data): - """Mock writing data.""" - data = dump(data) - written.append(data) - - with patch("homeassistant.components.config._read", mock_read), patch( - "homeassistant.components.config._write", mock_write - ), patch("homeassistant.config.async_hass_config_yaml", return_value={}): - resp = await client.post( - "/api/config/scene/config/light_off", - data=json.dumps( - { - "id": "light_off", - "name": "Lights off", - "entities": {"light.bedroom": {"state": "off"}}, - } - ), - ) + assert sorted(hass.states.async_entity_ids("scene")) == [ + "scene.lights_off", + ] assert resp.status == HTTPStatus.OK result = await resp.json() assert result == {"result": "ok"} - assert len(written) == 1 - written_yaml = written[0] - assert ( - written_yaml - == """- id: light_on -- id: light_off - name: Lights off - entities: - light.bedroom: - state: 'off' -""" - ) + assert hass_config_store["scenes.yaml"] == [ + {"id": "light_on"}, + { + "id": "light_off", + "name": "Lights off", + "entities": {"light.bedroom": {"state": "off"}}, + }, + ] @pytest.mark.parametrize("scene_config", ({},)) -async def test_bad_formatted_scene(hass, hass_client, setup_scene): +async def test_bad_formatted_scene(hass, hass_client, hass_config_store, setup_scene): """Test that we handle scene without ID.""" with patch.object(config, "SECTIONS", ["scene"]): await async_setup_component(hass, "config", {}) + assert sorted(hass.states.async_entity_ids("scene")) == [] + client = await hass_client() orig_data = [ @@ -135,43 +118,40 @@ async def test_bad_formatted_scene(hass, hass_client, setup_scene): }, {"id": "light_off"}, ] + hass_config_store["scenes.yaml"] = orig_data - def mock_read(path): - """Mock reading data.""" - return orig_data + resp = await client.post( + "/api/config/scene/config/light_off", + data=json.dumps( + { + "id": "light_off", + "name": "Lights off", + "entities": {"light.bedroom": {"state": "off"}}, + } + ), + ) + await hass.async_block_till_done() - written = [] - - def mock_write(path, data): - """Mock writing data.""" - written.append(data) - - with patch("homeassistant.components.config._read", mock_read), patch( - "homeassistant.components.config._write", mock_write - ), patch("homeassistant.config.async_hass_config_yaml", return_value={}): - resp = await client.post( - "/api/config/scene/config/light_off", - data=json.dumps( - { - "id": "light_off", - "name": "Lights off", - "entities": {"light.bedroom": {"state": "off"}}, - } - ), - ) + assert sorted(hass.states.async_entity_ids("scene")) == [ + "scene.lights_off", + ] assert resp.status == HTTPStatus.OK result = await resp.json() assert result == {"result": "ok"} # Verify ID added to orig_data - assert "id" in orig_data[0] - - assert orig_data[1] == { - "id": "light_off", - "name": "Lights off", - "entities": {"light.bedroom": {"state": "off"}}, - } + assert hass_config_store["scenes.yaml"] == [ + { + "id": ANY, + "entities": {"light.bedroom": "on"}, + }, + { + "id": "light_off", + "name": "Lights off", + "entities": {"light.bedroom": {"state": "off"}}, + }, + ] @pytest.mark.parametrize( @@ -183,7 +163,7 @@ async def test_bad_formatted_scene(hass, hass_client, setup_scene): ], ), ) -async def test_delete_scene(hass, hass_client, setup_scene): +async def test_delete_scene(hass, hass_client, hass_config_store, setup_scene): """Test deleting a scene.""" ent_reg = er.async_get(hass) @@ -192,31 +172,29 @@ async def test_delete_scene(hass, hass_client, setup_scene): with patch.object(config, "SECTIONS", ["scene"]): assert await async_setup_component(hass, "config", {}) + assert sorted(hass.states.async_entity_ids("scene")) == [ + "scene.light_off", + "scene.light_on", + ] + client = await hass_client() orig_data = [{"id": "light_on"}, {"id": "light_off"}] + hass_config_store["scenes.yaml"] = orig_data - def mock_read(path): - """Mock reading data.""" - return orig_data + resp = await client.delete("/api/config/scene/config/light_on") + await hass.async_block_till_done() - written = [] - - def mock_write(path, data): - """Mock writing data.""" - written.append(data) - - with patch("homeassistant.components.config._read", mock_read), patch( - "homeassistant.components.config._write", mock_write - ), patch("homeassistant.config.async_hass_config_yaml", return_value={}): - resp = await client.delete("/api/config/scene/config/light_on") - await hass.async_block_till_done() + assert sorted(hass.states.async_entity_ids("scene")) == [ + "scene.light_off", + ] assert resp.status == HTTPStatus.OK result = await resp.json() assert result == {"result": "ok"} - assert len(written) == 1 - assert written[0][0]["id"] == "light_off" + assert hass_config_store["scenes.yaml"] == [ + {"id": "light_off"}, + ] assert len(ent_reg.entities) == 1 diff --git a/tests/components/config/test_script.py b/tests/components/config/test_script.py index 4b6ca1bdc8f..b8e980c29b9 100644 --- a/tests/components/config/test_script.py +++ b/tests/components/config/test_script.py @@ -26,38 +26,35 @@ async def setup_script(hass, script_config, stub_blueprint_populate): # noqa: F }, ), ) -async def test_delete_script(hass, hass_client): +async def test_delete_script(hass, hass_client, hass_config_store): """Test deleting a script.""" with patch.object(config, "SECTIONS", ["script"]): await async_setup_component(hass, "config", {}) + assert sorted(hass.states.async_entity_ids("script")) == [ + "script.one", + "script.two", + ] + ent_reg = er.async_get(hass) assert len(ent_reg.entities) == 2 client = await hass_client() orig_data = {"one": {}, "two": {}} + hass_config_store["scripts.yaml"] = orig_data - def mock_read(path): - """Mock reading data.""" - return orig_data + resp = await client.delete("/api/config/script/config/two") + await hass.async_block_till_done() - written = [] - - def mock_write(path, data): - """Mock writing data.""" - written.append(data) - - with patch("homeassistant.components.config._read", mock_read), patch( - "homeassistant.components.config._write", mock_write - ): - resp = await client.delete("/api/config/script/config/two") + assert sorted(hass.states.async_entity_ids("script")) == [ + "script.one", + ] assert resp.status == HTTPStatus.OK result = await resp.json() assert result == {"result": "ok"} - assert len(written) == 1 - assert written[0] == {"one": {}} + assert hass_config_store["scripts.yaml"] == {"one": {}} assert len(ent_reg.entities) == 1