From 9a0cb5983025d22477f4c5b94d979d8a5ce6d0ed Mon Sep 17 00:00:00 2001 From: functionpointer Date: Tue, 8 Oct 2024 07:56:21 +0200 Subject: [PATCH] Clean up Tibber service tests (#127334) * Tibber: cleanup tests * Tibber: cleanup tests --- tests/components/tibber/conftest.py | 3 +- tests/components/tibber/test_services.py | 253 ++++++++++++----------- 2 files changed, 130 insertions(+), 126 deletions(-) diff --git a/tests/components/tibber/conftest.py b/tests/components/tibber/conftest.py index 0b48531bde1..441a9d0b888 100644 --- a/tests/components/tibber/conftest.py +++ b/tests/components/tibber/conftest.py @@ -5,6 +5,7 @@ from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch import pytest +from homeassistant.components.recorder import Recorder from homeassistant.components.tibber.const import DOMAIN from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.core import HomeAssistant @@ -26,7 +27,7 @@ def config_entry(hass: HomeAssistant) -> MockConfigEntry: @pytest.fixture async def mock_tibber_setup( - config_entry: MockConfigEntry, hass: HomeAssistant + recorder_mock: Recorder, config_entry: MockConfigEntry, hass: HomeAssistant ) -> AsyncGenerator[MagicMock]: """Mock tibber entry setup.""" unique_user_id = "unique_user_id" diff --git a/tests/components/tibber/test_services.py b/tests/components/tibber/test_services.py index 1df91d719fe..33dba9a0e8f 100644 --- a/tests/components/tibber/test_services.py +++ b/tests/components/tibber/test_services.py @@ -1,6 +1,5 @@ """Test service for Tibber integration.""" -import asyncio import datetime as dt from unittest.mock import MagicMock @@ -8,19 +7,16 @@ 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.components.tibber.services import PRICE_SERVICE_NAME +from homeassistant.core import HomeAssistant 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() -) +START_TIME = dt.datetime.fromtimestamp(1615766400).replace(tzinfo=dt.UTC) def generate_mock_home_data(): """Create mock data from the tibber connection.""" - tomorrow = STARTTIME + dt.timedelta(days=1) + tomorrow = START_TIME + dt.timedelta(days=1) mock_homes = [ MagicMock( name="first_home", @@ -31,15 +27,15 @@ def generate_mock_home_data(): "priceInfo": { "today": [ { - "startsAt": STARTTIME.isoformat(), - "total": 0.46914, + "startsAt": START_TIME.isoformat(), + "total": 0.36914, "level": "VERY_EXPENSIVE", }, { "startsAt": ( - STARTTIME + dt.timedelta(hours=1) + START_TIME + dt.timedelta(hours=1) ).isoformat(), - "total": 0.46914, + "total": 0.36914, "level": "VERY_EXPENSIVE", }, ], @@ -72,15 +68,15 @@ def generate_mock_home_data(): "priceInfo": { "today": [ { - "startsAt": STARTTIME.isoformat(), - "total": 0.46914, + "startsAt": START_TIME.isoformat(), + "total": 0.36914, "level": "VERY_EXPENSIVE", }, { "startsAt": ( - STARTTIME + dt.timedelta(hours=1) + START_TIME + dt.timedelta(hours=1) ).isoformat(), - "total": 0.46914, + "total": 0.36914, "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[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 - - +@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(STARTTIME) - tomorrow = STARTTIME + dt.timedelta(days=1) - call = ServiceCall( - DOMAIN, - PRICE_SERVICE_NAME, - {"start": STARTTIME.date().isoformat(), "end": tomorrow.date().isoformat()}, + """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 ) - - result = await __get_prices(call, hass=create_mock_hass()) + await hass.async_block_till_done() assert result == { "prices": { "first_home": [ { - "start_time": STARTTIME, - "price": 0.46914, + "start_time": dt.datetime.fromisoformat(START_TIME.isoformat()), + # back and forth conversion to deal with HAFakeDatetime vs real datetime being different types + "price": 0.36914, "level": "VERY_EXPENSIVE", }, { - "start_time": STARTTIME + dt.timedelta(hours=1), - "price": 0.46914, + "start_time": dt.datetime.fromisoformat( + (START_TIME + dt.timedelta(hours=1)).isoformat() + ), + "price": 0.36914, "level": "VERY_EXPENSIVE", }, ], "second_home": [ { - "start_time": STARTTIME, - "price": 0.46914, + "start_time": dt.datetime.fromisoformat(START_TIME.isoformat()), + "price": 0.36914, "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, + "start_time": dt.datetime.fromisoformat( + (START_TIME + dt.timedelta(hours=1)).isoformat() + ), + "price": 0.36914, "level": "VERY_EXPENSIVE", }, ], @@ -208,16 +170,24 @@ async def test_get_prices_no_input( 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(STARTTIME) - tomorrow = STARTTIME + dt.timedelta(days=1) - call = ServiceCall( - DOMAIN, PRICE_SERVICE_NAME, {"start": tomorrow.date().isoformat()} - ) + """Test get_prices with start date tomorrow.""" + freezer.move_to(START_TIME) + tomorrow = START_TIME + dt.timedelta(days=1) - 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 == { "prices": { @@ -228,7 +198,7 @@ async def test_get_prices_start_tomorrow( "level": "VERY_EXPENSIVE", }, { - "start_time": tomorrow + dt.timedelta(hours=1), + "start_time": (tomorrow + dt.timedelta(hours=1)), "price": 0.46914, "level": "VERY_EXPENSIVE", }, @@ -240,7 +210,7 @@ async def test_get_prices_start_tomorrow( "level": "VERY_EXPENSIVE", }, { - "start_time": tomorrow + dt.timedelta(hours=1), + "start_time": (tomorrow + dt.timedelta(hours=1)), "price": 0.46914, "level": "VERY_EXPENSIVE", }, @@ -252,46 +222,55 @@ async def test_get_prices_start_tomorrow( @pytest.mark.parametrize( "start_time", [ - STARTTIME.isoformat(), - STARTTIME.replace(tzinfo=None).isoformat(), - (STARTTIME + dt.timedelta(hours=4)) + 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(STARTTIME) - call = ServiceCall(DOMAIN, PRICE_SERVICE_NAME, {"start": start_time}) + """Test get_prices with timezone and without.""" + freezer.move_to(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 == { "prices": { "first_home": [ { - "start_time": STARTTIME, - "price": 0.46914, + "start_time": START_TIME, + "price": 0.36914, "level": "VERY_EXPENSIVE", }, { - "start_time": STARTTIME + dt.timedelta(hours=1), - "price": 0.46914, + "start_time": START_TIME + dt.timedelta(hours=1), + "price": 0.36914, "level": "VERY_EXPENSIVE", }, ], "second_home": [ { - "start_time": STARTTIME, - "price": 0.46914, + "start_time": START_TIME, + "price": 0.36914, "level": "VERY_EXPENSIVE", }, { - "start_time": STARTTIME + dt.timedelta(hours=1), - "price": 0.46914, + "start_time": START_TIME + dt.timedelta(hours=1), + "price": 0.36914, "level": "VERY_EXPENSIVE", }, ], @@ -302,29 +281,53 @@ async def test_get_prices_with_timezones( @pytest.mark.parametrize( "start_time", [ - (STARTTIME + dt.timedelta(hours=4)).isoformat(), - (STARTTIME + dt.timedelta(hours=4)).replace(tzinfo=None).isoformat(), + (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 timezone and without, while expecting it to fail.""" - freezer.move_to(STARTTIME) - call = ServiceCall(DOMAIN, PRICE_SERVICE_NAME, {"start": start_time}) + """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() - 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.""" +async def test_get_prices_invalid_input( + mock_tibber_setup: MagicMock, + hass: HomeAssistant, +) -> 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) + with pytest.raises(ServiceValidationError): + await hass.services.async_call( + DOMAIN, + PRICE_SERVICE_NAME, + {"start": "test"}, + blocking=True, + return_response=True, + )