Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert recorder entity registry tests to use async API #116448

Merged
merged 3 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions tests/components/recorder/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,11 @@ def assert_dict_of_states_equal_without_context_and_last_changed(
)


async def async_record_states(hass: HomeAssistant):
"""Record some test states."""
return await hass.async_add_executor_job(record_states, hass)


def record_states(hass):
"""Record some test states.

Expand Down
186 changes: 84 additions & 102 deletions tests/components/recorder/test_entity_registry.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
"""The tests for sensor recorder platform."""

from collections.abc import Callable
from unittest.mock import patch

import pytest
from sqlalchemy import select
from sqlalchemy.orm import Session

from homeassistant.components import recorder
from homeassistant.components.recorder import history
from homeassistant.components.recorder import Recorder, history
from homeassistant.components.recorder.db_schema import StatesMeta
from homeassistant.components.recorder.util import session_scope
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import setup_component
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util

from .common import (
ForceReturnConnectionToPool,
assert_dict_of_states_equal_without_context_and_last_changed,
async_record_states,
async_wait_recording_done,
record_states,
wait_recording_done,
)

from tests.common import MockEntity, MockEntityPlatform, mock_registry
from tests.common import MockEntity, MockEntityPlatform
from tests.typing import RecorderInstanceGenerator


Expand All @@ -40,50 +38,53 @@ def _count_entity_id_in_states_meta(
)


def test_rename_entity_without_collision(
hass_recorder: Callable[..., HomeAssistant], caplog: pytest.LogCaptureFixture
@pytest.fixture
async def mock_recorder_before_hass(
async_setup_recorder_instance: RecorderInstanceGenerator,
) -> None:
"""Set up recorder."""


@pytest.fixture(autouse=True)
def setup_recorder(recorder_mock: Recorder) -> recorder.Recorder:
"""Set up recorder."""


async def test_rename_entity_without_collision(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test states meta is migrated when entity_id is changed."""
hass = hass_recorder()
setup_component(hass, "sensor", {})

entity_reg = mock_registry(hass)

@callback
def add_entry():
reg_entry = entity_reg.async_get_or_create(
"sensor",
"test",
"unique_0000",
suggested_object_id="test1",
)
assert reg_entry.entity_id == "sensor.test1"
await async_setup_component(hass, "sensor", {})

hass.add_job(add_entry)
hass.block_till_done()
reg_entry = entity_registry.async_get_or_create(
"sensor",
"test",
"unique_0000",
suggested_object_id="test1",
)
assert reg_entry.entity_id == "sensor.test1"
await hass.async_block_till_done()

zero, four, states = record_states(hass)
zero, four, states = await async_record_states(hass)
hist = history.get_significant_states(
hass, zero, four, list(set(states) | {"sensor.test99", "sensor.test1"})
)

assert_dict_of_states_equal_without_context_and_last_changed(states, hist)

@callback
def rename_entry():
entity_reg.async_update_entity("sensor.test1", new_entity_id="sensor.test99")

hass.add_job(rename_entry)
wait_recording_done(hass)
entity_registry.async_update_entity("sensor.test1", new_entity_id="sensor.test99")
await async_wait_recording_done(hass)

hist = history.get_significant_states(
hass, zero, four, list(set(states) | {"sensor.test99", "sensor.test1"})
)
states["sensor.test99"] = states.pop("sensor.test1")
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)

hass.states.set("sensor.test99", "post_migrate")
wait_recording_done(hass)
hass.states.async_set("sensor.test99", "post_migrate")
await async_wait_recording_done(hass)
new_hist = history.get_significant_states(
hass,
zero,
Expand All @@ -101,8 +102,8 @@ def rename_entry():


async def test_rename_entity_on_mocked_platform(
async_setup_recorder_instance: RecorderInstanceGenerator,
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test states meta is migrated when entity_id is changed when using a mocked platform.
Expand All @@ -111,11 +112,10 @@ async def test_rename_entity_on_mocked_platform(
sure that we do not record the entity as removed in the database
when we rename it.
"""
instance = await async_setup_recorder_instance(hass)
entity_reg = er.async_get(hass)
instance = recorder.get_instance(hass)
start = dt_util.utcnow()

reg_entry = entity_reg.async_get_or_create(
reg_entry = entity_registry.async_get_or_create(
"sensor",
"test",
"unique_0000",
Expand All @@ -142,7 +142,7 @@ async def test_rename_entity_on_mocked_platform(
["sensor.test1", "sensor.test99"],
)

entity_reg.async_update_entity("sensor.test1", new_entity_id="sensor.test99")
entity_registry.async_update_entity("sensor.test1", new_entity_id="sensor.test99")
await hass.async_block_till_done()
# We have to call the remove method ourselves since we are mocking the platform
hass.states.async_remove("sensor.test1")
Expand Down Expand Up @@ -196,47 +196,38 @@ def _get_states_meta_counts():
assert "the new entity_id is already in use" not in caplog.text


def test_rename_entity_collision(
hass_recorder: Callable[..., HomeAssistant], caplog: pytest.LogCaptureFixture
async def test_rename_entity_collision(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test states meta is not migrated when there is a collision."""
hass = hass_recorder()
setup_component(hass, "sensor", {})

entity_reg = mock_registry(hass)

@callback
def add_entry():
reg_entry = entity_reg.async_get_or_create(
"sensor",
"test",
"unique_0000",
suggested_object_id="test1",
)
assert reg_entry.entity_id == "sensor.test1"
await async_setup_component(hass, "sensor", {})

hass.add_job(add_entry)
hass.block_till_done()
reg_entry = entity_registry.async_get_or_create(
"sensor",
"test",
"unique_0000",
suggested_object_id="test1",
)
assert reg_entry.entity_id == "sensor.test1"
await hass.async_block_till_done()

zero, four, states = record_states(hass)
zero, four, states = await async_record_states(hass)
hist = history.get_significant_states(
hass, zero, four, list(set(states) | {"sensor.test99", "sensor.test1"})
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
assert len(hist["sensor.test1"]) == 3

hass.states.set("sensor.test99", "collision")
hass.states.remove("sensor.test99")
hass.states.async_set("sensor.test99", "collision")
hass.states.async_remove("sensor.test99")

hass.block_till_done()
await hass.async_block_till_done()

# Rename entity sensor.test1 to sensor.test99
@callback
def rename_entry():
entity_reg.async_update_entity("sensor.test1", new_entity_id="sensor.test99")

hass.add_job(rename_entry)
wait_recording_done(hass)
entity_registry.async_update_entity("sensor.test1", new_entity_id="sensor.test99")
await async_wait_recording_done(hass)

# History is not migrated on collision
hist = history.get_significant_states(
Expand All @@ -248,8 +239,8 @@ def rename_entry():
with session_scope(hass=hass) as session:
assert _count_entity_id_in_states_meta(hass, session, "sensor.test99") == 1

hass.states.set("sensor.test99", "post_migrate")
wait_recording_done(hass)
hass.states.async_set("sensor.test99", "post_migrate")
await async_wait_recording_done(hass)
new_hist = history.get_significant_states(
hass,
zero,
Expand All @@ -270,44 +261,39 @@ def rename_entry():
assert "Blocked attempt to insert duplicated state rows" not in caplog.text


def test_rename_entity_collision_without_states_meta_safeguard(
hass_recorder: Callable[..., HomeAssistant], caplog: pytest.LogCaptureFixture
async def test_rename_entity_collision_without_states_meta_safeguard(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test states meta is not migrated when there is a collision.

This test disables the safeguard in the states_meta_manager
and relies on the filter_unique_constraint_integrity_error safeguard.
"""
hass = hass_recorder()
setup_component(hass, "sensor", {})

entity_reg = mock_registry(hass)

@callback
def add_entry():
reg_entry = entity_reg.async_get_or_create(
"sensor",
"test",
"unique_0000",
suggested_object_id="test1",
)
assert reg_entry.entity_id == "sensor.test1"
await async_setup_component(hass, "sensor", {})

hass.add_job(add_entry)
hass.block_till_done()
reg_entry = entity_registry.async_get_or_create(
"sensor",
"test",
"unique_0000",
suggested_object_id="test1",
)
assert reg_entry.entity_id == "sensor.test1"
await hass.async_block_till_done()

zero, four, states = record_states(hass)
zero, four, states = await async_record_states(hass)
hist = history.get_significant_states(
hass, zero, four, list(set(states) | {"sensor.test99", "sensor.test1"})
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
assert len(hist["sensor.test1"]) == 3

hass.states.set("sensor.test99", "collision")
hass.states.remove("sensor.test99")
hass.states.async_set("sensor.test99", "collision")
hass.states.async_remove("sensor.test99")

hass.block_till_done()
wait_recording_done(hass)
await hass.async_block_till_done()
await async_wait_recording_done(hass)

# Verify history before collision
hist = history.get_significant_states(
Expand All @@ -321,14 +307,10 @@ def add_entry():
# so that we hit the filter_unique_constraint_integrity_error safeguard in the entity_registry
with patch.object(instance.states_meta_manager, "get", return_value=None):
# Rename entity sensor.test1 to sensor.test99
@callback
def rename_entry():
entity_reg.async_update_entity(
"sensor.test1", new_entity_id="sensor.test99"
)

hass.add_job(rename_entry)
wait_recording_done(hass)
entity_registry.async_update_entity(
"sensor.test1", new_entity_id="sensor.test99"
)
await async_wait_recording_done(hass)

# History is not migrated on collision
hist = history.get_significant_states(
Expand All @@ -340,8 +322,8 @@ def rename_entry():
with session_scope(hass=hass) as session:
assert _count_entity_id_in_states_meta(hass, session, "sensor.test99") == 1

hass.states.set("sensor.test99", "post_migrate")
wait_recording_done(hass)
hass.states.async_set("sensor.test99", "post_migrate")
await async_wait_recording_done(hass)

new_hist = history.get_significant_states(
hass,
Expand Down