1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-19 23:10:15 +01:00
Files
core/tests/components/logbook/common.py
T

193 lines
6.1 KiB
Python

"""Tests for the logbook component."""
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,
),
)