diff --git a/.coveragerc b/.coveragerc index a4594a80e6e..36a1bb56ffb 100644 --- a/.coveragerc +++ b/.coveragerc @@ -471,9 +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/coordinator.py - homeassistant/components/fyta/entity.py - homeassistant/components/fyta/sensor.py homeassistant/components/garadget/cover.py homeassistant/components/garages_amsterdam/__init__.py homeassistant/components/garages_amsterdam/binary_sensor.py diff --git a/tests/components/fyta/conftest.py b/tests/components/fyta/conftest.py index aad93e38b90..cf6fb69e83d 100644 --- a/tests/components/fyta/conftest.py +++ b/tests/components/fyta/conftest.py @@ -11,7 +11,7 @@ from homeassistant.const import CONF_ACCESS_TOKEN, CONF_PASSWORD, CONF_USERNAME from .const import ACCESS_TOKEN, EXPIRATION, PASSWORD, USERNAME -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, load_json_object_fixture @pytest.fixture @@ -27,6 +27,7 @@ def mock_config_entry() -> MockConfigEntry: CONF_EXPIRATION: EXPIRATION, }, minor_version=2, + entry_id="ce5f5431554d101905d31797e1232da8", ) @@ -39,6 +40,13 @@ def mock_fyta_connector(): tzinfo=UTC ) mock_fyta_connector.client = AsyncMock(autospec=True) + mock_fyta_connector.update_all_plants.return_value = load_json_object_fixture( + "plant_status.json", FYTA_DOMAIN + ) + mock_fyta_connector.plant_list = load_json_object_fixture( + "plant_list.json", FYTA_DOMAIN + ) + mock_fyta_connector.login = AsyncMock( return_value={ CONF_ACCESS_TOKEN: ACCESS_TOKEN, diff --git a/tests/components/fyta/fixtures/plant_list.json b/tests/components/fyta/fixtures/plant_list.json new file mode 100644 index 00000000000..9527c7d9d96 --- /dev/null +++ b/tests/components/fyta/fixtures/plant_list.json @@ -0,0 +1,4 @@ +{ + "0": "Gummibaum", + "1": "Kakaobaum" +} diff --git a/tests/components/fyta/fixtures/plant_status.json b/tests/components/fyta/fixtures/plant_status.json new file mode 100644 index 00000000000..5d9cb2d31d9 --- /dev/null +++ b/tests/components/fyta/fixtures/plant_status.json @@ -0,0 +1,14 @@ +{ + "0": { + "name": "Gummibaum", + "scientific_name": "Ficus elastica", + "status": 1, + "sw_version": "1.0" + }, + "1": { + "name": "Kakaobaum", + "scientific_name": "Theobroma cacao", + "status": 2, + "sw_version": "1.0" + } +} diff --git a/tests/components/fyta/snapshots/test_sensor.ambr b/tests/components/fyta/snapshots/test_sensor.ambr new file mode 100644 index 00000000000..1041fff501e --- /dev/null +++ b/tests/components/fyta/snapshots/test_sensor.ambr @@ -0,0 +1,213 @@ +# serializer version: 1 +# name: test_all_entities[sensor.gummibaum_plant_state-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'deleted', + 'doing_great', + 'need_attention', + 'no_sensor', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.gummibaum_plant_state', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Plant state', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'plant_status', + 'unique_id': 'ce5f5431554d101905d31797e1232da8-0-status', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.gummibaum_plant_state-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Gummibaum Plant state', + 'options': list([ + 'deleted', + 'doing_great', + 'need_attention', + 'no_sensor', + ]), + }), + 'context': , + 'entity_id': 'sensor.gummibaum_plant_state', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'doing_great', + }) +# --- +# name: test_all_entities[sensor.gummibaum_scientific_name-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.gummibaum_scientific_name', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Scientific name', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'scientific_name', + 'unique_id': 'ce5f5431554d101905d31797e1232da8-0-scientific_name', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.gummibaum_scientific_name-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Gummibaum Scientific name', + }), + 'context': , + 'entity_id': 'sensor.gummibaum_scientific_name', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'Ficus elastica', + }) +# --- +# name: test_all_entities[sensor.kakaobaum_plant_state-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'deleted', + 'doing_great', + 'need_attention', + 'no_sensor', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.kakaobaum_plant_state', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Plant state', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'plant_status', + 'unique_id': 'ce5f5431554d101905d31797e1232da8-1-status', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.kakaobaum_plant_state-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Kakaobaum Plant state', + 'options': list([ + 'deleted', + 'doing_great', + 'need_attention', + 'no_sensor', + ]), + }), + 'context': , + 'entity_id': 'sensor.kakaobaum_plant_state', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'need_attention', + }) +# --- +# name: test_all_entities[sensor.kakaobaum_scientific_name-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.kakaobaum_scientific_name', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Scientific name', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'scientific_name', + 'unique_id': 'ce5f5431554d101905d31797e1232da8-1-scientific_name', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.kakaobaum_scientific_name-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Kakaobaum Scientific name', + }), + 'context': , + 'entity_id': 'sensor.kakaobaum_scientific_name', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'Theobroma cacao', + }) +# --- diff --git a/tests/components/fyta/test_init.py b/tests/components/fyta/test_init.py index 0abe877a4e2..88cb125ecee 100644 --- a/tests/components/fyta/test_init.py +++ b/tests/components/fyta/test_init.py @@ -41,6 +41,23 @@ async def test_load_unload( assert mock_config_entry.state is ConfigEntryState.NOT_LOADED +async def test_refresh_expired_token( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_fyta_connector: AsyncMock, +) -> None: + """Test we refresh an expired token.""" + + mock_fyta_connector.expiration = datetime.fromisoformat(EXPIRATION_OLD).replace( + tzinfo=UTC + ) + await setup_platform(hass, mock_config_entry, [Platform.SENSOR]) + assert mock_config_entry.state is ConfigEntryState.LOADED + + assert len(mock_fyta_connector.login.mock_calls) == 1 + assert mock_config_entry.data[CONF_EXPIRATION] == EXPIRATION + + @pytest.mark.parametrize( "exception", [ @@ -84,6 +101,26 @@ async def test_raise_config_entry_not_ready_when_offline( assert len(hass.config_entries.flow.async_progress()) == 0 +async def test_raise_config_entry_not_ready_when_offline_and_expired( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_fyta_connector: AsyncMock, +) -> None: + """Config entry state is SETUP_RETRY when FYTA is offline and access_token is expired.""" + + mock_fyta_connector.login.side_effect = FytaConnectionError + mock_fyta_connector.expiration = datetime.fromisoformat(EXPIRATION_OLD).replace( + tzinfo=UTC + ) + + 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_connector: AsyncMock, diff --git a/tests/components/fyta/test_sensor.py b/tests/components/fyta/test_sensor.py new file mode 100644 index 00000000000..0c73cbd41d2 --- /dev/null +++ b/tests/components/fyta/test_sensor.py @@ -0,0 +1,47 @@ +"""Test the Home Assistant fyta sensor module.""" + +from datetime import timedelta +from unittest.mock import AsyncMock + +from freezegun.api import FrozenDateTimeFactory +from fyta_cli.fyta_exceptions import FytaConnectionError +from syrupy import SnapshotAssertion + +from homeassistant.const import STATE_UNAVAILABLE, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import setup_platform + +from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform + + +async def test_all_entities( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + mock_fyta_connector: AsyncMock, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, +) -> None: + """Test all entities.""" + + await setup_platform(hass, mock_config_entry, [Platform.SENSOR]) + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) + + +async def test_connection_error( + hass: HomeAssistant, + mock_fyta_connector: AsyncMock, + mock_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test connection error.""" + await setup_platform(hass, mock_config_entry, [Platform.SENSOR]) + + mock_fyta_connector.update_all_plants.side_effect = FytaConnectionError + + freezer.tick(delta=timedelta(hours=12)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + assert hass.states.get("sensor.gummibaum_plant_state").state == STATE_UNAVAILABLE