From d88851a85a5e9beb8cdb21c68c3578674a9755d1 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 14 May 2024 20:47:14 +0200 Subject: [PATCH] Refactor Linear tests (#116336) --- .../components/linear_garage_door/__init__.py | 21 ++ .../components/linear_garage_door/conftest.py | 67 +++++ .../fixtures/get_device_state.json | 42 +++ .../fixtures/get_device_state_1.json | 42 +++ .../fixtures/get_devices.json | 22 ++ .../fixtures/get_sites.json | 1 + .../snapshots/test_cover.ambr | 193 +++++++++++++ .../snapshots/test_diagnostics.ambr | 2 +- .../linear_garage_door/test_config_flow.py | 237 +++++++--------- .../linear_garage_door/test_coordinator.py | 73 ----- .../linear_garage_door/test_cover.py | 267 ++++++------------ .../linear_garage_door/test_diagnostics.py | 13 +- .../linear_garage_door/test_init.py | 88 +++--- tests/components/linear_garage_door/util.py | 84 ------ 14 files changed, 621 insertions(+), 531 deletions(-) create mode 100644 tests/components/linear_garage_door/conftest.py create mode 100644 tests/components/linear_garage_door/fixtures/get_device_state.json create mode 100644 tests/components/linear_garage_door/fixtures/get_device_state_1.json create mode 100644 tests/components/linear_garage_door/fixtures/get_devices.json create mode 100644 tests/components/linear_garage_door/fixtures/get_sites.json create mode 100644 tests/components/linear_garage_door/snapshots/test_cover.ambr delete mode 100644 tests/components/linear_garage_door/test_coordinator.py delete mode 100644 tests/components/linear_garage_door/util.py diff --git a/tests/components/linear_garage_door/__init__.py b/tests/components/linear_garage_door/__init__.py index e5abc6c943c..67bd1ee2da2 100644 --- a/tests/components/linear_garage_door/__init__.py +++ b/tests/components/linear_garage_door/__init__.py @@ -1 +1,22 @@ """Tests for the Linear Garage Door integration.""" + +from unittest.mock import patch + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def setup_integration( + hass: HomeAssistant, config_entry: MockConfigEntry, platforms: list[Platform] +) -> None: + """Fixture for setting up the component.""" + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.linear_garage_door.PLATFORMS", + platforms, + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/components/linear_garage_door/conftest.py b/tests/components/linear_garage_door/conftest.py new file mode 100644 index 00000000000..5e7fcdeee68 --- /dev/null +++ b/tests/components/linear_garage_door/conftest.py @@ -0,0 +1,67 @@ +"""Common fixtures for the Linear Garage Door tests.""" + +from collections.abc import Generator +from unittest.mock import AsyncMock, patch + +import pytest + +from homeassistant.components.linear_garage_door import DOMAIN +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD + +from tests.common import ( + MockConfigEntry, + load_json_array_fixture, + load_json_object_fixture, +) + + +@pytest.fixture +def mock_setup_entry() -> Generator[AsyncMock, None, None]: + """Override async_setup_entry.""" + with patch( + "homeassistant.components.linear_garage_door.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + yield mock_setup_entry + + +@pytest.fixture +def mock_linear() -> Generator[AsyncMock, None, None]: + """Mock a Linear Garage Door client.""" + with ( + patch( + "homeassistant.components.linear_garage_door.coordinator.Linear", + autospec=True, + ) as mock_client, + patch( + "homeassistant.components.linear_garage_door.config_flow.Linear", + new=mock_client, + ), + ): + client = mock_client.return_value + client.login.return_value = True + client.get_devices.return_value = load_json_array_fixture( + "get_devices.json", DOMAIN + ) + client.get_sites.return_value = load_json_array_fixture( + "get_sites.json", DOMAIN + ) + device_states = load_json_object_fixture("get_device_state.json", DOMAIN) + client.get_device_state.side_effect = lambda device_id: device_states[device_id] + yield client + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Mock a config entry.""" + return MockConfigEntry( + domain=DOMAIN, + entry_id="acefdd4b3a4a0911067d1cf51414201e", + title="test-site-name", + data={ + CONF_EMAIL: "test-email", + CONF_PASSWORD: "test-password", + "site_id": "test-site-id", + "device_id": "test-uuid", + }, + ) diff --git a/tests/components/linear_garage_door/fixtures/get_device_state.json b/tests/components/linear_garage_door/fixtures/get_device_state.json new file mode 100644 index 00000000000..14247610e06 --- /dev/null +++ b/tests/components/linear_garage_door/fixtures/get_device_state.json @@ -0,0 +1,42 @@ +{ + "test1": { + "GDO": { + "Open_B": "true", + "Open_P": "100" + }, + "Light": { + "On_B": "true", + "On_P": "100" + } + }, + "test2": { + "GDO": { + "Open_B": "false", + "Open_P": "0" + }, + "Light": { + "On_B": "false", + "On_P": "0" + } + }, + "test3": { + "GDO": { + "Open_B": "false", + "Opening_P": "0" + }, + "Light": { + "On_B": "false", + "On_P": "0" + } + }, + "test4": { + "GDO": { + "Open_B": "true", + "Opening_P": "100" + }, + "Light": { + "On_B": "true", + "On_P": "100" + } + } +} diff --git a/tests/components/linear_garage_door/fixtures/get_device_state_1.json b/tests/components/linear_garage_door/fixtures/get_device_state_1.json new file mode 100644 index 00000000000..9dbd20eb42f --- /dev/null +++ b/tests/components/linear_garage_door/fixtures/get_device_state_1.json @@ -0,0 +1,42 @@ +{ + "test1": { + "GDO": { + "Open_B": "true", + "Opening_P": "100" + }, + "Light": { + "On_B": "true", + "On_P": "100" + } + }, + "test2": { + "GDO": { + "Open_B": "false", + "Opening_P": "0" + }, + "Light": { + "On_B": "false", + "On_P": "0" + } + }, + "test3": { + "GDO": { + "Open_B": "false", + "Opening_P": "0" + }, + "Light": { + "On_B": "false", + "On_P": "0" + } + }, + "test4": { + "GDO": { + "Open_B": "true", + "Opening_P": "100" + }, + "Light": { + "On_B": "true", + "On_P": "100" + } + } +} diff --git a/tests/components/linear_garage_door/fixtures/get_devices.json b/tests/components/linear_garage_door/fixtures/get_devices.json new file mode 100644 index 00000000000..da6eeaf7448 --- /dev/null +++ b/tests/components/linear_garage_door/fixtures/get_devices.json @@ -0,0 +1,22 @@ +[ + { + "id": "test1", + "name": "Test Garage 1", + "subdevices": ["GDO", "Light"] + }, + { + "id": "test2", + "name": "Test Garage 2", + "subdevices": ["GDO", "Light"] + }, + { + "id": "test3", + "name": "Test Garage 3", + "subdevices": ["GDO", "Light"] + }, + { + "id": "test4", + "name": "Test Garage 4", + "subdevices": ["GDO", "Light"] + } +] diff --git a/tests/components/linear_garage_door/fixtures/get_sites.json b/tests/components/linear_garage_door/fixtures/get_sites.json new file mode 100644 index 00000000000..2b0a49b9007 --- /dev/null +++ b/tests/components/linear_garage_door/fixtures/get_sites.json @@ -0,0 +1 @@ +[{ "id": "test-site-id", "name": "test-site-name" }] diff --git a/tests/components/linear_garage_door/snapshots/test_cover.ambr b/tests/components/linear_garage_door/snapshots/test_cover.ambr new file mode 100644 index 00000000000..96745e1d92a --- /dev/null +++ b/tests/components/linear_garage_door/snapshots/test_cover.ambr @@ -0,0 +1,193 @@ +# serializer version: 1 +# name: test_covers[cover.test_garage_1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'cover', + 'entity_category': None, + 'entity_id': 'cover.test_garage_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'linear_garage_door', + 'previous_unique_id': None, + 'supported_features': , + 'translation_key': None, + 'unique_id': 'test1-GDO', + 'unit_of_measurement': None, + }) +# --- +# name: test_covers[cover.test_garage_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'garage', + 'friendly_name': 'Test Garage 1', + 'supported_features': , + }), + 'context': , + 'entity_id': 'cover.test_garage_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'open', + }) +# --- +# name: test_covers[cover.test_garage_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'cover', + 'entity_category': None, + 'entity_id': 'cover.test_garage_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'linear_garage_door', + 'previous_unique_id': None, + 'supported_features': , + 'translation_key': None, + 'unique_id': 'test2-GDO', + 'unit_of_measurement': None, + }) +# --- +# name: test_covers[cover.test_garage_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'garage', + 'friendly_name': 'Test Garage 2', + 'supported_features': , + }), + 'context': , + 'entity_id': 'cover.test_garage_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'closed', + }) +# --- +# name: test_covers[cover.test_garage_3-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'cover', + 'entity_category': None, + 'entity_id': 'cover.test_garage_3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'linear_garage_door', + 'previous_unique_id': None, + 'supported_features': , + 'translation_key': None, + 'unique_id': 'test3-GDO', + 'unit_of_measurement': None, + }) +# --- +# name: test_covers[cover.test_garage_3-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'garage', + 'friendly_name': 'Test Garage 3', + 'supported_features': , + }), + 'context': , + 'entity_id': 'cover.test_garage_3', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'opening', + }) +# --- +# name: test_covers[cover.test_garage_4-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'cover', + 'entity_category': None, + 'entity_id': 'cover.test_garage_4', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'linear_garage_door', + 'previous_unique_id': None, + 'supported_features': , + 'translation_key': None, + 'unique_id': 'test4-GDO', + 'unit_of_measurement': None, + }) +# --- +# name: test_covers[cover.test_garage_4-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'garage', + 'friendly_name': 'Test Garage 4', + 'supported_features': , + }), + 'context': , + 'entity_id': 'cover.test_garage_4', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'closing', + }) +# --- diff --git a/tests/components/linear_garage_door/snapshots/test_diagnostics.ambr b/tests/components/linear_garage_door/snapshots/test_diagnostics.ambr index 72886410924..2543ca42156 100644 --- a/tests/components/linear_garage_door/snapshots/test_diagnostics.ambr +++ b/tests/components/linear_garage_door/snapshots/test_diagnostics.ambr @@ -71,7 +71,7 @@ 'pref_disable_new_entities': False, 'pref_disable_polling': False, 'source': 'user', - 'title': 'Mock Title', + 'title': 'test-site-name', 'unique_id': None, 'version': 1, }), diff --git a/tests/components/linear_garage_door/test_config_flow.py b/tests/components/linear_garage_door/test_config_flow.py index 9704268e650..4599bd24aef 100644 --- a/tests/components/linear_garage_door/test_config_flow.py +++ b/tests/components/linear_garage_door/test_config_flow.py @@ -1,180 +1,141 @@ """Test the Linear Garage Door config flow.""" -from unittest.mock import patch +from unittest.mock import AsyncMock, patch from linear_garage_door.errors import InvalidLoginError +import pytest -from homeassistant import config_entries from homeassistant.components.linear_garage_door.const import DOMAIN +from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType -from .util import async_init_integration +from tests.common import MockConfigEntry -async def test_form(hass: HomeAssistant) -> None: +async def test_form( + hass: HomeAssistant, mock_linear: AsyncMock, mock_setup_entry: AsyncMock +) -> None: """Test we get the form.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] is FlowResultType.FORM - assert result["errors"] is None + assert not result["errors"] - with ( - patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.login", - return_value=True, - ), - patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.get_sites", - return_value=[{"id": "test-site-id", "name": "test-site-name"}], - ), - patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.close", - return_value=None, - ), - patch( - "uuid.uuid4", - return_value="test-uuid", - ), + with patch( + "uuid.uuid4", + return_value="test-uuid", ): - result2 = await hass.config_entries.flow.async_configure( + result = await hass.config_entries.flow.async_configure( result["flow_id"], { - "email": "test-email", - "password": "test-password", + CONF_EMAIL: "test-email", + CONF_PASSWORD: "test-password", }, ) await hass.async_block_till_done() - with patch( - "homeassistant.components.linear_garage_door.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result3 = await hass.config_entries.flow.async_configure( - result2["flow_id"], {"site": "test-site-id"} - ) - await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"site": "test-site-id"} + ) + await hass.async_block_till_done() - assert result3["type"] is FlowResultType.CREATE_ENTRY - assert result3["title"] == "test-site-name" - assert result3["data"] == { - "email": "test-email", - "password": "test-password", + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["title"] == "test-site-name" + assert result["data"] == { + CONF_EMAIL: "test-email", + CONF_PASSWORD: "test-password", "site_id": "test-site-id", "device_id": "test-uuid", } assert len(mock_setup_entry.mock_calls) == 1 -async def test_reauth(hass: HomeAssistant) -> None: +async def test_reauth( + hass: HomeAssistant, + mock_linear: AsyncMock, + mock_setup_entry: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: """Test reauthentication.""" - - with patch( - "homeassistant.components.linear_garage_door.async_setup_entry", - return_value=True, - ): - entry = await async_init_integration(hass) - - result1 = await hass.config_entries.flow.async_init( - DOMAIN, - context={ - "source": config_entries.SOURCE_REAUTH, - "entry_id": entry.entry_id, - "title_placeholders": {"name": entry.title}, - "unique_id": entry.unique_id, - }, - data=entry.data, - ) - assert result1["type"] is FlowResultType.FORM - assert result1["step_id"] == "user" - - with ( - patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.login", - return_value=True, - ), - patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.get_sites", - return_value=[{"id": "test-site-id", "name": "test-site-name"}], - ), - patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.close", - return_value=None, - ), - patch( - "uuid.uuid4", - return_value="test-uuid", - ), - ): - result2 = await hass.config_entries.flow.async_configure( - result1["flow_id"], - { - "email": "new-email", - "password": "new-password", - }, - ) - await hass.async_block_till_done() - - assert result2["type"] is FlowResultType.ABORT - assert result2["reason"] == "reauth_successful" - - entries = hass.config_entries.async_entries() - assert len(entries) == 1 - assert entries[0].data == { - "email": "new-email", - "password": "new-password", - "site_id": "test-site-id", - "device_id": "test-uuid", - } - - -async def test_form_invalid_login(hass: HomeAssistant) -> None: - """Test we handle invalid auth.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - with ( - patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.login", - side_effect=InvalidLoginError, - ), - patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.close", - return_value=None, - ), - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "email": "test-email", - "password": "test-password", - }, - ) - - assert result2["type"] is FlowResultType.FORM - assert result2["errors"] == {"base": "invalid_auth"} - - -async def test_form_exception(hass: HomeAssistant) -> None: - """Test we handle invalid auth.""" + mock_config_entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_USER}, + context={ + "source": SOURCE_REAUTH, + "entry_id": mock_config_entry.entry_id, + "title_placeholders": {"name": mock_config_entry.title}, + "unique_id": mock_config_entry.unique_id, + }, + data=mock_config_entry.data, ) + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" with patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.login", - side_effect=Exception, + "uuid.uuid4", + return_value="test-uuid", ): - result2 = await hass.config_entries.flow.async_configure( + result = await hass.config_entries.flow.async_configure( result["flow_id"], { - "email": "test-email", - "password": "test-password", + CONF_EMAIL: "new-email", + CONF_PASSWORD: "new-password", }, ) + await hass.async_block_till_done() - assert result2["type"] is FlowResultType.FORM - assert result2["errors"] == {"base": "unknown"} + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reauth_successful" + + assert mock_config_entry.data == { + CONF_EMAIL: "new-email", + CONF_PASSWORD: "new-password", + "site_id": "test-site-id", + "device_id": "test-uuid", + } + + +@pytest.mark.parametrize( + ("side_effect", "expected_error"), + [(InvalidLoginError, "invalid_auth"), (Exception, "unknown")], +) +async def test_form_exceptions( + hass: HomeAssistant, + mock_linear: AsyncMock, + mock_setup_entry: AsyncMock, + side_effect: Exception, + expected_error: str, +) -> None: + """Test we handle invalid auth.""" + mock_linear.login.side_effect = side_effect + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_EMAIL: "test-email", + CONF_PASSWORD: "test-password", + }, + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": expected_error} + mock_linear.login.side_effect = None + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_EMAIL: "test-email", + CONF_PASSWORD: "test-password", + }, + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"site": "test-site-id"} + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.CREATE_ENTRY diff --git a/tests/components/linear_garage_door/test_coordinator.py b/tests/components/linear_garage_door/test_coordinator.py deleted file mode 100644 index be38b316c56..00000000000 --- a/tests/components/linear_garage_door/test_coordinator.py +++ /dev/null @@ -1,73 +0,0 @@ -"""Test data update coordinator for Linear Garage Door.""" - -from unittest.mock import patch - -from linear_garage_door.errors import InvalidLoginError - -from homeassistant.components.linear_garage_door.const import DOMAIN -from homeassistant.config_entries import ConfigEntryState -from homeassistant.core import HomeAssistant - -from tests.common import MockConfigEntry - - -async def test_invalid_password( - hass: HomeAssistant, -) -> None: - """Test invalid password.""" - config_entry = MockConfigEntry( - domain=DOMAIN, - data={ - "email": "test-email", - "password": "test-password", - "site_id": "test-site-id", - "device_id": "test-uuid", - }, - ) - config_entry.add_to_hass(hass) - - with patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.login", - side_effect=InvalidLoginError( - "Login provided is invalid, please check the email and password" - ), - ): - assert not await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - entries = hass.config_entries.async_entries(DOMAIN) - assert entries - assert len(entries) == 1 - assert entries[0].state is ConfigEntryState.SETUP_ERROR - flows = hass.config_entries.flow.async_progress_by_handler(DOMAIN) - assert flows - assert len(flows) == 1 - assert flows[0]["context"]["source"] == "reauth" - - -async def test_invalid_login( - hass: HomeAssistant, -) -> None: - """Test invalid login.""" - config_entry = MockConfigEntry( - domain=DOMAIN, - data={ - "email": "test-email", - "password": "test-password", - "site_id": "test-site-id", - "device_id": "test-uuid", - }, - ) - config_entry.add_to_hass(hass) - - with patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.login", - side_effect=InvalidLoginError("Some other error"), - ): - assert not await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - entries = hass.config_entries.async_entries(DOMAIN) - assert entries - assert len(entries) == 1 - assert entries[0].state is ConfigEntryState.SETUP_RETRY diff --git a/tests/components/linear_garage_door/test_cover.py b/tests/components/linear_garage_door/test_cover.py index 6236d2ba39c..f4593ff4d60 100644 --- a/tests/components/linear_garage_door/test_cover.py +++ b/tests/components/linear_garage_door/test_cover.py @@ -1,221 +1,124 @@ """Test Linear Garage Door cover.""" from datetime import timedelta -from unittest.mock import patch +from unittest.mock import AsyncMock + +from freezegun.api import FrozenDateTimeFactory +from syrupy import SnapshotAssertion from homeassistant.components.cover import ( DOMAIN as COVER_DOMAIN, SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER, +) +from homeassistant.components.linear_garage_door import DOMAIN +from homeassistant.const import ( + ATTR_ENTITY_ID, STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING, + Platform, ) -from homeassistant.components.linear_garage_door.const import DOMAIN -from homeassistant.config_entries import ConfigEntryState -from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import HomeAssistant -import homeassistant.util.dt as dt_util +from homeassistant.helpers import entity_registry as er -from .util import async_init_integration +from . import setup_integration -from tests.common import async_fire_time_changed +from tests.common import ( + MockConfigEntry, + async_fire_time_changed, + load_json_object_fixture, + snapshot_platform, +) -async def test_data(hass: HomeAssistant) -> None: +async def test_covers( + hass: HomeAssistant, + mock_linear: AsyncMock, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, + mock_config_entry: MockConfigEntry, +) -> None: """Test that data gets parsed and returned appropriately.""" - await async_init_integration(hass) + await setup_integration(hass, mock_config_entry, [Platform.COVER]) - assert hass.data[DOMAIN] - entries = hass.config_entries.async_entries(DOMAIN) - assert entries - assert len(entries) == 1 - assert entries[0].state is ConfigEntryState.LOADED - assert hass.states.get("cover.test_garage_1").state == STATE_OPEN - assert hass.states.get("cover.test_garage_2").state == STATE_CLOSED - assert hass.states.get("cover.test_garage_3").state == STATE_OPENING - assert hass.states.get("cover.test_garage_4").state == STATE_CLOSING + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) -async def test_open_cover(hass: HomeAssistant) -> None: +async def test_open_cover( + hass: HomeAssistant, mock_linear: AsyncMock, mock_config_entry: MockConfigEntry +) -> None: """Test that opening the cover works as intended.""" - await async_init_integration(hass) + await setup_integration(hass, mock_config_entry, [Platform.COVER]) - with patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.operate_device" - ) as operate_device: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: "cover.test_garage_1"}, - blocking=True, - ) + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_OPEN_COVER, + {ATTR_ENTITY_ID: "cover.test_garage_1"}, + blocking=True, + ) - assert operate_device.call_count == 0 + assert mock_linear.operate_device.call_count == 0 - with ( - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.login", - return_value=True, - ), - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.operate_device", - return_value=None, - ) as operate_device, - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.close", - return_value=True, - ), - ): - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: "cover.test_garage_2"}, - blocking=True, - ) + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_OPEN_COVER, + {ATTR_ENTITY_ID: "cover.test_garage_2"}, + blocking=True, + ) - assert operate_device.call_count == 1 - with ( - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.login", - return_value=True, - ), - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.get_devices", - return_value=[ - { - "id": "test1", - "name": "Test Garage 1", - "subdevices": ["GDO", "Light"], - }, - { - "id": "test2", - "name": "Test Garage 2", - "subdevices": ["GDO", "Light"], - }, - ], - ), - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.get_device_state", - side_effect=lambda id: { - "test1": { - "GDO": {"Open_B": "true", "Open_P": "100"}, - "Light": {"On_B": "true", "On_P": "100"}, - }, - "test2": { - "GDO": {"Open_B": "false", "Opening_P": "0"}, - "Light": {"On_B": "false", "On_P": "0"}, - }, - "test3": { - "GDO": {"Open_B": "false", "Opening_P": "0"}, - "Light": {"On_B": "false", "On_P": "0"}, - }, - "test4": { - "GDO": {"Open_B": "true", "Opening_P": "100"}, - "Light": {"On_B": "true", "On_P": "100"}, - }, - }[id], - ), - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.close", - return_value=True, - ), - ): - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=60)) - await hass.async_block_till_done() - - assert hass.states.get("cover.test_garage_2").state == STATE_OPENING + assert mock_linear.operate_device.call_count == 1 -async def test_close_cover(hass: HomeAssistant) -> None: +async def test_close_cover( + hass: HomeAssistant, mock_linear: AsyncMock, mock_config_entry: MockConfigEntry +) -> None: """Test that closing the cover works as intended.""" - await async_init_integration(hass) + await setup_integration(hass, mock_config_entry, [Platform.COVER]) - with patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.operate_device" - ) as operate_device: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: "cover.test_garage_2"}, - blocking=True, - ) + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: "cover.test_garage_2"}, + blocking=True, + ) - assert operate_device.call_count == 0 + assert mock_linear.operate_device.call_count == 0 - with ( - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.login", - return_value=True, - ), - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.operate_device", - return_value=None, - ) as operate_device, - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.close", - return_value=True, - ), - ): - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: "cover.test_garage_1"}, - blocking=True, - ) + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: "cover.test_garage_1"}, + blocking=True, + ) - assert operate_device.call_count == 1 - with ( - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.login", - return_value=True, - ), - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.get_devices", - return_value=[ - { - "id": "test1", - "name": "Test Garage 1", - "subdevices": ["GDO", "Light"], - }, - { - "id": "test2", - "name": "Test Garage 2", - "subdevices": ["GDO", "Light"], - }, - ], - ), - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.get_device_state", - side_effect=lambda id: { - "test1": { - "GDO": {"Open_B": "true", "Opening_P": "100"}, - "Light": {"On_B": "true", "On_P": "100"}, - }, - "test2": { - "GDO": {"Open_B": "false", "Open_P": "0"}, - "Light": {"On_B": "false", "On_P": "0"}, - }, - "test3": { - "GDO": {"Open_B": "false", "Opening_P": "0"}, - "Light": {"On_B": "false", "On_P": "0"}, - }, - "test4": { - "GDO": {"Open_B": "true", "Opening_P": "100"}, - "Light": {"On_B": "true", "On_P": "100"}, - }, - }[id], - ), - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.close", - return_value=True, - ), - ): - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=60)) - await hass.async_block_till_done() + assert mock_linear.operate_device.call_count == 1 + + +async def test_update_cover_state( + hass: HomeAssistant, + mock_linear: AsyncMock, + mock_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test that closing the cover works as intended.""" + + await setup_integration(hass, mock_config_entry, [Platform.COVER]) + + assert hass.states.get("cover.test_garage_1").state == STATE_OPEN + assert hass.states.get("cover.test_garage_2").state == STATE_CLOSED + + device_states = load_json_object_fixture("get_device_state_1.json", DOMAIN) + mock_linear.get_device_state.side_effect = lambda device_id: device_states[ + device_id + ] + + freezer.tick(timedelta(seconds=60)) + async_fire_time_changed(hass) assert hass.states.get("cover.test_garage_1").state == STATE_CLOSING + assert hass.states.get("cover.test_garage_2").state == STATE_OPENING diff --git a/tests/components/linear_garage_door/test_diagnostics.py b/tests/components/linear_garage_door/test_diagnostics.py index a9565441bbb..6bf7415bde5 100644 --- a/tests/components/linear_garage_door/test_diagnostics.py +++ b/tests/components/linear_garage_door/test_diagnostics.py @@ -1,11 +1,14 @@ """Test diagnostics of Linear Garage Door.""" +from unittest.mock import AsyncMock + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant -from .util import async_init_integration +from . import setup_integration +from tests.common import MockConfigEntry from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.typing import ClientSessionGenerator @@ -14,8 +17,12 @@ async def test_entry_diagnostics( hass: HomeAssistant, hass_client: ClientSessionGenerator, snapshot: SnapshotAssertion, + mock_linear: AsyncMock, + mock_config_entry: MockConfigEntry, ) -> None: """Test config entry diagnostics.""" - entry = await async_init_integration(hass) - result = await get_diagnostics_for_config_entry(hass, hass_client, entry) + await setup_integration(hass, mock_config_entry, []) + result = await get_diagnostics_for_config_entry( + hass, hass_client, mock_config_entry + ) assert result == snapshot diff --git a/tests/components/linear_garage_door/test_init.py b/tests/components/linear_garage_door/test_init.py index 63975c8bd3f..92ff832be87 100644 --- a/tests/components/linear_garage_door/test_init.py +++ b/tests/components/linear_garage_door/test_init.py @@ -1,64 +1,52 @@ """Test Linear Garage Door init.""" -from unittest.mock import patch +from unittest.mock import AsyncMock + +from linear_garage_door import InvalidLoginError +import pytest -from homeassistant.components.linear_garage_door.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry +from tests.components.linear_garage_door import setup_integration -async def test_unload_entry(hass: HomeAssistant) -> None: +async def test_unload_entry( + hass: HomeAssistant, mock_linear: AsyncMock, mock_config_entry: MockConfigEntry +) -> None: """Test the unload entry.""" - config_entry = MockConfigEntry( - domain=DOMAIN, - data={ - "email": "test-email", - "password": "test-password", - "site_id": "test-site-id", - "device_id": "test-uuid", - }, - ) - config_entry.add_to_hass(hass) - with ( - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.login", - return_value=True, - ), - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.get_devices", - return_value=[ - {"id": "test", "name": "Test Garage", "subdevices": ["GDO", "Light"]} - ], - ), - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.get_device_state", - return_value={ - "GDO": {"Open_B": "true", "Open_P": "100"}, - "Light": {"On_B": "true", "On_P": "10"}, - }, - ), - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.close", - return_value=True, - ), - ): - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + await setup_integration(hass, mock_config_entry, []) + assert mock_config_entry.state is ConfigEntryState.LOADED - assert hass.data[DOMAIN] + 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 - entries = hass.config_entries.async_entries(DOMAIN) - assert entries - assert len(entries) == 1 - assert entries[0].state is ConfigEntryState.LOADED - with patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.close", - return_value=True, - ): - await hass.config_entries.async_unload(entries[0].entry_id) - await hass.async_block_till_done() - assert entries[0].state is ConfigEntryState.NOT_LOADED +@pytest.mark.parametrize( + ("side_effect", "entry_state"), + [ + ( + InvalidLoginError( + "Login provided is invalid, please check the email and password" + ), + ConfigEntryState.SETUP_ERROR, + ), + (InvalidLoginError("Invalid login"), ConfigEntryState.SETUP_RETRY), + ], +) +async def test_setup_failure( + hass: HomeAssistant, + mock_linear: AsyncMock, + mock_config_entry: MockConfigEntry, + side_effect: Exception, + entry_state: ConfigEntryState, +) -> None: + """Test reauth trigger setup.""" + + mock_linear.login.side_effect = side_effect + + await setup_integration(hass, mock_config_entry, []) + assert mock_config_entry.state == entry_state diff --git a/tests/components/linear_garage_door/util.py b/tests/components/linear_garage_door/util.py deleted file mode 100644 index 30dbdbd06d5..00000000000 --- a/tests/components/linear_garage_door/util.py +++ /dev/null @@ -1,84 +0,0 @@ -"""Utilities for Linear Garage Door testing.""" - -from unittest.mock import patch - -from homeassistant.components.linear_garage_door.const import DOMAIN -from homeassistant.core import HomeAssistant - -from tests.common import MockConfigEntry - - -async def async_init_integration(hass: HomeAssistant) -> MockConfigEntry: - """Initialize mock integration.""" - config_entry = MockConfigEntry( - domain=DOMAIN, - entry_id="acefdd4b3a4a0911067d1cf51414201e", - data={ - "email": "test-email", - "password": "test-password", - "site_id": "test-site-id", - "device_id": "test-uuid", - }, - ) - config_entry.add_to_hass(hass) - - with ( - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.login", - return_value=True, - ), - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.get_devices", - return_value=[ - { - "id": "test1", - "name": "Test Garage 1", - "subdevices": ["GDO", "Light"], - }, - { - "id": "test2", - "name": "Test Garage 2", - "subdevices": ["GDO", "Light"], - }, - { - "id": "test3", - "name": "Test Garage 3", - "subdevices": ["GDO", "Light"], - }, - { - "id": "test4", - "name": "Test Garage 4", - "subdevices": ["GDO", "Light"], - }, - ], - ), - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.get_device_state", - side_effect=lambda id: { - "test1": { - "GDO": {"Open_B": "true", "Open_P": "100"}, - "Light": {"On_B": "true", "On_P": "100"}, - }, - "test2": { - "GDO": {"Open_B": "false", "Open_P": "0"}, - "Light": {"On_B": "false", "On_P": "0"}, - }, - "test3": { - "GDO": {"Open_B": "false", "Opening_P": "0"}, - "Light": {"On_B": "false", "On_P": "0"}, - }, - "test4": { - "GDO": {"Open_B": "true", "Opening_P": "100"}, - "Light": {"On_B": "true", "On_P": "100"}, - }, - }[id], - ), - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.close", - return_value=True, - ), - ): - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - return config_entry