Clean up Tibber service tests (#127334)

* Tibber: cleanup tests

* Tibber: cleanup tests
This commit is contained in:
functionpointer 2024-10-08 07:56:21 +02:00 committed by GitHub
parent 54c4fb5f56
commit 9a0cb59830
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 130 additions and 126 deletions

View File

@ -5,6 +5,7 @@ from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
import pytest import pytest
from homeassistant.components.recorder import Recorder
from homeassistant.components.tibber.const import DOMAIN from homeassistant.components.tibber.const import DOMAIN
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -26,7 +27,7 @@ def config_entry(hass: HomeAssistant) -> MockConfigEntry:
@pytest.fixture @pytest.fixture
async def mock_tibber_setup( async def mock_tibber_setup(
config_entry: MockConfigEntry, hass: HomeAssistant recorder_mock: Recorder, config_entry: MockConfigEntry, hass: HomeAssistant
) -> AsyncGenerator[MagicMock]: ) -> AsyncGenerator[MagicMock]:
"""Mock tibber entry setup.""" """Mock tibber entry setup."""
unique_user_id = "unique_user_id" unique_user_id = "unique_user_id"

View File

@ -1,6 +1,5 @@
"""Test service for Tibber integration.""" """Test service for Tibber integration."""
import asyncio
import datetime as dt import datetime as dt
from unittest.mock import MagicMock from unittest.mock import MagicMock
@ -8,19 +7,16 @@ from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from homeassistant.components.tibber.const import DOMAIN from homeassistant.components.tibber.const import DOMAIN
from homeassistant.components.tibber.services import PRICE_SERVICE_NAME, __get_prices from homeassistant.components.tibber.services import PRICE_SERVICE_NAME
from homeassistant.core import ServiceCall from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError from homeassistant.exceptions import ServiceValidationError
from homeassistant.util import dt as dt_util
STARTTIME = dt.datetime.fromtimestamp(1615766400).replace( START_TIME = dt.datetime.fromtimestamp(1615766400).replace(tzinfo=dt.UTC)
tzinfo=dt_util.get_default_time_zone()
)
def generate_mock_home_data(): def generate_mock_home_data():
"""Create mock data from the tibber connection.""" """Create mock data from the tibber connection."""
tomorrow = STARTTIME + dt.timedelta(days=1) tomorrow = START_TIME + dt.timedelta(days=1)
mock_homes = [ mock_homes = [
MagicMock( MagicMock(
name="first_home", name="first_home",
@ -31,15 +27,15 @@ def generate_mock_home_data():
"priceInfo": { "priceInfo": {
"today": [ "today": [
{ {
"startsAt": STARTTIME.isoformat(), "startsAt": START_TIME.isoformat(),
"total": 0.46914, "total": 0.36914,
"level": "VERY_EXPENSIVE", "level": "VERY_EXPENSIVE",
}, },
{ {
"startsAt": ( "startsAt": (
STARTTIME + dt.timedelta(hours=1) START_TIME + dt.timedelta(hours=1)
).isoformat(), ).isoformat(),
"total": 0.46914, "total": 0.36914,
"level": "VERY_EXPENSIVE", "level": "VERY_EXPENSIVE",
}, },
], ],
@ -72,15 +68,15 @@ def generate_mock_home_data():
"priceInfo": { "priceInfo": {
"today": [ "today": [
{ {
"startsAt": STARTTIME.isoformat(), "startsAt": START_TIME.isoformat(),
"total": 0.46914, "total": 0.36914,
"level": "VERY_EXPENSIVE", "level": "VERY_EXPENSIVE",
}, },
{ {
"startsAt": ( "startsAt": (
STARTTIME + dt.timedelta(hours=1) START_TIME + dt.timedelta(hours=1)
).isoformat(), ).isoformat(),
"total": 0.46914, "total": 0.36914,
"level": "VERY_EXPENSIVE", "level": "VERY_EXPENSIVE",
}, },
], ],
@ -105,101 +101,67 @@ def generate_mock_home_data():
}, },
), ),
] ]
# 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[0].name = "first_home"
mock_homes[1].name = "second_home" mock_homes[1].name = "second_home"
return mock_homes return mock_homes
def create_mock_tibber_connection(): @pytest.mark.parametrize(
"""Create a mock tibber connection.""" "data",
tibber_connection = MagicMock() [
tibber_connection.get_homes.return_value = generate_mock_home_data() {},
return tibber_connection {"start": START_TIME.isoformat()},
{
"start": START_TIME.isoformat(),
def create_mock_hass(): "end": (START_TIME + dt.timedelta(days=1)).isoformat(),
"""Create a mock hass object.""" },
mock_hass = MagicMock ],
mock_hass.data = {"tibber": create_mock_tibber_connection()} )
return mock_hass
async def test_get_prices( async def test_get_prices(
mock_tibber_setup: MagicMock,
hass: HomeAssistant,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
data,
) -> None: ) -> None:
"""Test __get_prices with mock data.""" """Test get_prices with mock data."""
freezer.move_to(STARTTIME) freezer.move_to(START_TIME)
tomorrow = STARTTIME + dt.timedelta(days=1) mock_tibber_setup.get_homes.return_value = generate_mock_home_data()
call = ServiceCall(
DOMAIN, result = await hass.services.async_call(
PRICE_SERVICE_NAME, DOMAIN, PRICE_SERVICE_NAME, data, blocking=True, return_response=True
{"start": STARTTIME.date().isoformat(), "end": tomorrow.date().isoformat()},
) )
await hass.async_block_till_done()
result = await __get_prices(call, hass=create_mock_hass())
assert result == { assert result == {
"prices": { "prices": {
"first_home": [ "first_home": [
{ {
"start_time": STARTTIME, "start_time": dt.datetime.fromisoformat(START_TIME.isoformat()),
"price": 0.46914, # back and forth conversion to deal with HAFakeDatetime vs real datetime being different types
"price": 0.36914,
"level": "VERY_EXPENSIVE", "level": "VERY_EXPENSIVE",
}, },
{ {
"start_time": STARTTIME + dt.timedelta(hours=1), "start_time": dt.datetime.fromisoformat(
"price": 0.46914, (START_TIME + dt.timedelta(hours=1)).isoformat()
),
"price": 0.36914,
"level": "VERY_EXPENSIVE", "level": "VERY_EXPENSIVE",
}, },
], ],
"second_home": [ "second_home": [
{ {
"start_time": STARTTIME, "start_time": dt.datetime.fromisoformat(START_TIME.isoformat()),
"price": 0.46914, "price": 0.36914,
"level": "VERY_EXPENSIVE", "level": "VERY_EXPENSIVE",
}, },
{ {
"start_time": STARTTIME + dt.timedelta(hours=1), "start_time": dt.datetime.fromisoformat(
"price": 0.46914, (START_TIME + dt.timedelta(hours=1)).isoformat()
"level": "VERY_EXPENSIVE", ),
}, "price": 0.36914,
],
}
}
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", "level": "VERY_EXPENSIVE",
}, },
], ],
@ -208,16 +170,24 @@ async def test_get_prices_no_input(
async def test_get_prices_start_tomorrow( async def test_get_prices_start_tomorrow(
mock_tibber_setup: MagicMock,
hass: HomeAssistant,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
) -> None: ) -> None:
"""Test __get_prices with start date tomorrow.""" """Test get_prices with start date tomorrow."""
freezer.move_to(STARTTIME) freezer.move_to(START_TIME)
tomorrow = STARTTIME + dt.timedelta(days=1) tomorrow = START_TIME + dt.timedelta(days=1)
call = ServiceCall(
DOMAIN, PRICE_SERVICE_NAME, {"start": tomorrow.date().isoformat()}
)
result = await __get_prices(call, hass=create_mock_hass()) 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 == { assert result == {
"prices": { "prices": {
@ -228,7 +198,7 @@ async def test_get_prices_start_tomorrow(
"level": "VERY_EXPENSIVE", "level": "VERY_EXPENSIVE",
}, },
{ {
"start_time": tomorrow + dt.timedelta(hours=1), "start_time": (tomorrow + dt.timedelta(hours=1)),
"price": 0.46914, "price": 0.46914,
"level": "VERY_EXPENSIVE", "level": "VERY_EXPENSIVE",
}, },
@ -240,7 +210,7 @@ async def test_get_prices_start_tomorrow(
"level": "VERY_EXPENSIVE", "level": "VERY_EXPENSIVE",
}, },
{ {
"start_time": tomorrow + dt.timedelta(hours=1), "start_time": (tomorrow + dt.timedelta(hours=1)),
"price": 0.46914, "price": 0.46914,
"level": "VERY_EXPENSIVE", "level": "VERY_EXPENSIVE",
}, },
@ -252,46 +222,55 @@ async def test_get_prices_start_tomorrow(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"start_time", "start_time",
[ [
STARTTIME.isoformat(), START_TIME.isoformat(),
STARTTIME.replace(tzinfo=None).isoformat(), (START_TIME + dt.timedelta(hours=4))
(STARTTIME + dt.timedelta(hours=4))
.replace(tzinfo=dt.timezone(dt.timedelta(hours=4))) .replace(tzinfo=dt.timezone(dt.timedelta(hours=4)))
.isoformat(), .isoformat(),
], ],
) )
async def test_get_prices_with_timezones( async def test_get_prices_with_timezones(
mock_tibber_setup: MagicMock,
hass: HomeAssistant,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
start_time: str, start_time: str,
) -> None: ) -> None:
"""Test __get_prices with timezone and without.""" """Test get_prices with timezone and without."""
freezer.move_to(STARTTIME) freezer.move_to(START_TIME)
call = ServiceCall(DOMAIN, PRICE_SERVICE_NAME, {"start": start_time})
result = await __get_prices(call, hass=create_mock_hass()) 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 == { assert result == {
"prices": { "prices": {
"first_home": [ "first_home": [
{ {
"start_time": STARTTIME, "start_time": START_TIME,
"price": 0.46914, "price": 0.36914,
"level": "VERY_EXPENSIVE", "level": "VERY_EXPENSIVE",
}, },
{ {
"start_time": STARTTIME + dt.timedelta(hours=1), "start_time": START_TIME + dt.timedelta(hours=1),
"price": 0.46914, "price": 0.36914,
"level": "VERY_EXPENSIVE", "level": "VERY_EXPENSIVE",
}, },
], ],
"second_home": [ "second_home": [
{ {
"start_time": STARTTIME, "start_time": START_TIME,
"price": 0.46914, "price": 0.36914,
"level": "VERY_EXPENSIVE", "level": "VERY_EXPENSIVE",
}, },
{ {
"start_time": STARTTIME + dt.timedelta(hours=1), "start_time": START_TIME + dt.timedelta(hours=1),
"price": 0.46914, "price": 0.36914,
"level": "VERY_EXPENSIVE", "level": "VERY_EXPENSIVE",
}, },
], ],
@ -302,29 +281,53 @@ async def test_get_prices_with_timezones(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"start_time", "start_time",
[ [
(STARTTIME + dt.timedelta(hours=4)).isoformat(), (START_TIME + dt.timedelta(hours=2)).isoformat(),
(STARTTIME + dt.timedelta(hours=4)).replace(tzinfo=None).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( async def test_get_prices_with_wrong_timezones(
mock_tibber_setup: MagicMock,
hass: HomeAssistant,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
start_time: str, start_time: str,
) -> None: ) -> None:
"""Test __get_prices with timezone and without, while expecting it to fail.""" """Test get_prices with incorrect time and/or timezone. We expect an empty list."""
freezer.move_to(STARTTIME) freezer.move_to(START_TIME)
call = ServiceCall(DOMAIN, PRICE_SERVICE_NAME, {"start": 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()
result = await __get_prices(call, hass=create_mock_hass())
assert result == {"prices": {"first_home": [], "second_home": []}} assert result == {"prices": {"first_home": [], "second_home": []}}
async def test_get_prices_invalid_input() -> None: async def test_get_prices_invalid_input(
"""Test __get_prices with invalid input.""" mock_tibber_setup: MagicMock,
hass: HomeAssistant,
) -> None:
"""Test get_prices with invalid input."""
call = ServiceCall(DOMAIN, PRICE_SERVICE_NAME, {"start": "test"}) with pytest.raises(ServiceValidationError):
task = asyncio.create_task(__get_prices(call, hass=create_mock_hass())) await hass.services.async_call(
DOMAIN,
with pytest.raises(ServiceValidationError) as excinfo: PRICE_SERVICE_NAME,
await task {"start": "test"},
blocking=True,
assert "Invalid datetime provided." in str(excinfo.value) return_response=True,
)