diff --git a/.coveragerc b/.coveragerc index fbae5ff5228..8fa48ea3cf7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -471,7 +471,6 @@ omit = homeassistant/components/frontier_silicon/browse_media.py homeassistant/components/frontier_silicon/media_player.py homeassistant/components/futurenow/light.py - homeassistant/components/fyta/__init__.py homeassistant/components/fyta/coordinator.py homeassistant/components/fyta/entity.py homeassistant/components/fyta/sensor.py diff --git a/tests/components/fyta/__init__.py b/tests/components/fyta/__init__.py index cdc2cf63b0d..b2b1c762208 100644 --- a/tests/components/fyta/__init__.py +++ b/tests/components/fyta/__init__.py @@ -1 +1,19 @@ """Tests for the Fyta integration.""" + +from unittest.mock import patch + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def setup_platform( + hass: HomeAssistant, config_entry: MockConfigEntry, platforms: list[Platform] +) -> MockConfigEntry: + """Set up the Fyta platform.""" + config_entry.add_to_hass(hass) + + with patch("homeassistant.components.fyta.PLATFORMS", platforms): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/components/fyta/conftest.py b/tests/components/fyta/conftest.py index 63af6340ade..aad93e38b90 100644 --- a/tests/components/fyta/conftest.py +++ b/tests/components/fyta/conftest.py @@ -1,50 +1,63 @@ -"""Test helpers.""" +"""Test helpers for FYTA.""" from collections.abc import Generator -from datetime import UTC, datetime, timedelta +from datetime import UTC, datetime from unittest.mock import AsyncMock, patch import pytest -from homeassistant.components.fyta.const import CONF_EXPIRATION -from homeassistant.const import CONF_ACCESS_TOKEN +from homeassistant.components.fyta.const import CONF_EXPIRATION, DOMAIN as FYTA_DOMAIN +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_PASSWORD, CONF_USERNAME -from .test_config_flow import ACCESS_TOKEN, EXPIRATION +from .const import ACCESS_TOKEN, EXPIRATION, PASSWORD, USERNAME + +from tests.common import MockConfigEntry @pytest.fixture -def mock_fyta(): - """Build a fixture for the Fyta API that connects successfully and returns one device.""" - - mock_fyta_api = AsyncMock() - with patch( - "homeassistant.components.fyta.config_flow.FytaConnector", - return_value=mock_fyta_api, - ) as mock_fyta_api: - mock_fyta_api.return_value.login.return_value = { +def mock_config_entry() -> MockConfigEntry: + """Mock a config entry.""" + return MockConfigEntry( + domain=FYTA_DOMAIN, + title="fyta_user", + data={ + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, CONF_ACCESS_TOKEN: ACCESS_TOKEN, CONF_EXPIRATION: EXPIRATION, - } - yield mock_fyta_api + }, + minor_version=2, + ) @pytest.fixture -def mock_fyta_init(): +def mock_fyta_connector(): """Build a fixture for the Fyta API that connects successfully and returns one device.""" - mock_fyta_api = AsyncMock() - mock_fyta_api.expiration = datetime.now(tz=UTC) + timedelta(days=1) - mock_fyta_api.login = AsyncMock( + mock_fyta_connector = AsyncMock() + mock_fyta_connector.expiration = datetime.fromisoformat(EXPIRATION).replace( + tzinfo=UTC + ) + mock_fyta_connector.client = AsyncMock(autospec=True) + mock_fyta_connector.login = AsyncMock( return_value={ CONF_ACCESS_TOKEN: ACCESS_TOKEN, - CONF_EXPIRATION: EXPIRATION, + CONF_EXPIRATION: datetime.fromisoformat(EXPIRATION).replace(tzinfo=UTC), } ) - with patch( - "homeassistant.components.fyta.FytaConnector.__new__", - return_value=mock_fyta_api, + with ( + patch( + "homeassistant.components.fyta.FytaConnector", + autospec=True, + return_value=mock_fyta_connector, + ), + patch( + "homeassistant.components.fyta.config_flow.FytaConnector", + autospec=True, + return_value=mock_fyta_connector, + ), ): - yield mock_fyta_api + yield mock_fyta_connector @pytest.fixture diff --git a/tests/components/fyta/const.py b/tests/components/fyta/const.py new file mode 100644 index 00000000000..97143af9f79 --- /dev/null +++ b/tests/components/fyta/const.py @@ -0,0 +1,7 @@ +"""Common methods and const used across tests for FYTA.""" + +USERNAME = "fyta_user" +PASSWORD = "fyta_pass" +ACCESS_TOKEN = "123xyz" +EXPIRATION = "2030-12-31T10:00:00+00:00" +EXPIRATION_OLD = "2020-01-01T00:00:00+00:00" diff --git a/tests/components/fyta/test_config_flow.py b/tests/components/fyta/test_config_flow.py index dedb468a617..df0626d0af0 100644 --- a/tests/components/fyta/test_config_flow.py +++ b/tests/components/fyta/test_config_flow.py @@ -1,6 +1,5 @@ """Test the fyta config flow.""" -from datetime import UTC, datetime from unittest.mock import AsyncMock from fyta_cli.fyta_exceptions import ( @@ -16,16 +15,13 @@ from homeassistant.const import CONF_ACCESS_TOKEN, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType -from tests.common import MockConfigEntry +from .const import ACCESS_TOKEN, EXPIRATION, PASSWORD, USERNAME -USERNAME = "fyta_user" -PASSWORD = "fyta_pass" -ACCESS_TOKEN = "123xyz" -EXPIRATION = datetime.fromisoformat("2024-12-31T10:00:00").replace(tzinfo=UTC) +from tests.common import MockConfigEntry async def test_user_flow( - hass: HomeAssistant, mock_fyta: AsyncMock, mock_setup_entry: AsyncMock + hass: HomeAssistant, mock_fyta_connector: AsyncMock, mock_setup_entry: AsyncMock ) -> None: """Test we get the form.""" @@ -46,7 +42,7 @@ async def test_user_flow( CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD, CONF_ACCESS_TOKEN: ACCESS_TOKEN, - CONF_EXPIRATION: "2024-12-31T10:00:00+00:00", + CONF_EXPIRATION: EXPIRATION, } assert len(mock_setup_entry.mock_calls) == 1 @@ -64,7 +60,7 @@ async def test_form_exceptions( hass: HomeAssistant, exception: Exception, error: dict[str, str], - mock_fyta: AsyncMock, + mock_fyta_connector: AsyncMock, mock_setup_entry: AsyncMock, ) -> None: """Test we can handle Form exceptions.""" @@ -73,7 +69,7 @@ async def test_form_exceptions( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - mock_fyta.return_value.login.side_effect = exception + mock_fyta_connector.login.side_effect = exception # tests with connection error result = await hass.config_entries.flow.async_configure( @@ -85,7 +81,7 @@ async def test_form_exceptions( assert result["step_id"] == "user" assert result["errors"] == error - mock_fyta.return_value.login.side_effect = None + mock_fyta_connector.login.side_effect = None # tests with all information provided result = await hass.config_entries.flow.async_configure( @@ -98,12 +94,14 @@ async def test_form_exceptions( assert result["data"][CONF_USERNAME] == USERNAME assert result["data"][CONF_PASSWORD] == PASSWORD assert result["data"][CONF_ACCESS_TOKEN] == ACCESS_TOKEN - assert result["data"][CONF_EXPIRATION] == "2024-12-31T10:00:00+00:00" + assert result["data"][CONF_EXPIRATION] == EXPIRATION assert len(mock_setup_entry.mock_calls) == 1 -async def test_duplicate_entry(hass: HomeAssistant, mock_fyta: AsyncMock) -> None: +async def test_duplicate_entry( + hass: HomeAssistant, mock_fyta_connector: AsyncMock +) -> None: """Test duplicate setup handling.""" entry = MockConfigEntry( domain=DOMAIN, @@ -143,7 +141,7 @@ async def test_reauth( hass: HomeAssistant, exception: Exception, error: dict[str, str], - mock_fyta: AsyncMock, + mock_fyta_connector: AsyncMock, mock_setup_entry: AsyncMock, ) -> None: """Test reauth-flow works.""" @@ -155,7 +153,7 @@ async def test_reauth( CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD, CONF_ACCESS_TOKEN: ACCESS_TOKEN, - CONF_EXPIRATION: "2024-06-30T10:00:00+00:00", + CONF_EXPIRATION: EXPIRATION, }, ) entry.add_to_hass(hass) @@ -168,7 +166,7 @@ async def test_reauth( assert result["type"] is FlowResultType.FORM assert result["step_id"] == "reauth_confirm" - mock_fyta.return_value.login.side_effect = exception + mock_fyta_connector.login.side_effect = exception # tests with connection error result = await hass.config_entries.flow.async_configure( @@ -181,7 +179,7 @@ async def test_reauth( assert result["step_id"] == "reauth_confirm" assert result["errors"] == error - mock_fyta.return_value.login.side_effect = None + mock_fyta_connector.login.side_effect = None # tests with all information provided result = await hass.config_entries.flow.async_configure( @@ -195,4 +193,4 @@ async def test_reauth( assert entry.data[CONF_USERNAME] == "other_username" assert entry.data[CONF_PASSWORD] == "other_password" assert entry.data[CONF_ACCESS_TOKEN] == ACCESS_TOKEN - assert entry.data[CONF_EXPIRATION] == "2024-12-31T10:00:00+00:00" + assert entry.data[CONF_EXPIRATION] == EXPIRATION diff --git a/tests/components/fyta/test_init.py b/tests/components/fyta/test_init.py index 844a818df85..0abe877a4e2 100644 --- a/tests/components/fyta/test_init.py +++ b/tests/components/fyta/test_init.py @@ -1,23 +1,96 @@ """Test the initialization.""" +from datetime import UTC, datetime from unittest.mock import AsyncMock -from homeassistant.components.fyta.const import CONF_EXPIRATION, DOMAIN -from homeassistant.const import CONF_ACCESS_TOKEN, CONF_PASSWORD, CONF_USERNAME +from fyta_cli.fyta_exceptions import ( + FytaAuthentificationError, + FytaConnectionError, + FytaPasswordError, +) +import pytest + +from homeassistant.components.fyta.const import CONF_EXPIRATION, DOMAIN as FYTA_DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import ( + CONF_ACCESS_TOKEN, + CONF_PASSWORD, + CONF_USERNAME, + Platform, +) from homeassistant.core import HomeAssistant -from .test_config_flow import ACCESS_TOKEN, PASSWORD, USERNAME +from . import setup_platform +from .const import ACCESS_TOKEN, EXPIRATION, EXPIRATION_OLD, PASSWORD, USERNAME from tests.common import MockConfigEntry +async def test_load_unload( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_fyta_connector: AsyncMock, +) -> None: + """Test load and unload.""" + + await setup_platform(hass, mock_config_entry, [Platform.SENSOR]) + assert mock_config_entry.state is ConfigEntryState.LOADED + + assert await hass.config_entries.async_unload(mock_config_entry.entry_id) + await hass.async_block_till_done() + assert mock_config_entry.state is ConfigEntryState.NOT_LOADED + + +@pytest.mark.parametrize( + "exception", + [ + FytaAuthentificationError, + FytaPasswordError, + ], +) +async def test_invalid_credentials( + hass: HomeAssistant, + exception: Exception, + mock_config_entry: MockConfigEntry, + mock_fyta_connector: AsyncMock, +) -> None: + """Test FYTA credentials changing.""" + + mock_fyta_connector.expiration = datetime.fromisoformat(EXPIRATION_OLD).replace( + tzinfo=UTC + ) + mock_fyta_connector.login.side_effect = exception + + await setup_platform(hass, mock_config_entry, [Platform.SENSOR]) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR + + +async def test_raise_config_entry_not_ready_when_offline( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_fyta_connector: AsyncMock, +) -> None: + """Config entry state is SETUP_RETRY when FYTA is offline.""" + + mock_fyta_connector.update_all_plants.side_effect = FytaConnectionError + + await setup_platform(hass, mock_config_entry, [Platform.SENSOR]) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY + + assert len(hass.config_entries.flow.async_progress()) == 0 + + async def test_migrate_config_entry( hass: HomeAssistant, - mock_fyta_init: AsyncMock, + mock_fyta_connector: AsyncMock, ) -> None: """Test successful migration of entry data.""" entry = MockConfigEntry( - domain=DOMAIN, + domain=FYTA_DOMAIN, title=USERNAME, data={ CONF_USERNAME: USERNAME, @@ -39,4 +112,4 @@ async def test_migrate_config_entry( assert entry.data[CONF_USERNAME] == USERNAME assert entry.data[CONF_PASSWORD] == PASSWORD assert entry.data[CONF_ACCESS_TOKEN] == ACCESS_TOKEN - assert entry.data[CONF_EXPIRATION] == "2024-12-31T10:00:00+00:00" + assert entry.data[CONF_EXPIRATION] == EXPIRATION