core/tests/components/tibber/test_services.py

277 lines
8.5 KiB
Python

"""Test service for Tibber integration."""
import datetime as dt
from unittest.mock import MagicMock
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant.components.tibber.const import DOMAIN
from homeassistant.components.tibber.services import PRICE_SERVICE_NAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
START_TIME = dt.datetime.fromtimestamp(1615766400).replace(tzinfo=dt.UTC)
def generate_mock_home_data():
"""Create mock data from the tibber connection."""
tomorrow = START_TIME + dt.timedelta(days=1)
mock_homes = [
MagicMock(
name="first_home",
price_total={
START_TIME.isoformat(): 0.36914,
(START_TIME + dt.timedelta(hours=1)).isoformat(): 0.36914,
tomorrow.isoformat(): 0.46914,
(tomorrow + dt.timedelta(hours=1)).isoformat(): 0.46914,
},
price_level={
START_TIME.isoformat(): "VERY_EXPENSIVE",
(START_TIME + dt.timedelta(hours=1)).isoformat(): "VERY_EXPENSIVE",
tomorrow.isoformat(): "VERY_EXPENSIVE",
(tomorrow + dt.timedelta(hours=1)).isoformat(): "VERY_EXPENSIVE",
},
),
MagicMock(
name="second_home",
price_total={
START_TIME.isoformat(): 0.36914,
(START_TIME + dt.timedelta(hours=1)).isoformat(): 0.36914,
tomorrow.isoformat(): 0.46914,
(tomorrow + dt.timedelta(hours=1)).isoformat(): 0.46914,
},
price_level={
START_TIME.isoformat(): "VERY_EXPENSIVE",
(START_TIME + dt.timedelta(hours=1)).isoformat(): "VERY_EXPENSIVE",
tomorrow.isoformat(): "VERY_EXPENSIVE",
(tomorrow + dt.timedelta(hours=1)).isoformat(): "VERY_EXPENSIVE",
},
),
]
# set name again, as the name is special in mock objects
# see documentation: https://docs.python.org/3/library/unittest.mock.html#mock-names-and-the-name-attribute
mock_homes[0].name = "first_home"
mock_homes[1].name = "second_home"
return mock_homes
@pytest.mark.parametrize(
"data",
[
{},
{"start": START_TIME.isoformat()},
{
"start": START_TIME.isoformat(),
"end": (START_TIME + dt.timedelta(days=1)).isoformat(),
},
],
)
async def test_get_prices(
mock_tibber_setup: MagicMock,
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
data,
) -> None:
"""Test get_prices with mock data."""
freezer.move_to(START_TIME)
mock_tibber_setup.get_homes.return_value = generate_mock_home_data()
result = await hass.services.async_call(
DOMAIN, PRICE_SERVICE_NAME, data, blocking=True, return_response=True
)
await hass.async_block_till_done()
assert result == {
"prices": {
"first_home": [
{
"start_time": START_TIME.isoformat(),
"price": 0.36914,
"level": "VERY_EXPENSIVE",
},
{
"start_time": (START_TIME + dt.timedelta(hours=1)).isoformat(),
"price": 0.36914,
"level": "VERY_EXPENSIVE",
},
],
"second_home": [
{
"start_time": START_TIME.isoformat(),
"price": 0.36914,
"level": "VERY_EXPENSIVE",
},
{
"start_time": (START_TIME + dt.timedelta(hours=1)).isoformat(),
"price": 0.36914,
"level": "VERY_EXPENSIVE",
},
],
}
}
async def test_get_prices_start_tomorrow(
mock_tibber_setup: MagicMock,
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test get_prices with start date tomorrow."""
freezer.move_to(START_TIME)
tomorrow = START_TIME + dt.timedelta(days=1)
mock_tibber_setup.get_homes.return_value = generate_mock_home_data()
result = await hass.services.async_call(
DOMAIN,
PRICE_SERVICE_NAME,
{"start": tomorrow.isoformat()},
blocking=True,
return_response=True,
)
await hass.async_block_till_done()
assert result == {
"prices": {
"first_home": [
{
"start_time": tomorrow.isoformat(),
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
{
"start_time": (tomorrow + dt.timedelta(hours=1)).isoformat(),
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
],
"second_home": [
{
"start_time": tomorrow.isoformat(),
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
{
"start_time": (tomorrow + dt.timedelta(hours=1)).isoformat(),
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
],
}
}
@pytest.mark.parametrize(
"start_time",
[
START_TIME.isoformat(),
(START_TIME + dt.timedelta(hours=4))
.replace(tzinfo=dt.timezone(dt.timedelta(hours=4)))
.isoformat(),
],
)
async def test_get_prices_with_timezones(
mock_tibber_setup: MagicMock,
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
start_time: str,
) -> None:
"""Test get_prices with timezone and without."""
freezer.move_to(START_TIME)
mock_tibber_setup.get_homes.return_value = generate_mock_home_data()
result = await hass.services.async_call(
DOMAIN,
PRICE_SERVICE_NAME,
{"start": start_time},
blocking=True,
return_response=True,
)
await hass.async_block_till_done()
assert result == {
"prices": {
"first_home": [
{
"start_time": START_TIME.isoformat(),
"price": 0.36914,
"level": "VERY_EXPENSIVE",
},
{
"start_time": (START_TIME + dt.timedelta(hours=1)).isoformat(),
"price": 0.36914,
"level": "VERY_EXPENSIVE",
},
],
"second_home": [
{
"start_time": START_TIME.isoformat(),
"price": 0.36914,
"level": "VERY_EXPENSIVE",
},
{
"start_time": (START_TIME + dt.timedelta(hours=1)).isoformat(),
"price": 0.36914,
"level": "VERY_EXPENSIVE",
},
],
}
}
@pytest.mark.parametrize(
"start_time",
[
(START_TIME + dt.timedelta(hours=2)).isoformat(),
(START_TIME + dt.timedelta(hours=2))
.astimezone(tz=dt.timezone(dt.timedelta(hours=5)))
.isoformat(),
(START_TIME + dt.timedelta(hours=2))
.astimezone(tz=dt.timezone(dt.timedelta(hours=8)))
.isoformat(),
(START_TIME + dt.timedelta(hours=2))
.astimezone(tz=dt.timezone(dt.timedelta(hours=-8)))
.isoformat(),
],
)
async def test_get_prices_with_wrong_timezones(
mock_tibber_setup: MagicMock,
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
start_time: str,
) -> None:
"""Test get_prices with incorrect time and/or timezone. We expect an empty list."""
freezer.move_to(START_TIME)
tomorrow = START_TIME + dt.timedelta(days=1)
mock_tibber_setup.get_homes.return_value = generate_mock_home_data()
result = await hass.services.async_call(
DOMAIN,
PRICE_SERVICE_NAME,
{"start": start_time, "end": tomorrow.isoformat()},
blocking=True,
return_response=True,
)
await hass.async_block_till_done()
assert result == {"prices": {"first_home": [], "second_home": []}}
async def test_get_prices_invalid_input(
mock_tibber_setup: MagicMock,
hass: HomeAssistant,
) -> None:
"""Test get_prices with invalid input."""
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
DOMAIN,
PRICE_SERVICE_NAME,
{"start": "test"},
blocking=True,
return_response=True,
)