mirror of
https://github.com/home-assistant/core.git
synced 2026-05-16 13:31:01 +01:00
27d8c7b93e
Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: J. Nick Koston <nick@home-assistant.io>
195 lines
6.1 KiB
Python
195 lines
6.1 KiB
Python
"""Tests for the logbook component."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
from homeassistant.components import logbook
|
|
from homeassistant.components.logbook import processor
|
|
from homeassistant.components.logbook.models import EventAsRow, LogbookConfig
|
|
from homeassistant.components.recorder.models import (
|
|
process_timestamp_to_utc_isoformat,
|
|
ulid_to_bytes_or_none,
|
|
uuid_hex_to_bytes_or_none,
|
|
)
|
|
from homeassistant.const import (
|
|
ATTR_DOMAIN,
|
|
ATTR_ENTITY_ID,
|
|
ATTR_FRIENDLY_NAME,
|
|
ATTR_SERVICE,
|
|
EVENT_CALL_SERVICE,
|
|
STATE_OFF,
|
|
STATE_ON,
|
|
)
|
|
from homeassistant.core import Context, HomeAssistant
|
|
from homeassistant.helpers import entity_registry as er
|
|
from homeassistant.helpers.json import JSONEncoder
|
|
from homeassistant.util import dt as dt_util
|
|
|
|
IDX_TO_NAME = dict(enumerate(EventAsRow._fields))
|
|
|
|
|
|
class MockRow:
|
|
"""Minimal row mock."""
|
|
|
|
def __init__(
|
|
self,
|
|
event_type: str,
|
|
data: dict[str, Any] | None = None,
|
|
context: Context | None = None,
|
|
) -> None:
|
|
"""Init the fake row."""
|
|
self.event_type = event_type
|
|
self.event_data = json.dumps(data, cls=JSONEncoder)
|
|
self.data = data
|
|
self.time_fired = dt_util.utcnow()
|
|
self.time_fired_ts = self.time_fired.timestamp()
|
|
self.context_parent_id_bin = (
|
|
ulid_to_bytes_or_none(context.parent_id) if context else None
|
|
)
|
|
self.context_user_id_bin = (
|
|
uuid_hex_to_bytes_or_none(context.user_id) if context else None
|
|
)
|
|
self.context_id_bin = ulid_to_bytes_or_none(context.id) if context else None
|
|
self.state = None
|
|
self.entity_id = None
|
|
self.row_id = None
|
|
self.shared_attrs = None
|
|
self.attributes = None
|
|
self.context_only = False
|
|
|
|
def __getitem__(self, idx: int) -> Any:
|
|
"""Get item."""
|
|
return getattr(self, IDX_TO_NAME[idx])
|
|
|
|
@property
|
|
def time_fired_minute(self):
|
|
"""Minute the event was fired."""
|
|
return self.time_fired.minute
|
|
|
|
@property
|
|
def time_fired_isoformat(self):
|
|
"""Time event was fired in utc isoformat."""
|
|
return process_timestamp_to_utc_isoformat(self.time_fired)
|
|
|
|
|
|
def setup_thermostat_context_test_entities(hass_: HomeAssistant) -> None:
|
|
"""Set up initial states for the thermostat context chain test entities."""
|
|
hass_.states.async_set(
|
|
"climate.living_room",
|
|
"off",
|
|
{ATTR_FRIENDLY_NAME: "Living Room Thermostat"},
|
|
)
|
|
hass_.states.async_set("switch.heater", STATE_OFF)
|
|
|
|
|
|
def simulate_thermostat_context_chain(
|
|
hass_: HomeAssistant,
|
|
user_id: str = "b400facee45711eaa9308bfd3d19e474",
|
|
) -> tuple[Context, Context]:
|
|
"""Simulate the generic_thermostat context chain.
|
|
|
|
Fires events in the realistic order:
|
|
1. EVENT_CALL_SERVICE for set_hvac_mode (parent context)
|
|
2. EVENT_CALL_SERVICE for homeassistant.turn_on (child context)
|
|
3. Climate state changes off → heat (parent context)
|
|
4. Switch state changes off → on (child context)
|
|
|
|
Returns the (parent_context, child_context) tuple.
|
|
"""
|
|
parent_context = Context(
|
|
id="01GTDGKBCH00GW0X476W5TVAAA",
|
|
user_id=user_id,
|
|
)
|
|
child_context = Context(
|
|
id="01GTDGKBCH00GW0X476W5TVDDD",
|
|
parent_id=parent_context.id,
|
|
)
|
|
|
|
hass_.bus.async_fire(
|
|
EVENT_CALL_SERVICE,
|
|
{
|
|
ATTR_DOMAIN: "climate",
|
|
ATTR_SERVICE: "set_hvac_mode",
|
|
"service_data": {ATTR_ENTITY_ID: "climate.living_room"},
|
|
},
|
|
context=parent_context,
|
|
)
|
|
hass_.bus.async_fire(
|
|
EVENT_CALL_SERVICE,
|
|
{
|
|
ATTR_DOMAIN: "homeassistant",
|
|
ATTR_SERVICE: "turn_on",
|
|
"service_data": {ATTR_ENTITY_ID: "switch.heater"},
|
|
},
|
|
context=child_context,
|
|
)
|
|
hass_.states.async_set(
|
|
"climate.living_room",
|
|
"heat",
|
|
{ATTR_FRIENDLY_NAME: "Living Room Thermostat"},
|
|
context=parent_context,
|
|
)
|
|
hass_.states.async_set(
|
|
"switch.heater",
|
|
STATE_ON,
|
|
{ATTR_FRIENDLY_NAME: "Heater"},
|
|
context=child_context,
|
|
)
|
|
return parent_context, child_context
|
|
|
|
|
|
def assert_thermostat_context_chain_events(
|
|
events: list[dict[str, Any]], parent_context: Context
|
|
) -> None:
|
|
"""Assert the logbook events for a thermostat context chain.
|
|
|
|
Verifies that climate and switch state changes have correct
|
|
state, user attribution, and service call context.
|
|
"""
|
|
climate_entries = [e for e in events if e.get("entity_id") == "climate.living_room"]
|
|
assert len(climate_entries) == 1
|
|
assert climate_entries[0]["state"] == "heat"
|
|
assert climate_entries[0]["context_user_id"] == parent_context.user_id
|
|
assert climate_entries[0]["context_event_type"] == EVENT_CALL_SERVICE
|
|
assert climate_entries[0]["context_domain"] == "climate"
|
|
assert climate_entries[0]["context_service"] == "set_hvac_mode"
|
|
|
|
heater_entries = [e for e in events if e.get("entity_id") == "switch.heater"]
|
|
assert len(heater_entries) == 1
|
|
assert heater_entries[0]["state"] == "on"
|
|
assert heater_entries[0]["context_user_id"] == parent_context.user_id
|
|
assert heater_entries[0]["context_event_type"] == EVENT_CALL_SERVICE
|
|
assert heater_entries[0]["context_domain"] == "homeassistant"
|
|
assert heater_entries[0]["context_service"] == "turn_on"
|
|
|
|
|
|
def mock_humanify(hass_, rows):
|
|
"""Wrap humanify with mocked logbook objects."""
|
|
entity_name_cache = processor.EntityNameCache(hass_)
|
|
ent_reg = er.async_get(hass_)
|
|
event_cache = processor.EventCache({})
|
|
context_lookup = {}
|
|
logbook_config = hass_.data.get(logbook.DOMAIN, LogbookConfig({}, None, None))
|
|
external_events = logbook_config.external_events
|
|
logbook_run = processor.LogbookRun(
|
|
context_lookup,
|
|
external_events,
|
|
event_cache,
|
|
entity_name_cache,
|
|
include_entity_name=True,
|
|
timestamp=False,
|
|
)
|
|
context_augmenter = processor.ContextAugmenter(logbook_run)
|
|
return list(
|
|
processor._humanify(
|
|
hass_,
|
|
rows,
|
|
ent_reg,
|
|
logbook_run,
|
|
context_augmenter,
|
|
None,
|
|
),
|
|
)
|