core/tests/components/homeassistant/test_init.py

653 lines
21 KiB
Python
Raw Normal View History

"""The tests for Core components."""
import asyncio
2014-11-23 17:51:16 +00:00
import unittest
2021-01-01 21:31:56 +00:00
from unittest.mock import Mock, patch
import pytest
import voluptuous as vol
import yaml
2014-11-23 17:51:16 +00:00
from homeassistant import config
import homeassistant.components as comps
from homeassistant.components.homeassistant import (
ATTR_ENTRY_ID,
SERVICE_CHECK_CONFIG,
SERVICE_RELOAD_ALL,
SERVICE_RELOAD_CORE_CONFIG,
SERVICE_RELOAD_CUSTOM_TEMPLATES,
SERVICE_SET_LOCATION,
)
from homeassistant.const import (
2019-07-31 19:25:30 +00:00
ATTR_ENTITY_ID,
ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
EVENT_CORE_CONFIG_UPDATE,
2019-07-31 19:25:30 +00:00
SERVICE_HOMEASSISTANT_RESTART,
SERVICE_HOMEASSISTANT_STOP,
SERVICE_SAVE_PERSISTENT_STATES,
2019-07-31 19:25:30 +00:00
SERVICE_TOGGLE,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_OFF,
STATE_ON,
2019-07-31 19:25:30 +00:00
)
import homeassistant.core as ha
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, Unauthorized
from homeassistant.helpers import entity, entity_registry as er
from homeassistant.setup import async_setup_component
2014-11-23 17:51:16 +00:00
from tests.common import (
MockConfigEntry,
MockUser,
async_capture_events,
async_mock_service,
2019-07-31 19:25:30 +00:00
get_test_home_assistant,
mock_service,
patch_yaml_files,
)
2014-11-23 17:51:16 +00:00
def turn_on(hass, entity_id=None, **service_data):
"""Turn specified entity on if possible.
This is a legacy helper method. Do not use it for new tests.
"""
if entity_id is not None:
service_data[ATTR_ENTITY_ID] = entity_id
hass.services.call(ha.DOMAIN, SERVICE_TURN_ON, service_data)
def turn_off(hass, entity_id=None, **service_data):
"""Turn specified entity off.
This is a legacy helper method. Do not use it for new tests.
"""
if entity_id is not None:
service_data[ATTR_ENTITY_ID] = entity_id
hass.services.call(ha.DOMAIN, SERVICE_TURN_OFF, service_data)
def toggle(hass, entity_id=None, **service_data):
"""Toggle specified entity.
This is a legacy helper method. Do not use it for new tests.
"""
if entity_id is not None:
service_data[ATTR_ENTITY_ID] = entity_id
hass.services.call(ha.DOMAIN, SERVICE_TOGGLE, service_data)
def stop(hass):
"""Stop Home Assistant.
This is a legacy helper method. Do not use it for new tests.
"""
hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP)
def restart(hass):
"""Stop Home Assistant.
This is a legacy helper method. Do not use it for new tests.
"""
hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART)
def check_config(hass):
"""Check the config files.
This is a legacy helper method. Do not use it for new tests.
"""
hass.services.call(ha.DOMAIN, SERVICE_CHECK_CONFIG)
def reload_core_config(hass):
"""Reload the core config.
This is a legacy helper method. Do not use it for new tests.
"""
hass.services.call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG)
2014-11-23 17:51:16 +00:00
class TestComponentsCore(unittest.TestCase):
2016-03-09 09:25:50 +00:00
"""Test homeassistant.components module."""
2014-11-23 17:51:16 +00:00
def setUp(self):
2018-08-19 20:29:08 +00:00
"""Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
assert asyncio.run_coroutine_threadsafe(
2019-07-31 19:25:30 +00:00
async_setup_component(self.hass, "homeassistant", {}), self.hass.loop
).result()
2014-11-23 17:51:16 +00:00
2019-07-31 19:25:30 +00:00
self.hass.states.set("light.Bowl", STATE_ON)
self.hass.states.set("light.Ceiling", STATE_OFF)
2020-06-08 19:26:40 +00:00
self.addCleanup(self.hass.stop)
2014-11-23 20:57:29 +00:00
2014-11-23 17:51:16 +00:00
def test_is_on(self):
2016-03-09 09:25:50 +00:00
"""Test is_on method."""
2019-07-31 19:25:30 +00:00
assert comps.is_on(self.hass, "light.Bowl")
assert not comps.is_on(self.hass, "light.Ceiling")
assert comps.is_on(self.hass)
2019-07-31 19:25:30 +00:00
assert not comps.is_on(self.hass, "non_existing.entity")
def test_turn_on_without_entities(self):
"""Test turn_on method without entities."""
2019-07-31 19:25:30 +00:00
calls = mock_service(self.hass, "light", SERVICE_TURN_ON)
turn_on(self.hass)
self.hass.block_till_done()
assert len(calls) == 0
2014-11-23 17:51:16 +00:00
def test_turn_on(self):
2016-03-09 09:25:50 +00:00
"""Test turn_on method."""
2019-07-31 19:25:30 +00:00
calls = mock_service(self.hass, "light", SERVICE_TURN_ON)
turn_on(self.hass, "light.Ceiling")
self.hass.block_till_done()
assert len(calls) == 1
2014-11-23 17:51:16 +00:00
def test_turn_off(self):
2016-03-09 09:25:50 +00:00
"""Test turn_off method."""
2019-07-31 19:25:30 +00:00
calls = mock_service(self.hass, "light", SERVICE_TURN_OFF)
turn_off(self.hass, "light.Bowl")
self.hass.block_till_done()
assert len(calls) == 1
def test_toggle(self):
2016-03-09 09:25:50 +00:00
"""Test toggle method."""
2019-07-31 19:25:30 +00:00
calls = mock_service(self.hass, "light", SERVICE_TOGGLE)
toggle(self.hass, "light.Bowl")
self.hass.block_till_done()
assert len(calls) == 1
2019-07-31 19:25:30 +00:00
@patch("homeassistant.config.os.path.isfile", Mock(return_value=True))
def test_reload_core_conf(self):
"""Test reload core conf service."""
ent = entity.Entity()
2019-07-31 19:25:30 +00:00
ent.entity_id = "test.entity"
ent.hass = self.hass
ent.schedule_update_ha_state()
self.hass.block_till_done()
2019-07-31 19:25:30 +00:00
state = self.hass.states.get("test.entity")
assert state is not None
2019-07-31 19:25:30 +00:00
assert state.state == "unknown"
assert state.attributes == {}
files = {
2019-07-31 19:25:30 +00:00
config.YAML_CONFIG_FILE: yaml.dump(
{
ha.DOMAIN: {
"country": "SE", # To avoid creating issue country_not_configured
2019-07-31 19:25:30 +00:00
"latitude": 10,
"longitude": 20,
"customize": {"test.Entity": {"hello": "world"}},
}
}
2019-07-31 19:25:30 +00:00
)
}
with patch_yaml_files(files, True):
reload_core_config(self.hass)
self.hass.block_till_done()
assert self.hass.config.latitude == 10
assert self.hass.config.longitude == 20
ent.schedule_update_ha_state()
self.hass.block_till_done()
2019-07-31 19:25:30 +00:00
state = self.hass.states.get("test.entity")
assert state is not None
2019-07-31 19:25:30 +00:00
assert state.state == "unknown"
assert state.attributes.get("hello") == "world"
2019-07-31 19:25:30 +00:00
@patch("homeassistant.config.os.path.isfile", Mock(return_value=True))
@patch("homeassistant.components.homeassistant._LOGGER.error")
@patch("homeassistant.config.async_process_ha_core_config")
def test_reload_core_with_wrong_conf(self, mock_process, mock_error):
"""Test reload core conf service."""
2019-07-31 19:25:30 +00:00
files = {config.YAML_CONFIG_FILE: yaml.dump(["invalid", "config"])}
with patch_yaml_files(files, True):
reload_core_config(self.hass)
self.hass.block_till_done()
assert mock_error.called
assert mock_process.called is False
2020-04-25 21:32:55 +00:00
@patch("homeassistant.core.HomeAssistant.async_stop", return_value=None)
2019-07-31 19:25:30 +00:00
@patch(
"homeassistant.config.async_check_ha_config_file",
side_effect=HomeAssistantError("Test error"),
)
def test_restart_homeassistant_wrong_conf(self, mock_check, mock_restart):
"""Test stop service."""
restart(self.hass)
self.hass.block_till_done()
assert mock_check.called
assert not mock_restart.called
2020-04-25 21:32:55 +00:00
@patch("homeassistant.core.HomeAssistant.async_stop", return_value=None)
@patch("homeassistant.config.async_check_ha_config_file", return_value=None)
def test_check_config(self, mock_check, mock_stop):
"""Test stop service."""
check_config(self.hass)
self.hass.block_till_done()
assert mock_check.called
assert not mock_stop.called
async def test_turn_on_skips_domains_without_service(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
2018-06-07 13:58:54 +00:00
"""Test if turn_on is blocking domain with no service."""
2019-07-31 19:25:30 +00:00
await async_setup_component(hass, "homeassistant", {})
async_mock_service(hass, "light", SERVICE_TURN_ON)
hass.states.async_set("light.Bowl", STATE_ON)
hass.states.async_set("light.Ceiling", STATE_OFF)
2018-06-07 13:58:54 +00:00
# We can't test if our service call results in services being called
# because by mocking out the call service method, we mock out all
# So we mimic how the service registry calls services
2019-07-31 19:25:30 +00:00
service_call = ha.ServiceCall(
"homeassistant",
"turn_on",
{"entity_id": ["light.test", "sensor.bla", "binary_sensor.blub", "light.bla"]},
2019-07-31 19:25:30 +00:00
)
service = hass.services._services["homeassistant"]["turn_on"]
2018-06-07 13:58:54 +00:00
2019-07-31 19:25:30 +00:00
with patch(
2020-08-27 11:56:20 +00:00
"homeassistant.core.ServiceRegistry.async_call",
return_value=None,
2019-07-31 19:25:30 +00:00
) as mock_call:
await service.job.target(service_call)
2018-06-07 13:58:54 +00:00
assert mock_call.call_count == 1
2018-06-07 13:58:54 +00:00
assert mock_call.call_args_list[0][0] == (
2019-07-31 19:25:30 +00:00
"light",
"turn_on",
{"entity_id": ["light.bla", "light.test"]},
)
assert mock_call.call_args_list[0][1] == {
"blocking": True,
"context": service_call.context,
}
assert (
"The service homeassistant.turn_on does not support entities binary_sensor.blub, sensor.bla"
in caplog.text
2019-07-31 19:25:30 +00:00
)
2018-10-09 14:54:38 +00:00
async def test_entity_update(hass: HomeAssistant) -> None:
2018-10-09 14:54:38 +00:00
"""Test being able to call entity update."""
2019-07-31 19:25:30 +00:00
await async_setup_component(hass, "homeassistant", {})
with patch(
"homeassistant.components.homeassistant.async_update_entity",
2020-08-27 11:56:20 +00:00
return_value=None,
2019-07-31 19:25:30 +00:00
) as mock_update:
await hass.services.async_call(
"homeassistant",
"update_entity",
{"entity_id": ["light.kitchen"]},
blocking=True,
)
2018-10-09 14:54:38 +00:00
assert len(mock_update.mock_calls) == 1
2019-07-31 19:25:30 +00:00
assert mock_update.mock_calls[0][1][1] == "light.kitchen"
async def test_setting_location(hass: HomeAssistant) -> None:
"""Test setting the location."""
2019-07-31 19:25:30 +00:00
await async_setup_component(hass, "homeassistant", {})
events = async_capture_events(hass, EVENT_CORE_CONFIG_UPDATE)
# Just to make sure that we are updating values.
assert hass.config.latitude != 30
assert hass.config.longitude != 40
2019-07-31 19:25:30 +00:00
await hass.services.async_call(
"homeassistant",
"set_location",
{"latitude": 30, "longitude": 40},
blocking=True,
)
assert len(events) == 1
assert hass.config.latitude == 30
assert hass.config.longitude == 40
async def test_require_admin(
hass: HomeAssistant, hass_read_only_user: MockUser
) -> None:
"""Test services requiring admin."""
await async_setup_component(hass, "homeassistant", {})
for service in (
SERVICE_HOMEASSISTANT_RESTART,
SERVICE_HOMEASSISTANT_STOP,
SERVICE_CHECK_CONFIG,
SERVICE_RELOAD_CORE_CONFIG,
):
with pytest.raises(Unauthorized):
await hass.services.async_call(
ha.DOMAIN,
service,
{},
context=ha.Context(user_id=hass_read_only_user.id),
blocking=True,
)
2023-01-27 10:10:29 +00:00
pytest.fail(f"Should have raises for {service}")
with pytest.raises(Unauthorized):
await hass.services.async_call(
ha.DOMAIN,
SERVICE_SET_LOCATION,
{"latitude": 0, "longitude": 0},
context=ha.Context(user_id=hass_read_only_user.id),
blocking=True,
)
async def test_turn_on_off_toggle_schema(
hass: HomeAssistant, hass_read_only_user: MockUser
) -> None:
"""Test the schemas for the turn on/off/toggle services."""
await async_setup_component(hass, "homeassistant", {})
for service in SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE:
for invalid in None, "nothing", ENTITY_MATCH_ALL, ENTITY_MATCH_NONE:
with pytest.raises(vol.Invalid):
await hass.services.async_call(
ha.DOMAIN,
service,
{"entity_id": invalid},
context=ha.Context(user_id=hass_read_only_user.id),
blocking=True,
)
async def test_not_allowing_recursion(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test we do not allow recursion."""
await async_setup_component(hass, "homeassistant", {})
for service in SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE:
await hass.services.async_call(
2020-08-27 11:56:20 +00:00
ha.DOMAIN,
service,
{"entity_id": "homeassistant.light"},
blocking=True,
)
assert (
f"Called service homeassistant.{service} with invalid entities homeassistant.light"
in caplog.text
), service
async def test_reload_config_entry_by_entity_id(
hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
"""Test being able to reload a config entry by entity_id."""
await async_setup_component(hass, "homeassistant", {})
entry1 = MockConfigEntry(domain="mockdomain")
entry1.add_to_hass(hass)
entry2 = MockConfigEntry(domain="mockdomain")
entry2.add_to_hass(hass)
reg_entity1 = entity_registry.async_get_or_create(
"binary_sensor", "powerwall", "battery_charging", config_entry=entry1
)
reg_entity2 = entity_registry.async_get_or_create(
"binary_sensor", "powerwall", "battery_status", config_entry=entry2
)
with patch(
"homeassistant.config_entries.ConfigEntries.async_reload",
return_value=None,
) as mock_reload:
await hass.services.async_call(
"homeassistant",
"reload_config_entry",
{"entity_id": f"{reg_entity1.entity_id},{reg_entity2.entity_id}"},
blocking=True,
)
assert len(mock_reload.mock_calls) == 2
assert {mock_reload.mock_calls[0][1][0], mock_reload.mock_calls[1][1][0]} == {
entry1.entry_id,
entry2.entry_id,
}
with pytest.raises(ValueError):
await hass.services.async_call(
"homeassistant",
"reload_config_entry",
{"entity_id": "unknown.entity_id"},
blocking=True,
)
async def test_reload_config_entry_by_entry_id(hass: HomeAssistant) -> None:
"""Test being able to reload a config entry by config entry id."""
await async_setup_component(hass, "homeassistant", {})
with patch(
"homeassistant.config_entries.ConfigEntries.async_reload",
return_value=None,
) as mock_reload:
await hass.services.async_call(
"homeassistant",
"reload_config_entry",
{ATTR_ENTRY_ID: "8955375327824e14ba89e4b29cc3ec9a"},
blocking=True,
)
assert len(mock_reload.mock_calls) == 1
assert mock_reload.mock_calls[0][1][0] == "8955375327824e14ba89e4b29cc3ec9a"
@pytest.mark.parametrize(
"service", [SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP]
)
async def test_raises_when_db_upgrade_in_progress(
hass: HomeAssistant, service, caplog: pytest.LogCaptureFixture
) -> None:
"""Test an exception is raised when the database migration is in progress."""
await async_setup_component(hass, "homeassistant", {})
with pytest.raises(HomeAssistantError), patch(
"homeassistant.helpers.recorder.async_migration_in_progress",
return_value=True,
) as mock_async_migration_in_progress:
await hass.services.async_call(
"homeassistant",
service,
blocking=True,
)
assert "The system cannot" in caplog.text
assert "while a database upgrade in progress" in caplog.text
assert mock_async_migration_in_progress.called
caplog.clear()
with patch(
"homeassistant.helpers.recorder.async_migration_in_progress",
return_value=False,
) as mock_async_migration_in_progress, patch(
"homeassistant.config.async_check_ha_config_file", return_value=None
):
await hass.services.async_call(
"homeassistant",
service,
blocking=True,
)
assert "The system cannot" not in caplog.text
assert "while a database upgrade in progress" not in caplog.text
assert mock_async_migration_in_progress.called
async def test_raises_when_config_is_invalid(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test an exception is raised when the configuration is invalid."""
await async_setup_component(hass, "homeassistant", {})
with pytest.raises(HomeAssistantError), patch(
"homeassistant.helpers.recorder.async_migration_in_progress",
return_value=False,
), patch(
"homeassistant.config.async_check_ha_config_file", return_value=["Error 1"]
) as mock_async_check_ha_config_file:
await hass.services.async_call(
"homeassistant",
SERVICE_HOMEASSISTANT_RESTART,
blocking=True,
)
assert "The system cannot" in caplog.text
assert "because the configuration is not valid" in caplog.text
assert "Error 1" in caplog.text
assert mock_async_check_ha_config_file.called
caplog.clear()
with patch(
"homeassistant.helpers.recorder.async_migration_in_progress",
return_value=False,
), patch(
"homeassistant.config.async_check_ha_config_file", return_value=None
) as mock_async_check_ha_config_file:
await hass.services.async_call(
"homeassistant",
SERVICE_HOMEASSISTANT_RESTART,
blocking=True,
)
assert mock_async_check_ha_config_file.called
async def test_restart_homeassistant(hass: HomeAssistant) -> None:
"""Test we can restart when there is no configuration error."""
await async_setup_component(hass, "homeassistant", {})
with patch(
"homeassistant.config.async_check_ha_config_file", return_value=None
) as mock_check, patch(
"homeassistant.core.HomeAssistant.async_stop", return_value=None
) as mock_restart:
await hass.services.async_call(
"homeassistant",
SERVICE_HOMEASSISTANT_RESTART,
blocking=True,
)
assert mock_check.called
await hass.async_block_till_done()
assert mock_restart.called
async def test_stop_homeassistant(hass: HomeAssistant) -> None:
"""Test we can stop when there is a configuration error."""
await async_setup_component(hass, "homeassistant", {})
with patch(
"homeassistant.config.async_check_ha_config_file", return_value=None
) as mock_check, patch(
"homeassistant.core.HomeAssistant.async_stop", return_value=None
) as mock_restart:
await hass.services.async_call(
"homeassistant",
SERVICE_HOMEASSISTANT_STOP,
blocking=True,
)
assert not mock_check.called
await hass.async_block_till_done()
assert mock_restart.called
async def test_save_persistent_states(hass: HomeAssistant) -> None:
"""Test we can call save_persistent_states."""
await async_setup_component(hass, "homeassistant", {})
with patch(
"homeassistant.helpers.restore_state.RestoreStateData.async_save_persistent_states",
return_value=None,
) as mock_save:
await hass.services.async_call(
"homeassistant",
SERVICE_SAVE_PERSISTENT_STATES,
blocking=True,
)
assert mock_save.called
async def test_reload_custom_templates(hass: HomeAssistant) -> None:
"""Test we can call reload_custom_templates."""
await async_setup_component(hass, "homeassistant", {})
with patch(
"homeassistant.components.homeassistant.async_load_custom_templates",
return_value=None,
) as mock_load_custom_templates:
await hass.services.async_call(
"homeassistant",
SERVICE_RELOAD_CUSTOM_TEMPLATES,
blocking=True,
)
assert mock_load_custom_templates.called
async def test_reload_all(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test reload_all service."""
await async_setup_component(hass, "homeassistant", {})
test1 = async_mock_service(hass, "test1", "reload")
test2 = async_mock_service(hass, "test2", "reload")
no_reload = async_mock_service(hass, "test3", "not_reload")
notify = async_mock_service(hass, "notify", "reload")
core_config = async_mock_service(hass, "homeassistant", "reload_core_config")
themes = async_mock_service(hass, "frontend", "reload_themes")
jinja = async_mock_service(hass, "homeassistant", "reload_custom_templates")
with patch(
"homeassistant.config.async_check_ha_config_file",
return_value=None,
) as mock_async_check_ha_config_file:
await hass.services.async_call(
"homeassistant",
SERVICE_RELOAD_ALL,
blocking=True,
)
assert mock_async_check_ha_config_file.called
assert len(test1) == 1
assert len(test2) == 1
assert len(no_reload) == 0
assert len(notify) == 0
assert len(core_config) == 1
assert len(themes) == 1
with pytest.raises(
HomeAssistantError,
match=(
"Cannot quick reload all YAML configurations because the configuration is "
"not valid: Oh no, drama!"
),
), patch(
"homeassistant.config.async_check_ha_config_file",
return_value="Oh no, drama!",
) as mock_async_check_ha_config_file:
await hass.services.async_call(
"homeassistant",
SERVICE_RELOAD_ALL,
blocking=True,
)
assert mock_async_check_ha_config_file.called
assert (
"The system cannot reload because the configuration is not valid: Oh no, drama!"
in caplog.text
)
# None have been called again
assert len(test1) == 1
assert len(test2) == 1
assert len(core_config) == 1
assert len(themes) == 1
assert len(jinja) == 1