hass-core/tests/components/websocket_api/test_messages.py
J. Nick Koston f9205cd88d
Avoid additional timestamp conversion to set state (#118885)
Avoid addtional timestamp conversion to set state

Since we already have the timestamp, we can pass it on to the State
object and avoid the additional timestamp conversion which can be as
much as 30% of the state write runtime.

Since datetime objects are limited to microsecond precision, we need
to adjust some tests to account for the additional precision that we
will now be able to get in the database
2024-06-05 23:43:34 -04:00

301 lines
8.8 KiB
Python

"""Test Websocket API messages module."""
import pytest
from homeassistant.components.websocket_api.messages import (
_partial_cached_event_message as lru_event_cache,
_state_diff_event,
cached_event_message,
message_to_json_bytes,
)
from homeassistant.const import EVENT_STATE_CHANGED
from homeassistant.core import Context, Event, HomeAssistant, State, callback
from tests.common import async_capture_events
async def test_cached_event_message(hass: HomeAssistant) -> None:
"""Test that we cache event messages."""
events = []
@callback
def _event_listener(event):
events.append(event)
hass.bus.async_listen(EVENT_STATE_CHANGED, _event_listener)
hass.states.async_set("light.window", "on")
hass.states.async_set("light.window", "off")
await hass.async_block_till_done()
assert len(events) == 2
lru_event_cache.cache_clear()
msg0 = cached_event_message(b"2", events[0])
assert msg0 == cached_event_message(b"2", events[0])
msg1 = cached_event_message(b"2", events[1])
assert msg1 == cached_event_message(b"2", events[1])
assert msg0 != msg1
cache_info = lru_event_cache.cache_info()
assert cache_info.hits == 2
assert cache_info.misses == 2
assert cache_info.currsize == 2
cached_event_message(b"2", events[1])
cache_info = lru_event_cache.cache_info()
assert cache_info.hits == 3
assert cache_info.misses == 2
assert cache_info.currsize == 2
async def test_cached_event_message_with_different_idens(hass: HomeAssistant) -> None:
"""Test that we cache event messages when the subscrition idens differ."""
events = []
@callback
def _event_listener(event):
events.append(event)
hass.bus.async_listen(EVENT_STATE_CHANGED, _event_listener)
hass.states.async_set("light.window", "on")
await hass.async_block_till_done()
assert len(events) == 1
lru_event_cache.cache_clear()
msg0 = cached_event_message(b"2", events[0])
msg1 = cached_event_message(b"3", events[0])
msg2 = cached_event_message(b"4", events[0])
assert msg0 != msg1
assert msg0 != msg2
cache_info = lru_event_cache.cache_info()
assert cache_info.hits == 2
assert cache_info.misses == 1
assert cache_info.currsize == 1
async def test_state_diff_event(hass: HomeAssistant) -> None:
"""Test building state_diff_message."""
state_change_events = async_capture_events(hass, EVENT_STATE_CHANGED)
context = Context(user_id="user-id", parent_id="parent-id", id="id")
hass.states.async_set("light.window", "on", context=context)
hass.states.async_set("light.window", "off", context=context)
await hass.async_block_till_done()
last_state_event: Event = state_change_events[-1]
new_state: State = last_state_event.data["new_state"]
message = _state_diff_event(last_state_event)
assert message == {
"c": {
"light.window": {"+": {"lc": new_state.last_changed_timestamp, "s": "off"}}
}
}
hass.states.async_set(
"light.window",
"red",
context=Context(user_id="user-id", parent_id="new-parent-id", id="id"),
)
await hass.async_block_till_done()
last_state_event: Event = state_change_events[-1]
new_state: State = last_state_event.data["new_state"]
message = _state_diff_event(last_state_event)
assert message == {
"c": {
"light.window": {
"+": {
"c": {"parent_id": "new-parent-id"},
"lc": new_state.last_changed_timestamp,
"s": "red",
}
}
}
}
hass.states.async_set(
"light.window",
"green",
context=Context(
user_id="new-user-id", parent_id="another-new-parent-id", id="id"
),
)
await hass.async_block_till_done()
last_state_event: Event = state_change_events[-1]
new_state: State = last_state_event.data["new_state"]
message = _state_diff_event(last_state_event)
assert message == {
"c": {
"light.window": {
"+": {
"c": {
"parent_id": "another-new-parent-id",
"user_id": "new-user-id",
},
"lc": new_state.last_changed_timestamp,
"s": "green",
}
}
}
}
hass.states.async_set(
"light.window",
"blue",
context=Context(
user_id="another-new-user-id", parent_id="another-new-parent-id", id="id"
),
)
await hass.async_block_till_done()
last_state_event: Event = state_change_events[-1]
new_state: State = last_state_event.data["new_state"]
message = _state_diff_event(last_state_event)
assert message == {
"c": {
"light.window": {
"+": {
"c": {"user_id": "another-new-user-id"},
"lc": new_state.last_changed_timestamp,
"s": "blue",
}
}
}
}
hass.states.async_set(
"light.window",
"yellow",
context=Context(
user_id="another-new-user-id",
parent_id="another-new-parent-id",
id="id-new",
),
)
await hass.async_block_till_done()
last_state_event: Event = state_change_events[-1]
new_state: State = last_state_event.data["new_state"]
message = _state_diff_event(last_state_event)
assert message == {
"c": {
"light.window": {
"+": {
"c": "id-new",
"lc": new_state.last_changed_timestamp,
"s": "yellow",
}
}
}
}
new_context = Context()
hass.states.async_set(
"light.window", "purple", {"new": "attr"}, context=new_context
)
await hass.async_block_till_done()
last_state_event: Event = state_change_events[-1]
new_state: State = last_state_event.data["new_state"]
message = _state_diff_event(last_state_event)
assert message == {
"c": {
"light.window": {
"+": {
"a": {"new": "attr"},
"c": {"id": new_context.id, "parent_id": None, "user_id": None},
"lc": new_state.last_changed_timestamp,
"s": "purple",
}
}
}
}
hass.states.async_set("light.window", "green", {}, context=new_context)
await hass.async_block_till_done()
last_state_event: Event = state_change_events[-1]
new_state: State = last_state_event.data["new_state"]
message = _state_diff_event(last_state_event)
assert message == {
"c": {
"light.window": {
"+": {"lc": new_state.last_changed_timestamp, "s": "green"},
"-": {"a": ["new"]},
}
}
}
hass.states.async_set(
"light.window",
"green",
{"list_attr": ["a", "b", "c", "d"], "list_attr_2": ["a", "b"]},
context=new_context,
)
await hass.async_block_till_done()
last_state_event: Event = state_change_events[-1]
new_state: State = last_state_event.data["new_state"]
message = _state_diff_event(last_state_event)
assert message == {
"c": {
"light.window": {
"+": {
"a": {"list_attr": ["a", "b", "c", "d"], "list_attr_2": ["a", "b"]},
"lu": new_state.last_updated_timestamp,
}
}
}
}
hass.states.async_set(
"light.window",
"green",
{"list_attr": ["a", "b", "c", "e"]},
context=new_context,
)
await hass.async_block_till_done()
last_state_event: Event = state_change_events[-1]
new_state: State = last_state_event.data["new_state"]
message = _state_diff_event(last_state_event)
assert message == {
"c": {
"light.window": {
"+": {
"a": {"list_attr": ["a", "b", "c", "e"]},
"lu": new_state.last_updated_timestamp,
},
"-": {"a": ["list_attr_2"]},
}
}
}
async def test_message_to_json_bytes(caplog: pytest.LogCaptureFixture) -> None:
"""Test we can serialize websocket messages."""
json_str = message_to_json_bytes({"id": 1, "message": "xyz"})
assert json_str == b'{"id":1,"message":"xyz"}'
json_str2 = message_to_json_bytes({"id": 1, "message": _Unserializeable()})
assert (
json_str2
== b'{"id":1,"type":"result","success":false,"error":{"code":"unknown_error","message":"Invalid JSON in response"}}'
)
assert "Unable to serialize to JSON" in caplog.text
class _Unserializeable:
"""A class that cannot be serialized."""