core/tests/components/recorder/test_models.py

258 lines
8.2 KiB
Python
Raw Normal View History

2016-07-11 07:46:56 +00:00
"""The tests for the Recorder component."""
from datetime import datetime
import unittest
2016-07-11 07:46:56 +00:00
import pytest
import pytz
2016-07-11 07:46:56 +00:00
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from homeassistant.components.recorder.models import (
Base,
Events,
RecorderRuns,
States,
process_timestamp,
process_timestamp_to_utc_isoformat,
)
2016-07-11 07:46:56 +00:00
from homeassistant.const import EVENT_STATE_CHANGED
import homeassistant.core as ha
from homeassistant.exceptions import InvalidEntityFormatError
2016-07-11 07:46:56 +00:00
from homeassistant.util import dt
import homeassistant.util.dt as dt_util
2016-07-11 07:46:56 +00:00
ENGINE = None
SESSION = None
2016-07-11 07:46:56 +00:00
def setUpModule(): # pylint: disable=invalid-name
2016-07-11 07:46:56 +00:00
"""Set up a database to use."""
global ENGINE
global SESSION
2016-07-11 07:46:56 +00:00
ENGINE = create_engine("sqlite://")
Base.metadata.create_all(ENGINE)
session_factory = sessionmaker(bind=ENGINE)
SESSION = scoped_session(session_factory)
2016-07-11 07:46:56 +00:00
def tearDownModule(): # pylint: disable=invalid-name
2016-07-11 07:46:56 +00:00
"""Close database."""
global ENGINE
global SESSION
2016-07-11 07:46:56 +00:00
ENGINE.dispose()
ENGINE = None
SESSION = None
2016-07-11 07:46:56 +00:00
class TestEvents(unittest.TestCase):
"""Test Events model."""
# pylint: disable=no-self-use
2016-07-11 07:46:56 +00:00
def test_from_event(self):
"""Test converting event to db event."""
2019-07-31 19:25:30 +00:00
event = ha.Event("test_event", {"some_data": 15})
2016-07-11 07:46:56 +00:00
assert event == Events.from_event(event).to_native()
class TestStates(unittest.TestCase):
"""Test States model."""
# pylint: disable=no-self-use
2016-07-11 07:46:56 +00:00
def test_from_event(self):
"""Test converting event to db state."""
2019-07-31 19:25:30 +00:00
state = ha.State("sensor.temperature", "18")
event = ha.Event(
EVENT_STATE_CHANGED,
{"entity_id": "sensor.temperature", "old_state": None, "new_state": state},
context=state.context,
)
Optimize database indexes for existing queries (#37036) Cleanup indexes as >50% of the db size was indexes, many of them unused in any current query Logbook search was having to filter event_types without an index: Created ix_events_event_type_time_fired Dropped ix_events_event_type States had a redundant keys on composite index: Dropped ix_states_entity_id Its unused since we have ix_states_entity_id_last_updated De-duplicate storage of context in states as its always stored in events and can be found by joining the state on the event_id. Dropped ix_states_context_id Dropped ix_states_context_parent_id Dropped ix_states_context_user_id After schema v9: STATES............................................ 10186 40.9% EVENTS............................................ 5502 22.1% IX_STATES_ENTITY_ID_LAST_UPDATED.................. 2177 8.7% IX_EVENTS_EVENT_TYPE_TIME_FIRED................... 1910 7.7% IX_EVENTS_CONTEXT_ID.............................. 1592 6.4% IX_EVENTS_TIME_FIRED.............................. 1383 5.6% IX_STATES_LAST_UPDATED............................ 1079 4.3% IX_STATES_EVENT_ID................................ 375 1.5% IX_EVENTS_CONTEXT_PARENT_ID....................... 347 1.4% IX_EVENTS_CONTEXT_USER_ID......................... 346 1.4% IX_RECORDER_RUNS_START_END........................ 1 0.004% RECORDER_RUNS..................................... 1 0.004% SCHEMA_CHANGES.................................... 1 0.004% SQLITE_MASTER..................................... 1 0.004%
2020-06-23 17:57:52 +00:00
# We don't restore context unless we need it by joining the
# events table on the event_id for state_changed events
state.context = ha.Context(id=None)
2016-07-11 07:46:56 +00:00
assert state == States.from_event(event).to_native()
def test_from_event_to_delete_state(self):
"""Test converting deleting state event to db state."""
2019-07-31 19:25:30 +00:00
event = ha.Event(
EVENT_STATE_CHANGED,
{
"entity_id": "sensor.temperature",
"old_state": ha.State("sensor.temperature", "18"),
"new_state": None,
},
)
2016-07-11 07:46:56 +00:00
db_state = States.from_event(event)
2019-07-31 19:25:30 +00:00
assert db_state.entity_id == "sensor.temperature"
assert db_state.domain == "sensor"
assert db_state.state == ""
2016-07-11 07:46:56 +00:00
assert db_state.last_changed == event.time_fired
assert db_state.last_updated == event.time_fired
class TestRecorderRuns(unittest.TestCase):
"""Test recorder run model."""
def setUp(self): # pylint: disable=invalid-name
2016-07-11 07:46:56 +00:00
"""Set up recorder runs."""
self.session = session = SESSION()
2016-07-11 07:46:56 +00:00
session.query(Events).delete()
session.query(States).delete()
session.query(RecorderRuns).delete()
2020-06-08 19:26:40 +00:00
self.addCleanup(self.tear_down_cleanup)
2016-07-11 07:46:56 +00:00
2020-06-08 19:26:40 +00:00
def tear_down_cleanup(self):
2016-07-11 07:46:56 +00:00
"""Clean up."""
self.session.rollback()
def test_entity_ids(self):
"""Test if entity ids helper method works."""
run = RecorderRuns(
start=datetime(2016, 7, 9, 11, 0, 0, tzinfo=dt.UTC),
end=datetime(2016, 7, 9, 23, 0, 0, tzinfo=dt.UTC),
closed_incorrect=False,
created=datetime(2016, 7, 9, 11, 0, 0, tzinfo=dt.UTC),
)
self.session.add(run)
self.session.commit()
before_run = datetime(2016, 7, 9, 8, 0, 0, tzinfo=dt.UTC)
in_run = datetime(2016, 7, 9, 13, 0, 0, tzinfo=dt.UTC)
in_run2 = datetime(2016, 7, 9, 15, 0, 0, tzinfo=dt.UTC)
in_run3 = datetime(2016, 7, 9, 18, 0, 0, tzinfo=dt.UTC)
after_run = datetime(2016, 7, 9, 23, 30, 0, tzinfo=dt.UTC)
assert run.to_native() == run
assert run.entity_ids() == []
2019-07-31 19:25:30 +00:00
self.session.add(
States(
entity_id="sensor.temperature",
state="20",
last_changed=before_run,
last_updated=before_run,
)
)
self.session.add(
States(
entity_id="sensor.sound",
state="10",
last_changed=after_run,
last_updated=after_run,
)
)
self.session.add(
States(
entity_id="sensor.humidity",
state="76",
last_changed=in_run,
last_updated=in_run,
)
)
self.session.add(
States(
entity_id="sensor.lux",
state="5",
last_changed=in_run3,
last_updated=in_run3,
)
)
assert sorted(run.entity_ids()) == ["sensor.humidity", "sensor.lux"]
assert run.entity_ids(in_run2) == ["sensor.humidity"]
def test_states_from_native_invalid_entity_id():
"""Test loading a state from an invalid entity ID."""
state = States()
state.entity_id = "test.invalid__id"
state.attributes = "{}"
with pytest.raises(InvalidEntityFormatError):
state = state.to_native()
state = state.to_native(validate_entity_id=False)
2019-07-31 19:25:30 +00:00
assert state.entity_id == "test.invalid__id"
async def test_process_timestamp():
"""Test processing time stamp to UTC."""
datetime_with_tzinfo = datetime(2016, 7, 9, 11, 0, 0, tzinfo=dt.UTC)
datetime_without_tzinfo = datetime(2016, 7, 9, 11, 0, 0)
est = pytz.timezone("US/Eastern")
datetime_est_timezone = datetime(2016, 7, 9, 11, 0, 0, tzinfo=est)
nst = pytz.timezone("Canada/Newfoundland")
datetime_nst_timezone = datetime(2016, 7, 9, 11, 0, 0, tzinfo=nst)
hst = pytz.timezone("US/Hawaii")
datetime_hst_timezone = datetime(2016, 7, 9, 11, 0, 0, tzinfo=hst)
assert process_timestamp(datetime_with_tzinfo) == datetime(
2016, 7, 9, 11, 0, 0, tzinfo=dt.UTC
)
assert process_timestamp(datetime_without_tzinfo) == datetime(
2016, 7, 9, 11, 0, 0, tzinfo=dt.UTC
)
assert process_timestamp(datetime_est_timezone) == datetime(
2016, 7, 9, 15, 56, tzinfo=dt.UTC
)
assert process_timestamp(datetime_nst_timezone) == datetime(
2016, 7, 9, 14, 31, tzinfo=dt.UTC
)
assert process_timestamp(datetime_hst_timezone) == datetime(
2016, 7, 9, 21, 31, tzinfo=dt.UTC
)
assert process_timestamp(None) is None
async def test_process_timestamp_to_utc_isoformat():
"""Test processing time stamp to UTC isoformat."""
datetime_with_tzinfo = datetime(2016, 7, 9, 11, 0, 0, tzinfo=dt.UTC)
datetime_without_tzinfo = datetime(2016, 7, 9, 11, 0, 0)
est = pytz.timezone("US/Eastern")
datetime_est_timezone = datetime(2016, 7, 9, 11, 0, 0, tzinfo=est)
est = pytz.timezone("US/Eastern")
datetime_est_timezone = datetime(2016, 7, 9, 11, 0, 0, tzinfo=est)
nst = pytz.timezone("Canada/Newfoundland")
datetime_nst_timezone = datetime(2016, 7, 9, 11, 0, 0, tzinfo=nst)
hst = pytz.timezone("US/Hawaii")
datetime_hst_timezone = datetime(2016, 7, 9, 11, 0, 0, tzinfo=hst)
assert (
process_timestamp_to_utc_isoformat(datetime_with_tzinfo)
== "2016-07-09T11:00:00+00:00"
)
assert (
process_timestamp_to_utc_isoformat(datetime_without_tzinfo)
== "2016-07-09T11:00:00+00:00"
)
assert (
process_timestamp_to_utc_isoformat(datetime_est_timezone)
== "2016-07-09T15:56:00+00:00"
)
assert (
process_timestamp_to_utc_isoformat(datetime_nst_timezone)
== "2016-07-09T14:31:00+00:00"
)
assert (
process_timestamp_to_utc_isoformat(datetime_hst_timezone)
== "2016-07-09T21:31:00+00:00"
)
assert process_timestamp_to_utc_isoformat(None) is None
async def test_event_to_db_model():
"""Test we can round trip Event conversion."""
event = ha.Event(
"state_changed", {"some": "attr"}, ha.EventOrigin.local, dt_util.utcnow()
)
native = Events.from_event(event).to_native()
assert native == event
native = Events.from_event(event, event_data="{}").to_native()
event.data = {}
assert native == event