hass-core/tests/components/tibber/test_services.py
functionpointer 5bd2d27488
Fix Tibber get_prices when called with aware datetime (#123289)
* Tibber: Add extra test to expose aware/naive datetime issue

* Tibber: Fix get_prices action not working with aware datetimes

* Tibber: Simplify comparison

* Tibber: Combine timezone tests into single parametrized one

* Tibber: Split test again to prevent if statement
2024-10-02 08:43:31 +02:00

330 lines
11 KiB
Python

"""Test service for Tibber integration."""
import asyncio
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, __get_prices
from homeassistant.core import ServiceCall
from homeassistant.exceptions import ServiceValidationError
from homeassistant.util import dt as dt_util
STARTTIME = dt.datetime.fromtimestamp(1615766400).replace(
tzinfo=dt_util.get_default_time_zone()
)
def generate_mock_home_data():
"""Create mock data from the tibber connection."""
tomorrow = STARTTIME + dt.timedelta(days=1)
mock_homes = [
MagicMock(
name="first_home",
info={
"viewer": {
"home": {
"currentSubscription": {
"priceInfo": {
"today": [
{
"startsAt": STARTTIME.isoformat(),
"total": 0.46914,
"level": "VERY_EXPENSIVE",
},
{
"startsAt": (
STARTTIME + dt.timedelta(hours=1)
).isoformat(),
"total": 0.46914,
"level": "VERY_EXPENSIVE",
},
],
"tomorrow": [
{
"startsAt": tomorrow.isoformat(),
"total": 0.46914,
"level": "VERY_EXPENSIVE",
},
{
"startsAt": (
tomorrow + dt.timedelta(hours=1)
).isoformat(),
"total": 0.46914,
"level": "VERY_EXPENSIVE",
},
],
}
}
}
}
},
),
MagicMock(
name="second_home",
info={
"viewer": {
"home": {
"currentSubscription": {
"priceInfo": {
"today": [
{
"startsAt": STARTTIME.isoformat(),
"total": 0.46914,
"level": "VERY_EXPENSIVE",
},
{
"startsAt": (
STARTTIME + dt.timedelta(hours=1)
).isoformat(),
"total": 0.46914,
"level": "VERY_EXPENSIVE",
},
],
"tomorrow": [
{
"startsAt": tomorrow.isoformat(),
"total": 0.46914,
"level": "VERY_EXPENSIVE",
},
{
"startsAt": (
tomorrow + dt.timedelta(hours=1)
).isoformat(),
"total": 0.46914,
"level": "VERY_EXPENSIVE",
},
],
}
}
}
}
},
),
]
mock_homes[0].name = "first_home"
mock_homes[1].name = "second_home"
return mock_homes
def create_mock_tibber_connection():
"""Create a mock tibber connection."""
tibber_connection = MagicMock()
tibber_connection.get_homes.return_value = generate_mock_home_data()
return tibber_connection
def create_mock_hass():
"""Create a mock hass object."""
mock_hass = MagicMock
mock_hass.data = {"tibber": create_mock_tibber_connection()}
return mock_hass
async def test_get_prices(
freezer: FrozenDateTimeFactory,
) -> None:
"""Test __get_prices with mock data."""
freezer.move_to(STARTTIME)
tomorrow = STARTTIME + dt.timedelta(days=1)
call = ServiceCall(
DOMAIN,
PRICE_SERVICE_NAME,
{"start": STARTTIME.date().isoformat(), "end": tomorrow.date().isoformat()},
)
result = await __get_prices(call, hass=create_mock_hass())
assert result == {
"prices": {
"first_home": [
{
"start_time": STARTTIME,
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
{
"start_time": STARTTIME + dt.timedelta(hours=1),
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
],
"second_home": [
{
"start_time": STARTTIME,
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
{
"start_time": STARTTIME + dt.timedelta(hours=1),
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
],
}
}
async def test_get_prices_no_input(
freezer: FrozenDateTimeFactory,
) -> None:
"""Test __get_prices with no input."""
freezer.move_to(STARTTIME)
call = ServiceCall(DOMAIN, PRICE_SERVICE_NAME, {})
result = await __get_prices(call, hass=create_mock_hass())
assert result == {
"prices": {
"first_home": [
{
"start_time": STARTTIME,
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
{
"start_time": STARTTIME + dt.timedelta(hours=1),
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
],
"second_home": [
{
"start_time": STARTTIME,
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
{
"start_time": STARTTIME + dt.timedelta(hours=1),
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
],
}
}
async def test_get_prices_start_tomorrow(
freezer: FrozenDateTimeFactory,
) -> None:
"""Test __get_prices with start date tomorrow."""
freezer.move_to(STARTTIME)
tomorrow = STARTTIME + dt.timedelta(days=1)
call = ServiceCall(
DOMAIN, PRICE_SERVICE_NAME, {"start": tomorrow.date().isoformat()}
)
result = await __get_prices(call, hass=create_mock_hass())
assert result == {
"prices": {
"first_home": [
{
"start_time": tomorrow,
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
{
"start_time": tomorrow + dt.timedelta(hours=1),
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
],
"second_home": [
{
"start_time": tomorrow,
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
{
"start_time": tomorrow + dt.timedelta(hours=1),
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
],
}
}
@pytest.mark.parametrize(
"start_time",
[
STARTTIME.isoformat(),
STARTTIME.replace(tzinfo=None).isoformat(),
(STARTTIME + dt.timedelta(hours=4))
.replace(tzinfo=dt.timezone(dt.timedelta(hours=4)))
.isoformat(),
],
)
async def test_get_prices_with_timezones(
freezer: FrozenDateTimeFactory,
start_time: str,
) -> None:
"""Test __get_prices with timezone and without."""
freezer.move_to(STARTTIME)
call = ServiceCall(DOMAIN, PRICE_SERVICE_NAME, {"start": start_time})
result = await __get_prices(call, hass=create_mock_hass())
assert result == {
"prices": {
"first_home": [
{
"start_time": STARTTIME,
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
{
"start_time": STARTTIME + dt.timedelta(hours=1),
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
],
"second_home": [
{
"start_time": STARTTIME,
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
{
"start_time": STARTTIME + dt.timedelta(hours=1),
"price": 0.46914,
"level": "VERY_EXPENSIVE",
},
],
}
}
@pytest.mark.parametrize(
"start_time",
[
(STARTTIME + dt.timedelta(hours=4)).isoformat(),
(STARTTIME + dt.timedelta(hours=4)).replace(tzinfo=None).isoformat(),
],
)
async def test_get_prices_with_wrong_timezones(
freezer: FrozenDateTimeFactory,
start_time: str,
) -> None:
"""Test __get_prices with timezone and without, while expecting it to fail."""
freezer.move_to(STARTTIME)
call = ServiceCall(DOMAIN, PRICE_SERVICE_NAME, {"start": start_time})
result = await __get_prices(call, hass=create_mock_hass())
assert result == {"prices": {"first_home": [], "second_home": []}}
async def test_get_prices_invalid_input() -> None:
"""Test __get_prices with invalid input."""
call = ServiceCall(DOMAIN, PRICE_SERVICE_NAME, {"start": "test"})
task = asyncio.create_task(__get_prices(call, hass=create_mock_hass()))
with pytest.raises(ServiceValidationError) as excinfo:
await task
assert "Invalid datetime provided." in str(excinfo.value)