core/tests/components/logbook/test_init.py

3005 lines
102 KiB
Python

"""The tests for the logbook component."""
import asyncio
from collections.abc import Callable
from datetime import datetime, timedelta
from http import HTTPStatus
from unittest.mock import Mock
from freezegun import freeze_time
import pytest
import voluptuous as vol
from homeassistant.components import logbook, recorder
# pylint: disable-next=hass-component-root-import
from homeassistant.components.alexa.smart_home import EVENT_ALEXA_SMART_HOME
from homeassistant.components.automation import EVENT_AUTOMATION_TRIGGERED
from homeassistant.components.logbook.models import EventAsRow, LazyEventPartialState
from homeassistant.components.logbook.processor import EventProcessor
from homeassistant.components.logbook.queries.common import PSEUDO_EVENT_STATE_CHANGED
from homeassistant.components.recorder import Recorder
from homeassistant.components.script import EVENT_SCRIPT_STARTED
from homeassistant.components.sensor import SensorStateClass
from homeassistant.const import (
ATTR_DOMAIN,
ATTR_ENTITY_ID,
ATTR_FRIENDLY_NAME,
ATTR_NAME,
ATTR_SERVICE,
ATTR_UNIT_OF_MEASUREMENT,
CONF_DOMAINS,
CONF_ENTITIES,
CONF_EXCLUDE,
CONF_INCLUDE,
EVENT_CALL_SERVICE,
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STARTED,
EVENT_HOMEASSISTANT_STOP,
EVENT_LOGBOOK_ENTRY,
STATE_OFF,
STATE_ON,
)
import homeassistant.core as ha
from homeassistant.core import Event, HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.entityfilter import CONF_ENTITY_GLOBS
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from .common import MockRow, mock_humanify
from tests.common import MockConfigEntry, async_capture_events, mock_platform
from tests.components.recorder.common import (
async_recorder_block_till_done,
async_wait_recording_done,
)
from tests.typing import ClientSessionGenerator, WebSocketGenerator
EMPTY_CONFIG = logbook.CONFIG_SCHEMA({logbook.DOMAIN: {}})
@pytest.fixture
async def hass_(recorder_mock: Recorder, hass: HomeAssistant) -> HomeAssistant:
"""Set up things to be run when tests are started."""
assert await async_setup_component(hass, logbook.DOMAIN, EMPTY_CONFIG)
return hass
@pytest.fixture
async def set_utc(hass: HomeAssistant) -> None:
"""Set timezone to UTC."""
await hass.config.async_set_time_zone("UTC")
async def test_service_call_create_logbook_entry(hass_: HomeAssistant) -> None:
"""Test if service call create log book entry."""
calls = async_capture_events(hass_, logbook.EVENT_LOGBOOK_ENTRY)
await hass_.services.async_call(
logbook.DOMAIN,
"log",
{
logbook.ATTR_NAME: "Alarm",
logbook.ATTR_MESSAGE: "is triggered",
logbook.ATTR_DOMAIN: "switch",
logbook.ATTR_ENTITY_ID: "switch.test_switch",
},
True,
)
await hass_.services.async_call(
logbook.DOMAIN,
"log",
{
logbook.ATTR_NAME: "This entry",
logbook.ATTR_MESSAGE: "has no domain or entity_id",
},
True,
)
# Logbook entry service call results in firing an event.
# Our service call will unblock when the event listeners have been
# scheduled. This means that they may not have been processed yet.
await async_wait_recording_done(hass_)
event_processor = EventProcessor(hass_, (EVENT_LOGBOOK_ENTRY,))
events = list(
event_processor.get_events(
dt_util.utcnow() - timedelta(hours=1),
dt_util.utcnow() + timedelta(hours=1),
)
)
assert len(events) == 2
assert len(calls) == 2
first_call = calls[-2]
assert first_call.data.get(logbook.ATTR_NAME) == "Alarm"
assert first_call.data.get(logbook.ATTR_MESSAGE) == "is triggered"
assert first_call.data.get(logbook.ATTR_DOMAIN) == "switch"
assert first_call.data.get(logbook.ATTR_ENTITY_ID) == "switch.test_switch"
last_call = calls[-1]
assert last_call.data.get(logbook.ATTR_NAME) == "This entry"
assert last_call.data.get(logbook.ATTR_MESSAGE) == "has no domain or entity_id"
assert last_call.data.get(logbook.ATTR_DOMAIN) == "logbook"
@pytest.mark.usefixtures("recorder_mock")
async def test_service_call_create_logbook_entry_invalid_entity_id(
hass: HomeAssistant,
) -> None:
"""Test if service call create log book entry with an invalid entity id."""
await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
hass.bus.async_fire(
logbook.EVENT_LOGBOOK_ENTRY,
{
logbook.ATTR_NAME: "Alarm",
logbook.ATTR_MESSAGE: "is triggered",
logbook.ATTR_DOMAIN: "switch",
logbook.ATTR_ENTITY_ID: 1234,
},
)
await async_wait_recording_done(hass)
event_processor = EventProcessor(hass, (EVENT_LOGBOOK_ENTRY,))
events = list(
event_processor.get_events(
dt_util.utcnow() - timedelta(hours=1),
dt_util.utcnow() + timedelta(hours=1),
)
)
assert len(events) == 1
assert events[0][logbook.ATTR_DOMAIN] == "switch"
assert events[0][logbook.ATTR_NAME] == "Alarm"
assert events[0][logbook.ATTR_ENTITY_ID] == 1234
assert events[0][logbook.ATTR_MESSAGE] == "is triggered"
async def test_service_call_create_log_book_entry_no_message(
hass_: HomeAssistant,
) -> None:
"""Test if service call create log book entry without message."""
calls = async_capture_events(hass_, logbook.EVENT_LOGBOOK_ENTRY)
with pytest.raises(vol.Invalid):
await hass_.services.async_call(logbook.DOMAIN, "log", {}, True)
# Logbook entry service call results in firing an event.
# Our service call will unblock when the event listeners have been
# scheduled. This means that they may not have been processed yet.
await hass_.async_block_till_done()
assert len(calls) == 0
async def test_filter_sensor(
hass_: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test numeric sensors are filtered."""
registry = er.async_get(hass_)
# Unregistered sensor without a unit of measurement - should be in logbook
entity_id1 = "sensor.bla"
attributes_1 = None
# Unregistered sensor with a unit of measurement - should be excluded from logbook
entity_id2 = "sensor.blu"
attributes_2 = {ATTR_UNIT_OF_MEASUREMENT: "cats"}
# Registered sensor with state class - should be excluded from logbook
entity_id3 = registry.async_get_or_create(
"sensor",
"test",
"unique_3",
suggested_object_id="bli",
capabilities={"state_class": SensorStateClass.MEASUREMENT},
).entity_id
attributes_3 = None
# Registered sensor without state class or unit - should be in logbook
entity_id4 = registry.async_get_or_create(
"sensor", "test", "unique_4", suggested_object_id="ble"
).entity_id
attributes_4 = None
hass_.states.async_set(entity_id1, None, attributes_1) # Excluded
hass_.states.async_set(entity_id1, 10, attributes_1) # Included
hass_.states.async_set(entity_id2, None, attributes_2) # Excluded
hass_.states.async_set(entity_id2, 10, attributes_2) # Excluded
hass_.states.async_set(entity_id3, None, attributes_3) # Excluded
hass_.states.async_set(entity_id3, 10, attributes_3) # Excluded
hass_.states.async_set(entity_id1, 20, attributes_1) # Included
hass_.states.async_set(entity_id2, 20, attributes_2) # Excluded
hass_.states.async_set(entity_id4, None, attributes_4) # Excluded
hass_.states.async_set(entity_id4, 10, attributes_4) # Included
await async_wait_recording_done(hass_)
client = await hass_client()
entries = await _async_fetch_logbook(client)
assert len(entries) == 3
_assert_entry(entries[0], name="bla", entity_id=entity_id1, state="10")
_assert_entry(entries[1], name="bla", entity_id=entity_id1, state="20")
_assert_entry(entries[2], name="ble", entity_id=entity_id4, state="10")
async def test_home_assistant_start_stop_not_grouped(hass_: HomeAssistant) -> None:
"""Test if HA start and stop events are no longer grouped."""
await async_setup_component(hass_, "homeassistant", {})
await hass_.async_block_till_done()
entries = mock_humanify(
hass_,
(
MockRow(EVENT_HOMEASSISTANT_STOP),
MockRow(EVENT_HOMEASSISTANT_START),
),
)
assert len(entries) == 2
assert_entry(entries[0], name="Home Assistant", message="stopped", domain=ha.DOMAIN)
assert_entry(entries[1], name="Home Assistant", message="started", domain=ha.DOMAIN)
async def test_home_assistant_start(hass_: HomeAssistant) -> None:
"""Test if HA start is not filtered or converted into a restart."""
await async_setup_component(hass_, "homeassistant", {})
await hass_.async_block_till_done()
entity_id = "switch.bla"
pointA = dt_util.utcnow()
entries = mock_humanify(
hass_,
(
MockRow(EVENT_HOMEASSISTANT_START),
create_state_changed_event(pointA, entity_id, 10).row,
),
)
assert len(entries) == 2
assert_entry(entries[0], name="Home Assistant", message="started", domain=ha.DOMAIN)
assert_entry(entries[1], pointA, "bla", entity_id=entity_id)
def test_process_custom_logbook_entries(hass_: HomeAssistant) -> None:
"""Test if custom log book entries get added as an entry."""
name = "Nice name"
message = "has a custom entry"
entity_id = "sun.sun"
entries = mock_humanify(
hass_,
(
MockRow(
logbook.EVENT_LOGBOOK_ENTRY,
{
logbook.ATTR_NAME: name,
logbook.ATTR_MESSAGE: message,
logbook.ATTR_ENTITY_ID: entity_id,
},
),
),
)
assert len(entries) == 1
assert_entry(entries[0], name=name, message=message, entity_id=entity_id)
def assert_entry(
entry, when=None, name=None, message=None, domain=None, entity_id=None
):
"""Assert an entry is what is expected."""
return _assert_entry(entry, when, name, message, domain, entity_id)
def create_state_changed_event(
event_time_fired,
entity_id,
state,
attributes=None,
last_changed=None,
last_reported=None,
last_updated=None,
):
"""Create state changed event."""
old_state = ha.State(
entity_id,
"old",
attributes,
last_changed=last_changed,
last_reported=last_reported,
last_updated=last_updated,
).as_dict()
new_state = ha.State(
entity_id,
state,
attributes,
last_changed=last_changed,
last_reported=last_reported,
last_updated=last_updated,
).as_dict()
return create_state_changed_event_from_old_new(
entity_id, event_time_fired, old_state, new_state
)
def create_state_changed_event_from_old_new(
entity_id, event_time_fired, old_state, new_state
):
"""Create a state changed event from a old and new state."""
row = EventAsRow(
row_id=1,
event_type=PSEUDO_EVENT_STATE_CHANGED,
event_data="{}",
time_fired_ts=dt_util.utc_to_timestamp(event_time_fired),
context_id_bin=None,
context_user_id_bin=None,
context_parent_id_bin=None,
state=new_state and new_state.get("state"),
entity_id=entity_id,
icon=None,
context_only=False,
data=None,
context=None,
)
return LazyEventPartialState(row, {})
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_view(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test the logbook view."""
await async_setup_component(hass, "logbook", {})
await async_recorder_block_till_done(hass)
client = await hass_client()
response = await client.get(f"/api/logbook/{dt_util.utcnow().isoformat()}")
assert response.status == HTTPStatus.OK
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_view_invalid_start_date_time(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test the logbook view with an invalid date time."""
await async_setup_component(hass, "logbook", {})
await async_recorder_block_till_done(hass)
client = await hass_client()
response = await client.get("/api/logbook/INVALID")
assert response.status == HTTPStatus.BAD_REQUEST
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_view_invalid_end_date_time(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test the logbook view."""
await async_setup_component(hass, "logbook", {})
await async_recorder_block_till_done(hass)
client = await hass_client()
response = await client.get(
f"/api/logbook/{dt_util.utcnow().isoformat()}?end_time=INVALID"
)
assert response.status == HTTPStatus.BAD_REQUEST
@pytest.mark.usefixtures("recorder_mock", "set_utc")
async def test_logbook_view_period_entity(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
) -> None:
"""Test the logbook view with period and entity."""
await async_setup_component(hass, "logbook", {})
await async_recorder_block_till_done(hass)
entity_id_test = "switch.test"
hass.states.async_set(entity_id_test, STATE_OFF)
hass.states.async_set(entity_id_test, STATE_ON)
entity_id_second = "switch.second"
hass.states.async_set(entity_id_second, STATE_OFF)
hass.states.async_set(entity_id_second, STATE_ON)
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries without filters
response = await client.get(f"/api/logbook/{start_date.isoformat()}")
assert response.status == HTTPStatus.OK
response_json = await response.json()
assert len(response_json) == 2
assert response_json[0]["entity_id"] == entity_id_test
assert response_json[1]["entity_id"] == entity_id_second
# Test today entries with filter by period
response = await client.get(f"/api/logbook/{start_date.isoformat()}?period=1")
assert response.status == HTTPStatus.OK
response_json = await response.json()
assert len(response_json) == 2
assert response_json[0]["entity_id"] == entity_id_test
assert response_json[1]["entity_id"] == entity_id_second
# Test today entries with filter by entity_id
response = await client.get(
f"/api/logbook/{start_date.isoformat()}?entity=switch.test"
)
assert response.status == HTTPStatus.OK
response_json = await response.json()
assert len(response_json) == 1
assert response_json[0]["entity_id"] == entity_id_test
# Test entries for 3 days with filter by entity_id
response = await client.get(
f"/api/logbook/{start_date.isoformat()}?period=3&entity=switch.test"
)
assert response.status == HTTPStatus.OK
response_json = await response.json()
assert len(response_json) == 1
assert response_json[0]["entity_id"] == entity_id_test
# Tomorrow time 00:00:00
start = (dt_util.utcnow() + timedelta(days=1)).date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test tomorrow entries without filters
response = await client.get(f"/api/logbook/{start_date.isoformat()}")
assert response.status == HTTPStatus.OK
response_json = await response.json()
assert len(response_json) == 0
# Test tomorrow entries with filter by entity_id
response = await client.get(
f"/api/logbook/{start_date.isoformat()}?entity=switch.test"
)
assert response.status == HTTPStatus.OK
response_json = await response.json()
assert len(response_json) == 0
# Test entries from tomorrow to 3 days ago with filter by entity_id
response = await client.get(
f"/api/logbook/{start_date.isoformat()}?period=3&entity=switch.test"
)
assert response.status == HTTPStatus.OK
response_json = await response.json()
assert len(response_json) == 1
assert response_json[0]["entity_id"] == entity_id_test
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_describe_event(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test teaching logbook about a new event."""
def _describe(event):
"""Describe an event."""
return {"name": "Test Name", "message": "tested a message"}
hass.config.components.add("fake_integration")
mock_platform(
hass,
"fake_integration.logbook",
Mock(
async_describe_events=(
lambda hass, async_describe_event: async_describe_event(
"test_domain",
"some_event",
_describe,
)
),
),
)
assert await async_setup_component(hass, "logbook", {})
with freeze_time(dt_util.utcnow() - timedelta(seconds=5)):
hass.bus.async_fire("some_event")
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries with filter by end_time
end_time = start_date + timedelta(hours=24)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}",
params={"end_time": end_time.isoformat()},
)
results = await response.json()
assert len(results) == 1
event = results[0]
assert event["name"] == "Test Name"
assert event["message"] == "tested a message"
assert event["domain"] == "test_domain"
@pytest.mark.usefixtures("recorder_mock")
async def test_exclude_described_event(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test exclusions of events that are described by another integration."""
name = "My Automation Rule"
entity_id = "automation.excluded_rule"
entity_id2 = "automation.included_rule"
entity_id3 = "sensor.excluded_domain"
def _describe(event: Event) -> dict[str, str]:
"""Describe an event."""
return {
"name": "Test Name",
"message": "tested a message",
"entity_id": event.data[ATTR_ENTITY_ID],
}
def async_describe_events(
hass: HomeAssistant,
async_describe_event: Callable[
[str, str, Callable[[Event], dict[str, str]]], None
],
) -> None:
"""Mock to describe events."""
async_describe_event("automation", "some_automation_event", _describe)
async_describe_event("sensor", "some_event", _describe)
hass.config.components.add("fake_integration")
mock_platform(
hass,
"fake_integration.logbook",
Mock(async_describe_events=async_describe_events),
)
assert await async_setup_component(
hass,
logbook.DOMAIN,
{
logbook.DOMAIN: {
CONF_EXCLUDE: {CONF_DOMAINS: ["sensor"], CONF_ENTITIES: [entity_id]}
}
},
)
with freeze_time(dt_util.utcnow() - timedelta(seconds=5)):
hass.bus.async_fire(
"some_automation_event",
{logbook.ATTR_NAME: name, logbook.ATTR_ENTITY_ID: entity_id},
)
hass.bus.async_fire(
"some_automation_event",
{logbook.ATTR_NAME: name, logbook.ATTR_ENTITY_ID: entity_id2},
)
hass.bus.async_fire(
"some_event", {logbook.ATTR_NAME: name, logbook.ATTR_ENTITY_ID: entity_id3}
)
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries with filter by end_time
end_time = start_date + timedelta(hours=24)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}",
params={"end_time": end_time.isoformat()},
)
results = await response.json()
assert len(results) == 1
event = results[0]
assert event["name"] == "Test Name"
assert event["entity_id"] == "automation.included_rule"
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_view_end_time_entity(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test the logbook view with end_time and entity."""
await async_setup_component(hass, "logbook", {})
await async_recorder_block_till_done(hass)
entity_id_test = "switch.test"
hass.states.async_set(entity_id_test, STATE_OFF)
hass.states.async_set(entity_id_test, STATE_ON)
entity_id_second = "switch.second"
hass.states.async_set(entity_id_second, STATE_OFF)
hass.states.async_set(entity_id_second, STATE_ON)
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries with filter by end_time
end_time = start_date + timedelta(hours=24)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}",
params={"end_time": end_time.isoformat()},
)
assert response.status == HTTPStatus.OK
response_json = await response.json()
assert len(response_json) == 2
assert response_json[0]["entity_id"] == entity_id_test
assert response_json[1]["entity_id"] == entity_id_second
# Test entries for 3 days with filter by entity_id
end_time = start + timedelta(hours=72)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}",
params={"end_time": end_time.isoformat(), "entity": "switch.test"},
)
assert response.status == HTTPStatus.OK
response_json = await response.json()
assert len(response_json) == 1
assert response_json[0]["entity_id"] == entity_id_test
# Tomorrow time 00:00:00
start = dt_util.utcnow()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test entries from today to 3 days with filter by entity_id
end_time = start_date + timedelta(hours=72)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}",
params={"end_time": end_time.isoformat(), "entity": "switch.test"},
)
response_json = await response.json()
assert response.status == HTTPStatus.OK
assert len(response_json) == 1
assert response_json[0]["entity_id"] == entity_id_test
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_entity_filter_with_automations(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test the logbook view with end_time and entity with automations and scripts."""
await asyncio.gather(
*[
async_setup_component(hass, comp, {})
for comp in ("homeassistant", "logbook", "automation", "script")
]
)
await async_recorder_block_till_done(hass)
entity_id_test = "alarm_control_panel.area_001"
hass.states.async_set(entity_id_test, STATE_OFF)
hass.states.async_set(entity_id_test, STATE_ON)
entity_id_second = "alarm_control_panel.area_002"
hass.states.async_set(entity_id_second, STATE_OFF)
hass.states.async_set(entity_id_second, STATE_ON)
hass.bus.async_fire(
EVENT_AUTOMATION_TRIGGERED,
{ATTR_NAME: "Mock automation", ATTR_ENTITY_ID: "automation.mock_automation"},
)
hass.bus.async_fire(
EVENT_SCRIPT_STARTED,
{ATTR_NAME: "Mock script", ATTR_ENTITY_ID: "script.mock_script"},
)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries with filter by end_time
end_time = start_date + timedelta(hours=24)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}",
params={"end_time": end_time.isoformat()},
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert json_dict[0]["entity_id"] == entity_id_test
assert json_dict[1]["entity_id"] == entity_id_second
assert json_dict[2]["entity_id"] == "automation.mock_automation"
assert json_dict[3]["entity_id"] == "script.mock_script"
assert json_dict[4]["domain"] == "homeassistant"
# Test entries for 3 days with filter by entity_id
end_time = start + timedelta(hours=72)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}",
params={
"end_time": end_time.isoformat(),
"entity": "alarm_control_panel.area_001",
},
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert len(json_dict) == 1
assert json_dict[0]["entity_id"] == entity_id_test
# Tomorrow time 00:00:00
start = dt_util.utcnow()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test entries from today to 3 days with filter by entity_id
end_time = start_date + timedelta(hours=72)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}",
params={
"end_time": end_time.isoformat(),
"entity": "alarm_control_panel.area_002",
},
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert len(json_dict) == 1
assert json_dict[0]["entity_id"] == entity_id_second
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_entity_no_longer_in_state_machine(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test the logbook view with an entity that hass been removed from the state machine."""
await async_setup_component(hass, "logbook", {})
await async_setup_component(hass, "automation", {})
await async_setup_component(hass, "script", {})
await async_wait_recording_done(hass)
entity_id_test = "alarm_control_panel.area_001"
hass.states.async_set(
entity_id_test, STATE_OFF, {ATTR_FRIENDLY_NAME: "Alarm Control Panel"}
)
hass.states.async_set(
entity_id_test, STATE_ON, {ATTR_FRIENDLY_NAME: "Alarm Control Panel"}
)
await async_wait_recording_done(hass)
hass.states.async_remove(entity_id_test)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries with filter by end_time
end_time = start_date + timedelta(hours=24)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}",
params={"end_time": end_time.isoformat()},
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert json_dict[0]["name"] == "area 001"
@pytest.mark.usefixtures("recorder_mock", "set_utc")
async def test_filter_continuous_sensor_values(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
) -> None:
"""Test remove continuous sensor events from logbook."""
await async_setup_component(hass, "logbook", {})
await async_recorder_block_till_done(hass)
entity_id_test = "switch.test"
hass.states.async_set(entity_id_test, STATE_OFF)
hass.states.async_set(entity_id_test, STATE_ON)
entity_id_second = "sensor.bla"
hass.states.async_set(entity_id_second, STATE_OFF, {"unit_of_measurement": "foo"})
hass.states.async_set(entity_id_second, STATE_ON, {"unit_of_measurement": "foo"})
entity_id_third = "light.bla"
hass.states.async_set(entity_id_third, STATE_OFF, {"unit_of_measurement": "foo"})
hass.states.async_set(entity_id_third, STATE_ON, {"unit_of_measurement": "foo"})
entity_id_proximity = "proximity.bla"
hass.states.async_set(entity_id_proximity, STATE_OFF)
hass.states.async_set(entity_id_proximity, STATE_ON)
entity_id_counter = "counter.bla"
hass.states.async_set(entity_id_counter, STATE_OFF)
hass.states.async_set(entity_id_counter, STATE_ON)
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries without filters
response = await client.get(f"/api/logbook/{start_date.isoformat()}")
assert response.status == HTTPStatus.OK
response_json = await response.json()
assert len(response_json) == 2
assert response_json[0]["entity_id"] == entity_id_test
assert response_json[1]["entity_id"] == entity_id_third
@pytest.mark.usefixtures("recorder_mock", "set_utc")
async def test_exclude_new_entities(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
) -> None:
"""Test if events are excluded on first update."""
await asyncio.gather(
*[
async_setup_component(hass, comp, {})
for comp in ("homeassistant", "logbook")
]
)
await async_recorder_block_till_done(hass)
entity_id = "climate.bla"
entity_id2 = "climate.blu"
hass.states.async_set(entity_id, STATE_OFF)
hass.states.async_set(entity_id2, STATE_ON)
hass.states.async_set(entity_id2, STATE_OFF)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries without filters
response = await client.get(f"/api/logbook/{start_date.isoformat()}")
assert response.status == HTTPStatus.OK
response_json = await response.json()
assert len(response_json) == 2
assert response_json[0]["entity_id"] == entity_id2
assert response_json[1]["domain"] == "homeassistant"
assert response_json[1]["message"] == "started"
@pytest.mark.usefixtures("recorder_mock", "set_utc")
async def test_exclude_removed_entities(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
) -> None:
"""Test if events are excluded on last update."""
await asyncio.gather(
*[
async_setup_component(hass, comp, {})
for comp in ("homeassistant", "logbook")
]
)
await async_recorder_block_till_done(hass)
entity_id = "climate.bla"
entity_id2 = "climate.blu"
hass.states.async_set(entity_id, STATE_ON)
hass.states.async_set(entity_id, STATE_OFF)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.states.async_set(entity_id2, STATE_ON)
hass.states.async_set(entity_id2, STATE_OFF)
hass.states.async_remove(entity_id)
hass.states.async_remove(entity_id2)
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries without filters
response = await client.get(f"/api/logbook/{start_date.isoformat()}")
assert response.status == HTTPStatus.OK
response_json = await response.json()
assert len(response_json) == 3
assert response_json[0]["entity_id"] == entity_id
assert response_json[1]["domain"] == "homeassistant"
assert response_json[1]["message"] == "started"
assert response_json[2]["entity_id"] == entity_id2
@pytest.mark.usefixtures("recorder_mock", "set_utc")
async def test_exclude_attribute_changes(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
) -> None:
"""Test if events of attribute changes are filtered."""
await asyncio.gather(
*[
async_setup_component(hass, comp, {})
for comp in ("homeassistant", "logbook")
]
)
await async_recorder_block_till_done(hass)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.states.async_set("light.kitchen", STATE_OFF)
hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 100})
hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 200})
hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 300})
hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 400})
hass.states.async_set("light.kitchen", STATE_OFF)
await hass.async_block_till_done()
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries without filters
response = await client.get(f"/api/logbook/{start_date.isoformat()}")
assert response.status == HTTPStatus.OK
response_json = await response.json()
assert len(response_json) == 3
assert response_json[0]["domain"] == "homeassistant"
assert response_json[1]["entity_id"] == "light.kitchen"
assert response_json[2]["entity_id"] == "light.kitchen"
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_entity_context_id(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test the logbook view with end_time and entity with automations and scripts."""
await asyncio.gather(
*[
async_setup_component(hass, comp, {})
for comp in ("homeassistant", "logbook", "automation", "script")
]
)
await async_recorder_block_till_done(hass)
context = ha.Context(
id="01GTDGKBCH00GW0X476W5TVAAA",
user_id="b400facee45711eaa9308bfd3d19e474",
)
# An Automation
automation_entity_id_test = "automation.alarm"
hass.bus.async_fire(
EVENT_AUTOMATION_TRIGGERED,
{ATTR_NAME: "Mock automation", ATTR_ENTITY_ID: automation_entity_id_test},
context=context,
)
hass.bus.async_fire(
EVENT_SCRIPT_STARTED,
{ATTR_NAME: "Mock script", ATTR_ENTITY_ID: "script.mock_script"},
context=context,
)
hass.states.async_set(
automation_entity_id_test,
STATE_ON,
{ATTR_FRIENDLY_NAME: "Alarm Automation"},
context=context,
)
entity_id_test = "alarm_control_panel.area_001"
hass.states.async_set(entity_id_test, STATE_OFF, context=context)
await hass.async_block_till_done()
hass.states.async_set(entity_id_test, STATE_ON, context=context)
await hass.async_block_till_done()
entity_id_second = "alarm_control_panel.area_002"
hass.states.async_set(entity_id_second, STATE_OFF, context=context)
await hass.async_block_till_done()
hass.states.async_set(entity_id_second, STATE_ON, context=context)
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
await hass.async_add_executor_job(
logbook.log_entry,
hass,
"mock_name",
"mock_message",
"alarm_control_panel",
"alarm_control_panel.area_003",
context,
)
await hass.async_block_till_done()
await hass.async_add_executor_job(
logbook.log_entry,
hass,
"mock_name",
"mock_message",
"homeassistant",
None,
context,
)
await hass.async_block_till_done()
# A service call
light_turn_off_service_context = ha.Context(
id="01GTDGKBCH00GW0X476W5TVBFC",
user_id="9400facee45711eaa9308bfd3d19e474",
)
hass.states.async_set("light.switch", STATE_ON)
await hass.async_block_till_done()
hass.bus.async_fire(
EVENT_CALL_SERVICE,
{
ATTR_DOMAIN: "light",
ATTR_SERVICE: "turn_off",
ATTR_ENTITY_ID: "light.switch",
},
context=light_turn_off_service_context,
)
await hass.async_block_till_done()
hass.states.async_set(
"light.switch", STATE_OFF, context=light_turn_off_service_context
)
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries with filter by end_time
end_time = start_date + timedelta(hours=24)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}",
params={"end_time": end_time.isoformat()},
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert json_dict[0]["entity_id"] == "automation.alarm"
assert "context_entity_id" not in json_dict[0]
assert json_dict[0]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[1]["entity_id"] == "script.mock_script"
assert json_dict[1]["context_event_type"] == "automation_triggered"
assert json_dict[1]["context_entity_id"] == "automation.alarm"
assert json_dict[1]["context_entity_id_name"] == "Alarm Automation"
assert json_dict[1]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[2]["entity_id"] == entity_id_test
assert json_dict[2]["context_event_type"] == "automation_triggered"
assert json_dict[2]["context_entity_id"] == "automation.alarm"
assert json_dict[2]["context_entity_id_name"] == "Alarm Automation"
assert json_dict[2]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[3]["entity_id"] == entity_id_second
assert json_dict[3]["context_event_type"] == "automation_triggered"
assert json_dict[3]["context_entity_id"] == "automation.alarm"
assert json_dict[3]["context_entity_id_name"] == "Alarm Automation"
assert json_dict[3]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[4]["domain"] == "homeassistant"
assert json_dict[5]["entity_id"] == "alarm_control_panel.area_003"
assert json_dict[5]["context_event_type"] == "automation_triggered"
assert json_dict[5]["context_entity_id"] == "automation.alarm"
assert json_dict[5]["domain"] == "alarm_control_panel"
assert json_dict[5]["context_entity_id_name"] == "Alarm Automation"
assert json_dict[5]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[6]["domain"] == "homeassistant"
assert json_dict[6]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[7]["entity_id"] == "light.switch"
assert json_dict[7]["context_event_type"] == "call_service"
assert json_dict[7]["context_domain"] == "light"
assert json_dict[7]["context_service"] == "turn_off"
assert json_dict[7]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474"
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_context_id_automation_script_started_manually(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test the logbook populates context_ids for scripts and automations started manually."""
await asyncio.gather(
*[
async_setup_component(hass, comp, {})
for comp in ("homeassistant", "logbook", "automation", "script")
]
)
await async_recorder_block_till_done(hass)
# An Automation
automation_entity_id_test = "automation.alarm"
automation_context = ha.Context(
id="01GTDGKBCH00GW0X476W5TVCCC",
user_id="f400facee45711eaa9308bfd3d19e474",
)
hass.bus.async_fire(
EVENT_AUTOMATION_TRIGGERED,
{ATTR_NAME: "Mock automation", ATTR_ENTITY_ID: automation_entity_id_test},
context=automation_context,
)
script_context = ha.Context(
id="01GTDGKBCH00GW0X476W5TVAAA",
user_id="b400facee45711eaa9308bfd3d19e474",
)
hass.bus.async_fire(
EVENT_SCRIPT_STARTED,
{ATTR_NAME: "Mock script", ATTR_ENTITY_ID: "script.mock_script"},
context=script_context,
)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
script_2_context = ha.Context(
id="01GTDGKBCH00GW0X476W5TVEEE",
user_id="b400facee45711eaa9308bfd3d19e474",
)
hass.bus.async_fire(
EVENT_SCRIPT_STARTED,
{ATTR_NAME: "Mock script"},
context=script_2_context,
)
hass.states.async_set("switch.new", STATE_ON, context=script_2_context)
hass.states.async_set("switch.new", STATE_OFF, context=script_2_context)
await hass.async_block_till_done()
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries with filter by end_time
end_time = start_date + timedelta(hours=24)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}",
params={"end_time": end_time.isoformat()},
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert json_dict[0]["entity_id"] == "automation.alarm"
assert "context_entity_id" not in json_dict[0]
assert json_dict[0]["context_user_id"] == "f400facee45711eaa9308bfd3d19e474"
assert json_dict[0]["context_id"] == "01GTDGKBCH00GW0X476W5TVCCC"
assert json_dict[1]["entity_id"] == "script.mock_script"
assert "context_entity_id" not in json_dict[1]
assert json_dict[1]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[1]["context_id"] == "01GTDGKBCH00GW0X476W5TVAAA"
assert json_dict[2]["domain"] == "homeassistant"
assert json_dict[3]["entity_id"] is None
assert json_dict[3]["name"] == "Mock script"
assert "context_entity_id" not in json_dict[1]
assert json_dict[3]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[3]["context_id"] == "01GTDGKBCH00GW0X476W5TVEEE"
assert json_dict[4]["entity_id"] == "switch.new"
assert json_dict[4]["state"] == "off"
assert "context_entity_id" not in json_dict[1]
assert json_dict[4]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[4]["context_event_type"] == "script_started"
assert json_dict[4]["context_domain"] == "script"
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_entity_context_parent_id(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test the logbook view links events via context parent_id."""
await asyncio.gather(
*[
async_setup_component(hass, comp, {})
for comp in ("homeassistant", "logbook", "automation", "script")
]
)
await async_recorder_block_till_done(hass)
context = ha.Context(
id="01GTDGKBCH00GW0X476W5TVAAA",
user_id="b400facee45711eaa9308bfd3d19e474",
)
# An Automation triggering scripts with a new context
automation_entity_id_test = "automation.alarm"
hass.bus.async_fire(
EVENT_AUTOMATION_TRIGGERED,
{ATTR_NAME: "Mock automation", ATTR_ENTITY_ID: automation_entity_id_test},
context=context,
)
child_context = ha.Context(
id="01GTDGKBCH00GW0X476W5TVDDD",
parent_id="01GTDGKBCH00GW0X476W5TVAAA",
user_id="b400facee45711eaa9308bfd3d19e474",
)
hass.bus.async_fire(
EVENT_SCRIPT_STARTED,
{ATTR_NAME: "Mock script", ATTR_ENTITY_ID: "script.mock_script"},
context=child_context,
)
hass.states.async_set(
automation_entity_id_test,
STATE_ON,
{ATTR_FRIENDLY_NAME: "Alarm Automation"},
context=child_context,
)
entity_id_test = "alarm_control_panel.area_001"
hass.states.async_set(entity_id_test, STATE_OFF, context=child_context)
await hass.async_block_till_done()
hass.states.async_set(entity_id_test, STATE_ON, context=child_context)
await hass.async_block_till_done()
entity_id_second = "alarm_control_panel.area_002"
hass.states.async_set(entity_id_second, STATE_OFF, context=child_context)
await hass.async_block_till_done()
hass.states.async_set(entity_id_second, STATE_ON, context=child_context)
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
logbook.async_log_entry(
hass,
"mock_name",
"mock_message",
"alarm_control_panel",
"alarm_control_panel.area_003",
child_context,
)
await hass.async_block_till_done()
logbook.async_log_entry(
hass,
"mock_name",
"mock_message",
"homeassistant",
None,
child_context,
)
await hass.async_block_till_done()
# A state change via service call with the script as the parent
light_turn_off_service_context = ha.Context(
id="01GTDGKBCH00GW0X476W5TVBFC",
parent_id="01GTDGKBCH00GW0X476W5TVDDD",
user_id="9400facee45711eaa9308bfd3d19e474",
)
hass.states.async_set("light.switch", STATE_ON)
await hass.async_block_till_done()
hass.bus.async_fire(
EVENT_CALL_SERVICE,
{
ATTR_DOMAIN: "light",
ATTR_SERVICE: "turn_off",
ATTR_ENTITY_ID: "light.switch",
},
context=light_turn_off_service_context,
)
await hass.async_block_till_done()
hass.states.async_set(
"light.switch", STATE_OFF, context=light_turn_off_service_context
)
await hass.async_block_till_done()
# An event with a parent event, but the parent event isn't available
missing_parent_context = ha.Context(
id="01GTDGKBCH00GW0X476W5TEDDD",
parent_id="01GTDGKBCH00GW0X276W5TEDDD",
user_id="485cacf93ef84d25a99ced3126b921d2",
)
logbook.async_log_entry(
hass,
"mock_name",
"mock_message",
"alarm_control_panel",
"alarm_control_panel.area_009",
missing_parent_context,
)
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries with filter by end_time
end_time = start_date + timedelta(hours=24)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}",
params={"end_time": end_time.isoformat()},
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert json_dict[0]["entity_id"] == "automation.alarm"
assert "context_entity_id" not in json_dict[0]
assert json_dict[0]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
# New context, so this looks to be triggered by the Alarm Automation
assert json_dict[1]["entity_id"] == "script.mock_script"
assert json_dict[1]["context_event_type"] == "automation_triggered"
assert json_dict[1]["context_entity_id"] == "automation.alarm"
assert json_dict[1]["context_entity_id_name"] == "Alarm Automation"
assert json_dict[1]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[2]["entity_id"] == entity_id_test
assert json_dict[2]["context_event_type"] == "script_started"
assert json_dict[2]["context_entity_id"] == "script.mock_script"
assert json_dict[2]["context_entity_id_name"] == "mock script"
assert json_dict[2]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[3]["entity_id"] == entity_id_second
assert json_dict[3]["context_event_type"] == "script_started"
assert json_dict[3]["context_entity_id"] == "script.mock_script"
assert json_dict[3]["context_entity_id_name"] == "mock script"
assert json_dict[3]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[4]["domain"] == "homeassistant"
assert json_dict[5]["entity_id"] == "alarm_control_panel.area_003"
assert json_dict[5]["context_event_type"] == "script_started"
assert json_dict[5]["context_entity_id"] == "script.mock_script"
assert json_dict[5]["domain"] == "alarm_control_panel"
assert json_dict[5]["context_entity_id_name"] == "mock script"
assert json_dict[5]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[6]["domain"] == "homeassistant"
assert json_dict[6]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[7]["entity_id"] == "light.switch"
assert json_dict[7]["context_event_type"] == "call_service"
assert json_dict[7]["context_domain"] == "light"
assert json_dict[7]["context_service"] == "turn_off"
assert json_dict[7]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474"
assert json_dict[8]["entity_id"] == "alarm_control_panel.area_009"
assert json_dict[8]["domain"] == "alarm_control_panel"
assert "context_event_type" not in json_dict[8]
assert "context_entity_id" not in json_dict[8]
assert "context_entity_id_name" not in json_dict[8]
assert json_dict[8]["context_user_id"] == "485cacf93ef84d25a99ced3126b921d2"
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_context_from_template(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test the logbook view with end_time and entity with automations and scripts."""
await asyncio.gather(
*[
async_setup_component(hass, comp, {})
for comp in ("homeassistant", "logbook")
]
)
assert await async_setup_component(
hass,
"switch",
{
"switch": {
"platform": "template",
"switches": {
"test_template_switch": {
"value_template": "{{ states.switch.test_state.state }}",
"turn_on": {
"service": "switch.turn_on",
"entity_id": "switch.test_state",
},
"turn_off": {
"service": "switch.turn_off",
"entity_id": "switch.test_state",
},
}
},
}
},
)
await async_recorder_block_till_done(hass)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
# Entity added (should not be logged)
hass.states.async_set("switch.test_state", STATE_ON)
await hass.async_block_till_done()
# First state change (should be logged)
hass.states.async_set("switch.test_state", STATE_OFF)
await hass.async_block_till_done()
switch_turn_off_context = ha.Context(
id="01GTDGKBCH00GW0X476W5TVBFC",
user_id="9400facee45711eaa9308bfd3d19e474",
)
hass.states.async_set(
"switch.test_state", STATE_ON, context=switch_turn_off_context
)
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries with filter by end_time
end_time = start_date + timedelta(hours=24)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}",
params={"end_time": end_time.isoformat()},
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert json_dict[0]["domain"] == "homeassistant"
assert "context_entity_id" not in json_dict[0]
assert json_dict[1]["entity_id"] == "switch.test_template_switch"
assert json_dict[2]["entity_id"] == "switch.test_state"
assert json_dict[3]["entity_id"] == "switch.test_template_switch"
assert json_dict[3]["context_entity_id"] == "switch.test_state"
assert json_dict[3]["context_entity_id_name"] == "test state"
assert json_dict[4]["entity_id"] == "switch.test_state"
assert json_dict[4]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474"
assert json_dict[5]["entity_id"] == "switch.test_template_switch"
assert json_dict[5]["context_entity_id"] == "switch.test_state"
assert json_dict[5]["context_entity_id_name"] == "test state"
assert json_dict[5]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474"
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test the logbook view with a single entity and ."""
await async_setup_component(hass, "logbook", {})
assert await async_setup_component(
hass,
"switch",
{
"switch": {
"platform": "template",
"switches": {
"test_template_switch": {
"value_template": "{{ states.switch.test_state.state }}",
"turn_on": {
"service": "switch.turn_on",
"entity_id": "switch.test_state",
},
"turn_off": {
"service": "switch.turn_off",
"entity_id": "switch.test_state",
},
}
},
}
},
)
await async_recorder_block_till_done(hass)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
# Entity added (should not be logged)
hass.states.async_set("switch.test_state", STATE_ON)
await hass.async_block_till_done()
# First state change (should be logged)
hass.states.async_set("switch.test_state", STATE_OFF)
await hass.async_block_till_done()
switch_turn_off_context = ha.Context(
id="01GTDGKBCH00GW0X476W5TVBFC",
user_id="9400facee45711eaa9308bfd3d19e474",
)
hass.states.async_set(
"switch.test_state", STATE_ON, context=switch_turn_off_context
)
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries with filter by end_time
end_time = start + timedelta(hours=24)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=switch.test_state"
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert len(json_dict) == 2
assert json_dict[0]["entity_id"] == "switch.test_state"
assert json_dict[1]["entity_id"] == "switch.test_state"
assert json_dict[1]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474"
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_many_entities_multiple_calls(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test the logbook view with a many entities called multiple times."""
await async_setup_component(hass, "logbook", {})
await async_setup_component(hass, "automation", {})
await async_recorder_block_till_done(hass)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
for automation_id in range(5):
hass.bus.async_fire(
EVENT_AUTOMATION_TRIGGERED,
{
ATTR_NAME: f"Mock automation {automation_id}",
ATTR_ENTITY_ID: f"automation.mock_{automation_id}_automation",
},
)
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
end_time = start + timedelta(hours=24)
for automation_id in range(5):
# Test today entries with filter by end_time
response = await client.get(
f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=automation.mock_{automation_id}_automation"
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert len(json_dict) == 1
assert (
json_dict[0]["entity_id"] == f"automation.mock_{automation_id}_automation"
)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=automation.mock_0_automation,automation.mock_1_automation,automation.mock_2_automation"
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert len(json_dict) == 3
assert json_dict[0]["entity_id"] == "automation.mock_0_automation"
assert json_dict[1]["entity_id"] == "automation.mock_1_automation"
assert json_dict[2]["entity_id"] == "automation.mock_2_automation"
response = await client.get(
f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=automation.mock_4_automation,automation.mock_2_automation,automation.mock_0_automation,automation.mock_1_automation"
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert len(json_dict) == 4
assert json_dict[0]["entity_id"] == "automation.mock_0_automation"
assert json_dict[1]["entity_id"] == "automation.mock_1_automation"
assert json_dict[2]["entity_id"] == "automation.mock_2_automation"
assert json_dict[3]["entity_id"] == "automation.mock_4_automation"
response = await client.get(
f"/api/logbook/{end_time.isoformat()}?end_time={end_time}&entity=automation.mock_4_automation,automation.mock_2_automation,automation.mock_0_automation,automation.mock_1_automation"
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert len(json_dict) == 0
@pytest.mark.usefixtures("recorder_mock")
async def test_custom_log_entry_discoverable_via_(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test if a custom log entry is later discoverable via ."""
await async_setup_component(hass, "logbook", {})
await async_recorder_block_till_done(hass)
logbook.async_log_entry(
hass,
"Alarm",
"is triggered",
"switch",
"switch.test_switch",
)
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries with filter by end_time
end_time = start + timedelta(hours=24)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}?end_time={end_time.isoformat()}&entity=switch.test_switch"
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert len(json_dict) == 1
assert json_dict[0]["name"] == "Alarm"
assert json_dict[0]["message"] == "is triggered"
assert json_dict[0]["entity_id"] == "switch.test_switch"
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_multiple_entities(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test the logbook view with a multiple entities."""
await async_setup_component(hass, "logbook", {})
assert await async_setup_component(
hass,
"switch",
{
"switch": {
"platform": "template",
"switches": {
"test_template_switch": {
"value_template": "{{ states.switch.test_state.state }}",
"turn_on": {
"service": "switch.turn_on",
"entity_id": "switch.test_state",
},
"turn_off": {
"service": "switch.turn_off",
"entity_id": "switch.test_state",
},
}
},
}
},
)
await async_recorder_block_till_done(hass)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
# Entity added (should not be logged)
hass.states.async_set("switch.test_state", STATE_ON)
hass.states.async_set("light.test_state", STATE_ON)
hass.states.async_set("binary_sensor.test_state", STATE_ON)
await hass.async_block_till_done()
# First state change (should be logged)
hass.states.async_set("switch.test_state", STATE_OFF)
hass.states.async_set("light.test_state", STATE_OFF)
hass.states.async_set("binary_sensor.test_state", STATE_OFF)
await hass.async_block_till_done()
switch_turn_off_context = ha.Context(
id="01GTDGKBCH00GW0X476W5TVBFC",
user_id="9400facee45711eaa9308bfd3d19e474",
)
hass.states.async_set(
"switch.test_state", STATE_ON, context=switch_turn_off_context
)
hass.states.async_set("light.test_state", STATE_ON, context=switch_turn_off_context)
hass.states.async_set(
"binary_sensor.test_state", STATE_ON, context=switch_turn_off_context
)
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries with filter by end_time
end_time = start + timedelta(hours=24)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=switch.test_state,light.test_state"
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert len(json_dict) == 4
assert json_dict[0]["entity_id"] == "switch.test_state"
assert json_dict[1]["entity_id"] == "light.test_state"
assert json_dict[2]["entity_id"] == "switch.test_state"
assert json_dict[2]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474"
assert json_dict[3]["entity_id"] == "light.test_state"
assert json_dict[3]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474"
# Test today entries with filter by end_time
end_time = start + timedelta(hours=24)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=binary_sensor.test_state,light.test_state"
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert len(json_dict) == 4
assert json_dict[0]["entity_id"] == "light.test_state"
assert json_dict[1]["entity_id"] == "binary_sensor.test_state"
assert json_dict[2]["entity_id"] == "light.test_state"
assert json_dict[2]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474"
assert json_dict[3]["entity_id"] == "binary_sensor.test_state"
assert json_dict[3]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474"
# Test today entries with filter by end_time
end_time = start + timedelta(hours=24)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=light.test_state,binary_sensor.test_state"
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert len(json_dict) == 4
assert json_dict[0]["entity_id"] == "light.test_state"
assert json_dict[1]["entity_id"] == "binary_sensor.test_state"
assert json_dict[2]["entity_id"] == "light.test_state"
assert json_dict[2]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474"
assert json_dict[3]["entity_id"] == "binary_sensor.test_state"
assert json_dict[3]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474"
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_invalid_entity(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test the logbook view with requesting an invalid entity."""
await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries with filter by end_time
end_time = start + timedelta(hours=24)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=invalid"
)
assert response.status == HTTPStatus.INTERNAL_SERVER_ERROR
@pytest.mark.usefixtures("recorder_mock")
async def test_icon_and_state(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test to ensure state and custom icons are returned."""
await asyncio.gather(
*[
async_setup_component(hass, comp, {})
for comp in ("homeassistant", "logbook")
]
)
await async_recorder_block_till_done(hass)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.states.async_set("light.kitchen", STATE_OFF, {"icon": "mdi:chemical-weapon"})
hass.states.async_set(
"light.kitchen", STATE_ON, {"brightness": 100, "icon": "mdi:security"}
)
hass.states.async_set(
"light.kitchen", STATE_ON, {"brightness": 200, "icon": "mdi:security"}
)
hass.states.async_set(
"light.kitchen", STATE_ON, {"brightness": 300, "icon": "mdi:security"}
)
hass.states.async_set(
"light.kitchen", STATE_ON, {"brightness": 400, "icon": "mdi:security"}
)
hass.states.async_set("light.kitchen", STATE_OFF, {"icon": "mdi:chemical-weapon"})
await async_wait_recording_done(hass)
client = await hass_client()
response_json = await _async_fetch_logbook(client)
assert len(response_json) == 3
assert response_json[0]["domain"] == "homeassistant"
assert response_json[1]["entity_id"] == "light.kitchen"
assert response_json[1]["icon"] == "mdi:security"
assert response_json[1]["state"] == STATE_ON
assert response_json[2]["entity_id"] == "light.kitchen"
assert response_json[2]["icon"] == "mdi:chemical-weapon"
assert response_json[2]["state"] == STATE_OFF
@pytest.mark.usefixtures("recorder_mock")
async def test_fire_logbook_entries(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test many logbook entry calls."""
await async_setup_component(hass, "logbook", {})
await async_recorder_block_till_done(hass)
for _ in range(10):
hass.bus.async_fire(
logbook.EVENT_LOGBOOK_ENTRY,
{
logbook.ATTR_NAME: "Alarm",
logbook.ATTR_MESSAGE: "is triggered",
logbook.ATTR_DOMAIN: "switch",
logbook.ATTR_ENTITY_ID: "sensor.xyz",
},
)
hass.bus.async_fire(
logbook.EVENT_LOGBOOK_ENTRY,
{},
)
hass.bus.async_fire(
logbook.EVENT_LOGBOOK_ENTRY,
{
logbook.ATTR_NAME: "Alarm",
logbook.ATTR_MESSAGE: "is triggered",
logbook.ATTR_DOMAIN: "switch",
},
)
await async_wait_recording_done(hass)
client = await hass_client()
response_json = await _async_fetch_logbook(client)
# The empty events should be skipped
assert len(response_json) == 11
@pytest.mark.usefixtures("recorder_mock")
async def test_exclude_events_domain(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test if events are filtered if domain is excluded in config."""
entity_id = "switch.bla"
entity_id2 = "sensor.blu"
await async_setup_component(hass, "homeassistant", {})
config = logbook.CONFIG_SCHEMA(
{
ha.DOMAIN: {},
logbook.DOMAIN: {CONF_EXCLUDE: {CONF_DOMAINS: ["switch", "alexa"]}},
}
)
await async_setup_component(hass, "logbook", config)
await async_recorder_block_till_done(hass)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
hass.states.async_set(entity_id, None)
hass.states.async_set(entity_id, 10)
hass.states.async_set(entity_id2, None)
hass.states.async_set(entity_id2, 20)
await async_wait_recording_done(hass)
client = await hass_client()
entries = await _async_fetch_logbook(client)
assert len(entries) == 2
_assert_entry(
entries[0], name="Home Assistant", message="started", domain=ha.DOMAIN
)
_assert_entry(entries[1], name="blu", entity_id=entity_id2)
@pytest.mark.usefixtures("recorder_mock")
async def test_exclude_events_domain_glob(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test if events are filtered if domain or glob is excluded in config."""
entity_id = "switch.bla"
entity_id2 = "sensor.blu"
entity_id3 = "sensor.excluded"
config = logbook.CONFIG_SCHEMA(
{
ha.DOMAIN: {},
logbook.DOMAIN: {
CONF_EXCLUDE: {
CONF_DOMAINS: ["switch", "alexa"],
CONF_ENTITY_GLOBS: "*.excluded",
}
},
}
)
await asyncio.gather(
async_setup_component(hass, "homeassistant", {}),
async_setup_component(hass, "logbook", config),
)
await async_recorder_block_till_done(hass)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
hass.states.async_set(entity_id, None)
hass.states.async_set(entity_id, 10)
hass.states.async_set(entity_id2, None)
hass.states.async_set(entity_id2, 20)
hass.states.async_set(entity_id3, None)
hass.states.async_set(entity_id3, 30)
await async_wait_recording_done(hass)
client = await hass_client()
entries = await _async_fetch_logbook(client)
assert len(entries) == 2
_assert_entry(
entries[0], name="Home Assistant", message="started", domain=ha.DOMAIN
)
_assert_entry(entries[1], name="blu", entity_id=entity_id2)
@pytest.mark.usefixtures("recorder_mock")
async def test_include_events_entity(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test if events are filtered if entity is included in config."""
entity_id = "sensor.bla"
entity_id2 = "sensor.blu"
config = logbook.CONFIG_SCHEMA(
{
ha.DOMAIN: {},
logbook.DOMAIN: {
CONF_INCLUDE: {
CONF_DOMAINS: ["homeassistant"],
CONF_ENTITIES: [entity_id2],
}
},
}
)
await asyncio.gather(
async_setup_component(hass, "homeassistant", {}),
async_setup_component(hass, "logbook", config),
)
await async_recorder_block_till_done(hass)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
hass.states.async_set(entity_id, None)
hass.states.async_set(entity_id, 10)
hass.states.async_set(entity_id2, None)
hass.states.async_set(entity_id2, 20)
await async_wait_recording_done(hass)
client = await hass_client()
entries = await _async_fetch_logbook(client)
assert len(entries) == 2
_assert_entry(
entries[0], name="Home Assistant", message="started", domain=ha.DOMAIN
)
_assert_entry(entries[1], name="blu", entity_id=entity_id2)
@pytest.mark.usefixtures("recorder_mock")
async def test_exclude_events_entity(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test if events are filtered if entity is excluded in config."""
entity_id = "sensor.bla"
entity_id2 = "sensor.blu"
config = logbook.CONFIG_SCHEMA(
{
ha.DOMAIN: {},
logbook.DOMAIN: {CONF_EXCLUDE: {CONF_ENTITIES: [entity_id]}},
}
)
await asyncio.gather(
async_setup_component(hass, "homeassistant", {}),
async_setup_component(hass, "logbook", config),
)
await async_recorder_block_till_done(hass)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
hass.states.async_set(entity_id, None)
hass.states.async_set(entity_id, 10)
hass.states.async_set(entity_id2, None)
hass.states.async_set(entity_id2, 20)
await async_wait_recording_done(hass)
client = await hass_client()
entries = await _async_fetch_logbook(client)
assert len(entries) == 2
_assert_entry(
entries[0], name="Home Assistant", message="started", domain=ha.DOMAIN
)
_assert_entry(entries[1], name="blu", entity_id=entity_id2)
@pytest.mark.usefixtures("recorder_mock")
async def test_include_events_domain(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test if events are filtered if domain is included in config."""
assert await async_setup_component(hass, "alexa", {})
entity_id = "switch.bla"
entity_id2 = "sensor.blu"
config = logbook.CONFIG_SCHEMA(
{
ha.DOMAIN: {},
logbook.DOMAIN: {
CONF_INCLUDE: {CONF_DOMAINS: ["homeassistant", "sensor", "alexa"]}
},
}
)
await asyncio.gather(
async_setup_component(hass, "homeassistant", {}),
async_setup_component(hass, "logbook", config),
)
await async_recorder_block_till_done(hass)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
hass.bus.async_fire(
EVENT_ALEXA_SMART_HOME,
{"request": {"namespace": "Alexa.Discovery", "name": "Discover"}},
)
hass.states.async_set(entity_id, None)
hass.states.async_set(entity_id, 10)
hass.states.async_set(entity_id2, None)
hass.states.async_set(entity_id2, 20)
await async_wait_recording_done(hass)
client = await hass_client()
entries = await _async_fetch_logbook(client)
assert len(entries) == 3
_assert_entry(
entries[0], name="Home Assistant", message="started", domain=ha.DOMAIN
)
_assert_entry(entries[1], name="Amazon Alexa", domain="alexa")
_assert_entry(entries[2], name="blu", entity_id=entity_id2)
@pytest.mark.usefixtures("recorder_mock")
async def test_include_events_domain_glob(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test if events are filtered if domain or glob is included in config."""
assert await async_setup_component(hass, "alexa", {})
entity_id = "switch.bla"
entity_id2 = "sensor.blu"
entity_id3 = "switch.included"
config = logbook.CONFIG_SCHEMA(
{
ha.DOMAIN: {},
logbook.DOMAIN: {
CONF_INCLUDE: {
CONF_DOMAINS: ["homeassistant", "sensor", "alexa"],
CONF_ENTITY_GLOBS: ["*.included"],
}
},
}
)
await asyncio.gather(
async_setup_component(hass, "homeassistant", {}),
async_setup_component(hass, "logbook", config),
)
await async_recorder_block_till_done(hass)
hass.bus.async_fire(
logbook.EVENT_LOGBOOK_ENTRY,
{
logbook.ATTR_NAME: "Alarm",
logbook.ATTR_MESSAGE: "is triggered",
logbook.ATTR_ENTITY_ID: "switch.any",
},
)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
hass.bus.async_fire(
EVENT_ALEXA_SMART_HOME,
{"request": {"namespace": "Alexa.Discovery", "name": "Discover"}},
)
hass.states.async_set(entity_id, None)
hass.states.async_set(entity_id, 10)
hass.states.async_set(entity_id2, None)
hass.states.async_set(entity_id2, 20)
hass.states.async_set(entity_id3, None)
hass.states.async_set(entity_id3, 30)
await async_wait_recording_done(hass)
client = await hass_client()
entries = await _async_fetch_logbook(client)
assert len(entries) == 4
_assert_entry(
entries[0], name="Home Assistant", message="started", domain=ha.DOMAIN
)
_assert_entry(entries[1], name="Amazon Alexa", domain="alexa")
_assert_entry(entries[2], name="blu", entity_id=entity_id2)
_assert_entry(entries[3], name="included", entity_id=entity_id3)
@pytest.mark.usefixtures("recorder_mock")
async def test_include_exclude_events_no_globs(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test if events are filtered if include and exclude is configured."""
entity_id = "switch.bla"
entity_id2 = "sensor.blu"
entity_id3 = "sensor.bli"
entity_id4 = "sensor.keep"
config = logbook.CONFIG_SCHEMA(
{
ha.DOMAIN: {},
logbook.DOMAIN: {
CONF_INCLUDE: {
CONF_DOMAINS: ["sensor", "homeassistant"],
CONF_ENTITIES: ["switch.bla"],
},
CONF_EXCLUDE: {
CONF_DOMAINS: ["switch"],
CONF_ENTITIES: ["sensor.bli"],
},
},
}
)
await asyncio.gather(
async_setup_component(hass, "homeassistant", {}),
async_setup_component(hass, "logbook", config),
)
await async_recorder_block_till_done(hass)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
hass.states.async_set(entity_id, None)
hass.states.async_set(entity_id, 10)
hass.states.async_set(entity_id2, None)
hass.states.async_set(entity_id2, 10)
hass.states.async_set(entity_id3, None)
hass.states.async_set(entity_id3, 10)
hass.states.async_set(entity_id, 20)
hass.states.async_set(entity_id2, 20)
hass.states.async_set(entity_id4, None)
hass.states.async_set(entity_id4, 10)
await async_wait_recording_done(hass)
client = await hass_client()
entries = await _async_fetch_logbook(client)
assert len(entries) == 6
_assert_entry(
entries[0], name="Home Assistant", message="started", domain=ha.DOMAIN
)
_assert_entry(entries[1], name="bla", entity_id=entity_id, state="10")
_assert_entry(entries[2], name="blu", entity_id=entity_id2, state="10")
_assert_entry(entries[3], name="bla", entity_id=entity_id, state="20")
_assert_entry(entries[4], name="blu", entity_id=entity_id2, state="20")
_assert_entry(entries[5], name="keep", entity_id=entity_id4, state="10")
@pytest.mark.usefixtures("recorder_mock")
async def test_include_exclude_events_with_glob_filters(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test if events are filtered if include and exclude is configured."""
entity_id = "switch.bla"
entity_id2 = "sensor.blu"
entity_id3 = "sensor.bli"
entity_id4 = "light.included"
entity_id5 = "switch.included"
entity_id6 = "sensor.excluded"
config = logbook.CONFIG_SCHEMA(
{
ha.DOMAIN: {},
logbook.DOMAIN: {
CONF_INCLUDE: {
CONF_DOMAINS: ["sensor", "homeassistant"],
CONF_ENTITIES: ["switch.bla"],
CONF_ENTITY_GLOBS: ["*.included"],
},
CONF_EXCLUDE: {
CONF_DOMAINS: ["switch"],
CONF_ENTITY_GLOBS: ["*.excluded"],
CONF_ENTITIES: ["sensor.bli"],
},
},
}
)
await asyncio.gather(
async_setup_component(hass, "homeassistant", {}),
async_setup_component(hass, "logbook", config),
)
await async_recorder_block_till_done(hass)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
hass.states.async_set(entity_id, None)
hass.states.async_set(entity_id, 10)
hass.states.async_set(entity_id2, None)
hass.states.async_set(entity_id2, 10)
hass.states.async_set(entity_id3, None)
hass.states.async_set(entity_id3, 10)
hass.states.async_set(entity_id, 20)
hass.states.async_set(entity_id2, 20)
hass.states.async_set(entity_id4, None)
hass.states.async_set(entity_id4, 30)
hass.states.async_set(entity_id5, None)
hass.states.async_set(entity_id5, 30)
hass.states.async_set(entity_id6, None)
hass.states.async_set(entity_id6, 30)
await async_wait_recording_done(hass)
client = await hass_client()
entries = await _async_fetch_logbook(client)
assert len(entries) == 7
_assert_entry(
entries[0], name="Home Assistant", message="started", domain=ha.DOMAIN
)
_assert_entry(entries[1], name="bla", entity_id=entity_id, state="10")
_assert_entry(entries[2], name="blu", entity_id=entity_id2, state="10")
_assert_entry(entries[3], name="bla", entity_id=entity_id, state="20")
_assert_entry(entries[4], name="blu", entity_id=entity_id2, state="20")
_assert_entry(entries[5], name="included", entity_id=entity_id4, state="30")
_assert_entry(entries[6], name="included", entity_id=entity_id5, state="30")
@pytest.mark.usefixtures("recorder_mock")
async def test_empty_config(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test we can handle an empty entity filter."""
entity_id = "sensor.blu"
config = logbook.CONFIG_SCHEMA(
{
ha.DOMAIN: {},
logbook.DOMAIN: {},
}
)
await asyncio.gather(
async_setup_component(hass, "homeassistant", {}),
async_setup_component(hass, "logbook", config),
)
await async_recorder_block_till_done(hass)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
hass.states.async_set(entity_id, None)
hass.states.async_set(entity_id, 10)
await async_wait_recording_done(hass)
client = await hass_client()
entries = await _async_fetch_logbook(client)
assert len(entries) == 2
_assert_entry(
entries[0], name="Home Assistant", message="started", domain=ha.DOMAIN
)
_assert_entry(entries[1], name="blu", entity_id=entity_id)
@pytest.mark.usefixtures("recorder_mock")
async def test_context_filter(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test we can filter by context."""
assert await async_setup_component(hass, "logbook", {})
await async_recorder_block_till_done(hass)
entity_id = "switch.blu"
context = ha.Context()
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
hass.states.async_set(entity_id, None)
hass.states.async_set(entity_id, "on", context=context)
hass.states.async_set(entity_id, "off")
hass.states.async_set(entity_id, "unknown", context=context)
await async_wait_recording_done(hass)
client = await hass_client()
# Test results
entries = await _async_fetch_logbook(client, {"context_id": context.id})
assert len(entries) == 2
_assert_entry(entries[0], entity_id=entity_id, state="on")
_assert_entry(entries[1], entity_id=entity_id, state="unknown")
# Test we can't combine context filter with entity_id filter
response = await client.get(
"/api/logbook", params={"context_id": context.id, "entity": entity_id}
)
assert response.status == HTTPStatus.BAD_REQUEST
async def _async_fetch_logbook(client, params=None):
if params is None:
params = {}
# Today time 00:00:00
now = dt_util.utcnow()
start = datetime(now.year, now.month, now.day, tzinfo=dt_util.UTC)
start_date = datetime(
start.year, start.month, start.day, tzinfo=dt_util.UTC
) - timedelta(hours=24)
if "end_time" not in params:
params["end_time"] = (start + timedelta(hours=48)).isoformat()
# Test today entries without filters
response = await client.get(f"/api/logbook/{start_date.isoformat()}", params=params)
assert response.status == HTTPStatus.OK
return await response.json()
def _assert_entry(
entry, when=None, name=None, message=None, domain=None, entity_id=None, state=None
):
"""Assert an entry is what is expected."""
if when is not None:
assert when.isoformat() == entry["when"]
if name is not None:
assert name == entry["name"]
if message is not None:
assert message == entry["message"]
if domain is not None:
assert domain == entry["domain"]
if entity_id is not None:
assert entity_id == entry["entity_id"]
if state is not None:
assert state == entry["state"]
@pytest.mark.usefixtures("recorder_mock")
async def test_get_events(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
) -> None:
"""Test logbook get_events."""
now = dt_util.utcnow()
await asyncio.gather(
*[
async_setup_component(hass, comp, {})
for comp in ("homeassistant", "logbook")
]
)
await async_recorder_block_till_done(hass)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.states.async_set("light.kitchen", STATE_OFF)
await hass.async_block_till_done()
hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 100})
await hass.async_block_till_done()
hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 200})
await hass.async_block_till_done()
hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 300})
await hass.async_block_till_done()
hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 400})
await hass.async_block_till_done()
context = ha.Context(
id="01GTDGKBCH00GW0X476W5TVAAA",
user_id="b400facee45711eaa9308bfd3d19e474",
)
hass.states.async_set("light.kitchen", STATE_OFF, context=context)
await hass.async_block_till_done()
await async_wait_recording_done(hass)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "logbook/get_events",
"start_time": now.isoformat(),
"end_time": now.isoformat(),
"entity_ids": ["light.kitchen"],
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"] == []
await client.send_json(
{
"id": 2,
"type": "logbook/get_events",
"start_time": now.isoformat(),
"entity_ids": ["sensor.test"],
}
)
response = await client.receive_json()
assert response["success"]
assert response["id"] == 2
assert response["result"] == []
await client.send_json(
{
"id": 3,
"type": "logbook/get_events",
"start_time": now.isoformat(),
"entity_ids": ["light.kitchen"],
}
)
response = await client.receive_json()
assert response["success"]
assert response["id"] == 3
results = response["result"]
assert results[0]["entity_id"] == "light.kitchen"
assert results[0]["state"] == "on"
assert results[1]["entity_id"] == "light.kitchen"
assert results[1]["state"] == "off"
await client.send_json(
{
"id": 4,
"type": "logbook/get_events",
"start_time": now.isoformat(),
}
)
response = await client.receive_json()
assert response["success"]
assert response["id"] == 4
results = response["result"]
assert len(results) == 3
assert results[0]["message"] == "started"
assert results[1]["entity_id"] == "light.kitchen"
assert results[1]["state"] == "on"
assert isinstance(results[1]["when"], float)
assert results[2]["entity_id"] == "light.kitchen"
assert results[2]["state"] == "off"
assert isinstance(results[2]["when"], float)
await client.send_json(
{
"id": 5,
"type": "logbook/get_events",
"start_time": now.isoformat(),
"context_id": "01GTDGKBCH00GW0X476W5TVAAA",
}
)
response = await client.receive_json()
assert response["success"]
assert response["id"] == 5
results = response["result"]
assert len(results) == 1
assert results[0]["entity_id"] == "light.kitchen"
assert results[0]["state"] == "off"
assert isinstance(results[0]["when"], float)
@pytest.mark.usefixtures("recorder_mock")
async def test_get_events_future_start_time(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
) -> None:
"""Test get_events with a future start time."""
await async_setup_component(hass, "logbook", {})
await async_recorder_block_till_done(hass)
future = dt_util.utcnow() + timedelta(hours=10)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "logbook/get_events",
"start_time": future.isoformat(),
}
)
response = await client.receive_json()
assert response["success"]
assert response["id"] == 1
results = response["result"]
assert isinstance(results, list)
assert len(results) == 0
@pytest.mark.usefixtures("recorder_mock")
async def test_get_events_bad_start_time(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
) -> None:
"""Test get_events bad start time."""
await async_setup_component(hass, "logbook", {})
await async_recorder_block_till_done(hass)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "logbook/get_events",
"start_time": "cats",
}
)
response = await client.receive_json()
assert not response["success"]
assert response["error"]["code"] == "invalid_start_time"
@pytest.mark.usefixtures("recorder_mock")
async def test_get_events_bad_end_time(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
) -> None:
"""Test get_events bad end time."""
now = dt_util.utcnow()
await async_setup_component(hass, "logbook", {})
await async_recorder_block_till_done(hass)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "logbook/get_events",
"start_time": now.isoformat(),
"end_time": "dogs",
}
)
response = await client.receive_json()
assert not response["success"]
assert response["error"]["code"] == "invalid_end_time"
@pytest.mark.usefixtures("recorder_mock")
async def test_get_events_invalid_filters(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
) -> None:
"""Test get_events invalid filters."""
await async_setup_component(hass, "logbook", {})
await async_recorder_block_till_done(hass)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "logbook/get_events",
"entity_ids": [],
}
)
response = await client.receive_json()
assert not response["success"]
assert response["error"]["code"] == "invalid_format"
await client.send_json(
{
"id": 2,
"type": "logbook/get_events",
"device_ids": [],
}
)
response = await client.receive_json()
assert not response["success"]
assert response["error"]["code"] == "invalid_format"
@pytest.mark.usefixtures("recorder_mock")
async def test_get_events_with_device_ids(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test logbook get_events for device ids."""
now = dt_util.utcnow()
await asyncio.gather(
*[
async_setup_component(hass, comp, {})
for comp in ("homeassistant", "logbook")
]
)
entry = MockConfigEntry(domain="test", data={"first": True}, options=None)
entry.add_to_hass(hass)
device = device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
identifiers={("bridgeid", "0123")},
sw_version="sw-version",
name="device name",
manufacturer="manufacturer",
model="model",
suggested_area="Game Room",
)
class MockLogbookPlatform:
"""Mock a logbook platform."""
@ha.callback
def async_describe_events(
hass: HomeAssistant, # noqa: N805
async_describe_event: Callable[
[str, str, Callable[[Event], dict[str, str]]], None
],
) -> None:
"""Describe logbook events."""
@ha.callback
def async_describe_test_event(event: Event) -> dict[str, str]:
"""Describe mock logbook event."""
return {
"name": "device name",
"message": "is on fire",
}
async_describe_event("test", "mock_event", async_describe_test_event)
logbook._process_logbook_platform(hass, "test", MockLogbookPlatform)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.bus.async_fire("mock_event", {"device_id": device.id})
hass.states.async_set("light.kitchen", STATE_OFF)
await hass.async_block_till_done()
hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 100})
await hass.async_block_till_done()
hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 200})
await hass.async_block_till_done()
hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 300})
await hass.async_block_till_done()
hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 400})
await hass.async_block_till_done()
context = ha.Context(
id="01GTDGKBCH00GW0X476W5TVAAA",
user_id="b400facee45711eaa9308bfd3d19e474",
)
hass.states.async_set("light.kitchen", STATE_OFF, context=context)
await hass.async_block_till_done()
await async_wait_recording_done(hass)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "logbook/get_events",
"start_time": now.isoformat(),
"device_ids": [device.id],
}
)
response = await client.receive_json()
assert response["success"]
assert response["id"] == 1
results = response["result"]
assert len(results) == 1
assert results[0]["name"] == "device name"
assert results[0]["message"] == "is on fire"
assert isinstance(results[0]["when"], float)
await client.send_json(
{
"id": 2,
"type": "logbook/get_events",
"start_time": now.isoformat(),
"entity_ids": ["light.kitchen"],
"device_ids": [device.id],
}
)
response = await client.receive_json()
assert response["success"]
assert response["id"] == 2
results = response["result"]
assert results[0]["domain"] == "test"
assert results[0]["message"] == "is on fire"
assert results[0]["name"] == "device name"
assert results[1]["entity_id"] == "light.kitchen"
assert results[1]["state"] == "on"
assert results[2]["entity_id"] == "light.kitchen"
assert results[2]["state"] == "off"
await client.send_json(
{
"id": 3,
"type": "logbook/get_events",
"start_time": now.isoformat(),
}
)
response = await client.receive_json()
assert response["success"]
assert response["id"] == 3
results = response["result"]
assert len(results) == 4
assert results[0]["message"] == "started"
assert results[1]["name"] == "device name"
assert results[1]["message"] == "is on fire"
assert isinstance(results[1]["when"], float)
assert results[2]["entity_id"] == "light.kitchen"
assert results[2]["state"] == "on"
assert isinstance(results[2]["when"], float)
assert results[3]["entity_id"] == "light.kitchen"
assert results[3]["state"] == "off"
assert isinstance(results[3]["when"], float)
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_select_entities_context_id(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test the logbook view with end_time and entity with automations and scripts."""
await asyncio.gather(
*[
async_setup_component(hass, comp, {})
for comp in ("homeassistant", "logbook", "automation", "script")
]
)
await async_recorder_block_till_done(hass)
context = ha.Context(
id="01GTDGKBCH00GW0X476W5TVAAA",
user_id="b400facee45711eaa9308bfd3d19e474",
)
# An Automation
automation_entity_id_test = "automation.alarm"
hass.bus.async_fire(
EVENT_AUTOMATION_TRIGGERED,
{ATTR_NAME: "Mock automation", ATTR_ENTITY_ID: automation_entity_id_test},
context=context,
)
hass.bus.async_fire(
EVENT_SCRIPT_STARTED,
{ATTR_NAME: "Mock script", ATTR_ENTITY_ID: "script.mock_script"},
context=context,
)
hass.states.async_set(
automation_entity_id_test,
STATE_ON,
{ATTR_FRIENDLY_NAME: "Alarm Automation"},
context=context,
)
entity_id_test = "alarm_control_panel.area_001"
hass.states.async_set(entity_id_test, STATE_OFF, context=context)
await hass.async_block_till_done()
hass.states.async_set(entity_id_test, STATE_ON, context=context)
await hass.async_block_till_done()
entity_id_second = "alarm_control_panel.area_002"
hass.states.async_set(entity_id_second, STATE_OFF, context=context)
await hass.async_block_till_done()
hass.states.async_set(entity_id_second, STATE_ON, context=context)
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
entity_id_third = "alarm_control_panel.area_003"
logbook.async_log_entry(
hass,
"mock_name",
"mock_message",
"alarm_control_panel",
entity_id_third,
context,
)
await hass.async_block_till_done()
logbook.async_log_entry(
hass,
"mock_name",
"mock_message",
"homeassistant",
None,
context,
)
await hass.async_block_till_done()
# A service call
light_turn_off_service_context = ha.Context(
id="01GTDGKBCH00GW0X476W5TVBFC",
user_id="9400facee45711eaa9308bfd3d19e474",
)
hass.states.async_set("light.switch", STATE_ON)
await hass.async_block_till_done()
hass.bus.async_fire(
EVENT_CALL_SERVICE,
{
ATTR_DOMAIN: "light",
ATTR_SERVICE: "turn_off",
ATTR_ENTITY_ID: "light.switch",
},
context=light_turn_off_service_context,
)
await hass.async_block_till_done()
hass.states.async_set(
"light.switch", STATE_OFF, context=light_turn_off_service_context
)
await async_wait_recording_done(hass)
client = await hass_client()
# Today time 00:00:00
start = dt_util.utcnow().date()
start_date = datetime(start.year, start.month, start.day, tzinfo=dt_util.UTC)
# Test today entries with filter by end_time
end_time = start + timedelta(hours=24)
response = await client.get(
f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity={entity_id_test},{entity_id_second},{entity_id_third},light.switch"
)
assert response.status == HTTPStatus.OK
json_dict = await response.json()
assert json_dict[0]["entity_id"] == entity_id_test
assert json_dict[0]["context_event_type"] == "automation_triggered"
assert json_dict[0]["context_entity_id"] == "automation.alarm"
assert json_dict[0]["context_entity_id_name"] == "Alarm Automation"
assert json_dict[0]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[1]["entity_id"] == entity_id_second
assert json_dict[1]["context_event_type"] == "automation_triggered"
assert json_dict[1]["context_entity_id"] == "automation.alarm"
assert json_dict[1]["context_entity_id_name"] == "Alarm Automation"
assert json_dict[1]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[2]["entity_id"] == "alarm_control_panel.area_003"
assert json_dict[2]["context_event_type"] == "automation_triggered"
assert json_dict[2]["context_entity_id"] == "automation.alarm"
assert json_dict[2]["domain"] == "alarm_control_panel"
assert json_dict[2]["context_entity_id_name"] == "Alarm Automation"
assert json_dict[2]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert json_dict[3]["entity_id"] == "light.switch"
assert json_dict[3]["context_event_type"] == "call_service"
assert json_dict[3]["context_domain"] == "light"
assert json_dict[3]["context_service"] == "turn_off"
assert json_dict[3]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474"
@pytest.mark.usefixtures("recorder_mock")
async def test_get_events_with_context_state(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
) -> None:
"""Test logbook get_events with a context state."""
now = dt_util.utcnow()
await asyncio.gather(
*[
async_setup_component(hass, comp, {})
for comp in ("homeassistant", "logbook")
]
)
await async_recorder_block_till_done(hass)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.states.async_set("binary_sensor.is_light", STATE_ON)
hass.states.async_set("light.kitchen1", STATE_OFF)
hass.states.async_set("light.kitchen2", STATE_OFF)
context = ha.Context(
id="01GTDGKBCH00GW0X476W5TVAAA",
user_id="b400facee45711eaa9308bfd3d19e474",
)
hass.states.async_set("binary_sensor.is_light", STATE_OFF, context=context)
await hass.async_block_till_done()
hass.states.async_set(
"light.kitchen1", STATE_ON, {"brightness": 100}, context=context
)
await hass.async_block_till_done()
hass.states.async_set(
"light.kitchen2", STATE_ON, {"brightness": 200}, context=context
)
await hass.async_block_till_done()
await async_wait_recording_done(hass)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "logbook/get_events",
"start_time": now.isoformat(),
}
)
response = await client.receive_json()
assert response["success"]
assert response["id"] == 1
results = response["result"]
assert results[1]["entity_id"] == "binary_sensor.is_light"
assert results[1]["state"] == "off"
assert "context_state" not in results[1]
assert results[2]["entity_id"] == "light.kitchen1"
assert results[2]["state"] == "on"
assert results[2]["context_entity_id"] == "binary_sensor.is_light"
assert results[2]["context_state"] == "off"
assert results[2]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert "context_event_type" not in results[2]
assert results[3]["entity_id"] == "light.kitchen2"
assert results[3]["state"] == "on"
assert results[3]["context_entity_id"] == "binary_sensor.is_light"
assert results[3]["context_state"] == "off"
assert results[3]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474"
assert "context_event_type" not in results[3]
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_with_empty_config(hass: HomeAssistant) -> None:
"""Test we handle a empty configuration."""
assert await async_setup_component(
hass,
logbook.DOMAIN,
{
logbook.DOMAIN: {},
recorder.DOMAIN: {},
},
)
await hass.async_block_till_done()
@pytest.mark.usefixtures("recorder_mock")
async def test_logbook_with_non_iterable_entity_filter(hass: HomeAssistant) -> None:
"""Test we handle a non-iterable entity filter."""
assert await async_setup_component(
hass,
logbook.DOMAIN,
{
logbook.DOMAIN: {
CONF_EXCLUDE: {
CONF_ENTITIES: ["light.additional_excluded"],
}
},
recorder.DOMAIN: {
CONF_EXCLUDE: {
CONF_ENTITIES: None,
CONF_DOMAINS: None,
CONF_ENTITY_GLOBS: None,
}
},
},
)
await hass.async_block_till_done()