Add tests for Bring integration (#123087)

* Add tests to bring integration

* use more parametrization

* json fixture loading, move notification tests
This commit is contained in:
Mr. Bubbles 2024-08-21 22:18:33 +02:00 committed by GitHub
parent 5f53d3f917
commit 67bc568db6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 601 additions and 5 deletions

View File

@ -3,6 +3,7 @@
from collections.abc import Generator from collections.abc import Generator
from typing import cast from typing import cast
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
import uuid
from bring_api.types import BringAuthResponse from bring_api.types import BringAuthResponse
import pytest import pytest
@ -10,7 +11,7 @@ import pytest
from homeassistant.components.bring import DOMAIN from homeassistant.components.bring import DOMAIN
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from tests.common import MockConfigEntry from tests.common import MockConfigEntry, load_json_object_fixture
EMAIL = "test-email" EMAIL = "test-email"
PASSWORD = "test-password" PASSWORD = "test-password"
@ -43,10 +44,23 @@ def mock_bring_client() -> Generator[AsyncMock]:
client = mock_client.return_value client = mock_client.return_value
client.uuid = UUID client.uuid = UUID
client.login.return_value = cast(BringAuthResponse, {"name": "Bring"}) client.login.return_value = cast(BringAuthResponse, {"name": "Bring"})
client.load_lists.return_value = {"lists": []} client.load_lists.return_value = load_json_object_fixture("lists.json", DOMAIN)
client.get_list.return_value = load_json_object_fixture("items.json", DOMAIN)
yield client yield client
@pytest.fixture
def mock_uuid() -> Generator[AsyncMock]:
"""Mock uuid."""
with patch(
"homeassistant.components.bring.todo.uuid.uuid4",
autospec=True,
) as mock_client:
mock_client.return_value = uuid.UUID("b669ad23-606a-4652-b302-995d34b1cb1c")
yield mock_client
@pytest.fixture(name="bring_config_entry") @pytest.fixture(name="bring_config_entry")
def mock_bring_config_entry() -> MockConfigEntry: def mock_bring_config_entry() -> MockConfigEntry:
"""Mock bring configuration entry.""" """Mock bring configuration entry."""

View File

@ -0,0 +1,26 @@
{
"uuid": "77a151f8-77c4-47a3-8295-c750a0e69d4f",
"status": "REGISTERED",
"purchase": [
{
"uuid": "b5d0790b-5f32-4d5c-91da-e29066f167de",
"itemId": "Paprika",
"specification": "Rot",
"attributes": []
},
{
"uuid": "72d370ab-d8ca-4e41-b956-91df94795b4e",
"itemId": "Pouletbrüstli",
"specification": "Bio",
"attributes": []
}
],
"recently": [
{
"uuid": "fc8db30a-647e-4e6c-9d71-3b85d6a2d954",
"itemId": "Ananas",
"specification": "",
"attributes": []
}
]
}

View File

@ -0,0 +1,14 @@
{
"lists": [
{
"listUuid": "e542eef6-dba7-4c31-a52c-29e6ab9d83a5",
"name": "Einkauf",
"theme": "ch.publisheria.bring.theme.home"
},
{
"listUuid": "b4776778-7f6c-496e-951b-92a35d3db0dd",
"name": "Baumarkt",
"theme": "ch.publisheria.bring.theme.home"
}
]
}

View File

@ -0,0 +1,95 @@
# serializer version: 1
# name: test_todo[todo.baumarkt-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'todo',
'entity_category': None,
'entity_id': 'todo.baumarkt',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Baumarkt',
'platform': 'bring',
'previous_unique_id': None,
'supported_features': <TodoListEntityFeature: 71>,
'translation_key': 'shopping_list',
'unique_id': '00000000-00000000-00000000-00000000_b4776778-7f6c-496e-951b-92a35d3db0dd',
'unit_of_measurement': None,
})
# ---
# name: test_todo[todo.baumarkt-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Baumarkt',
'supported_features': <TodoListEntityFeature: 71>,
}),
'context': <ANY>,
'entity_id': 'todo.baumarkt',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '2',
})
# ---
# name: test_todo[todo.einkauf-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'todo',
'entity_category': None,
'entity_id': 'todo.einkauf',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Einkauf',
'platform': 'bring',
'previous_unique_id': None,
'supported_features': <TodoListEntityFeature: 71>,
'translation_key': 'shopping_list',
'unique_id': '00000000-00000000-00000000-00000000_e542eef6-dba7-4c31-a52c-29e6ab9d83a5',
'unit_of_measurement': None,
})
# ---
# name: test_todo[todo.einkauf-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Einkauf',
'supported_features': <TodoListEntityFeature: 71>,
}),
'context': <ANY>,
'entity_id': 'todo.einkauf',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '2',
})
# ---

View File

@ -28,9 +28,9 @@ async def setup_integration(
await hass.async_block_till_done() await hass.async_block_till_done()
@pytest.mark.usefixtures("mock_bring_client")
async def test_load_unload( async def test_load_unload(
hass: HomeAssistant, hass: HomeAssistant,
mock_bring_client: AsyncMock,
bring_config_entry: MockConfigEntry, bring_config_entry: MockConfigEntry,
) -> None: ) -> None:
"""Test loading and unloading of the config entry.""" """Test loading and unloading of the config entry."""
@ -58,7 +58,7 @@ async def test_init_failure(
mock_bring_client: AsyncMock, mock_bring_client: AsyncMock,
status: ConfigEntryState, status: ConfigEntryState,
exception: Exception, exception: Exception,
bring_config_entry: MockConfigEntry | None, bring_config_entry: MockConfigEntry,
) -> None: ) -> None:
"""Test an initialization error on integration load.""" """Test an initialization error on integration load."""
mock_bring_client.login.side_effect = exception mock_bring_client.login.side_effect = exception
@ -79,7 +79,7 @@ async def test_init_exceptions(
mock_bring_client: AsyncMock, mock_bring_client: AsyncMock,
exception: Exception, exception: Exception,
expected: Exception, expected: Exception,
bring_config_entry: MockConfigEntry | None, bring_config_entry: MockConfigEntry,
) -> None: ) -> None:
"""Test an initialization error on integration load.""" """Test an initialization error on integration load."""
bring_config_entry.add_to_hass(hass) bring_config_entry.add_to_hass(hass)
@ -87,3 +87,42 @@ async def test_init_exceptions(
with pytest.raises(expected): with pytest.raises(expected):
await async_setup_entry(hass, bring_config_entry) await async_setup_entry(hass, bring_config_entry)
@pytest.mark.parametrize("exception", [BringRequestException, BringParseException])
@pytest.mark.parametrize("bring_method", ["load_lists", "get_list"])
async def test_config_entry_not_ready(
hass: HomeAssistant,
bring_config_entry: MockConfigEntry,
mock_bring_client: AsyncMock,
exception: Exception,
bring_method: str,
) -> None:
"""Test config entry not ready."""
getattr(mock_bring_client, bring_method).side_effect = exception
bring_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(bring_config_entry.entry_id)
await hass.async_block_till_done()
assert bring_config_entry.state is ConfigEntryState.SETUP_RETRY
@pytest.mark.parametrize(
"exception", [None, BringAuthException, BringRequestException, BringParseException]
)
async def test_config_entry_not_ready_auth_error(
hass: HomeAssistant,
bring_config_entry: MockConfigEntry,
mock_bring_client: AsyncMock,
exception: Exception | None,
) -> None:
"""Test config entry not ready from authentication error."""
mock_bring_client.load_lists.side_effect = BringAuthException
mock_bring_client.retrieve_new_access_token.side_effect = exception
bring_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(bring_config_entry.entry_id)
await hass.async_block_till_done()
assert bring_config_entry.state is ConfigEntryState.SETUP_RETRY

View File

@ -0,0 +1,106 @@
"""Test todo entity notification action of the Bring! integration."""
import re
from unittest.mock import AsyncMock
from bring_api import BringNotificationType, BringRequestException
import pytest
from homeassistant.components.bring.const import (
ATTR_ITEM_NAME,
ATTR_NOTIFICATION_TYPE,
DOMAIN,
SERVICE_PUSH_NOTIFICATION,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from tests.common import MockConfigEntry
async def test_send_notification(
hass: HomeAssistant,
bring_config_entry: MockConfigEntry,
mock_bring_client: AsyncMock,
) -> None:
"""Test send bring push notification."""
bring_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(bring_config_entry.entry_id)
await hass.async_block_till_done()
assert bring_config_entry.state is ConfigEntryState.LOADED
await hass.services.async_call(
DOMAIN,
SERVICE_PUSH_NOTIFICATION,
service_data={
ATTR_NOTIFICATION_TYPE: "GOING_SHOPPING",
},
target={ATTR_ENTITY_ID: "todo.einkauf"},
blocking=True,
)
mock_bring_client.notify.assert_called_once_with(
"e542eef6-dba7-4c31-a52c-29e6ab9d83a5",
BringNotificationType.GOING_SHOPPING,
None,
)
async def test_send_notification_exception(
hass: HomeAssistant,
bring_config_entry: MockConfigEntry,
mock_bring_client: AsyncMock,
) -> None:
"""Test send bring push notification with exception."""
bring_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(bring_config_entry.entry_id)
await hass.async_block_till_done()
assert bring_config_entry.state is ConfigEntryState.LOADED
mock_bring_client.notify.side_effect = BringRequestException
with pytest.raises(
HomeAssistantError,
match="Failed to send push notification for bring due to a connection error, try again later",
):
await hass.services.async_call(
DOMAIN,
SERVICE_PUSH_NOTIFICATION,
service_data={
ATTR_NOTIFICATION_TYPE: "GOING_SHOPPING",
},
target={ATTR_ENTITY_ID: "todo.einkauf"},
blocking=True,
)
async def test_send_notification_service_validation_error(
hass: HomeAssistant,
bring_config_entry: MockConfigEntry,
mock_bring_client: AsyncMock,
) -> None:
"""Test send bring push notification."""
bring_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(bring_config_entry.entry_id)
await hass.async_block_till_done()
assert bring_config_entry.state is ConfigEntryState.LOADED
mock_bring_client.notify.side_effect = ValueError
with pytest.raises(
HomeAssistantError,
match=re.escape(
"Failed to perform action bring.send_message. 'URGENT_MESSAGE' requires a value @ data['item']. Got None"
),
):
await hass.services.async_call(
DOMAIN,
SERVICE_PUSH_NOTIFICATION,
service_data={ATTR_NOTIFICATION_TYPE: "URGENT_MESSAGE", ATTR_ITEM_NAME: ""},
target={ATTR_ENTITY_ID: "todo.einkauf"},
blocking=True,
)

View File

@ -0,0 +1,302 @@
"""Test for todo platform of the Bring! integration."""
import re
from unittest.mock import AsyncMock
from bring_api import BringItemOperation, BringRequestException
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.todo import (
ATTR_DESCRIPTION,
ATTR_ITEM,
ATTR_RENAME,
DOMAIN as TODO_DOMAIN,
TodoServices,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform
@pytest.mark.usefixtures("mock_bring_client")
async def test_todo(
hass: HomeAssistant,
bring_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
) -> None:
"""Snapshot test states of todo platform."""
bring_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(bring_config_entry.entry_id)
await hass.async_block_till_done()
assert bring_config_entry.state is ConfigEntryState.LOADED
await snapshot_platform(
hass, entity_registry, snapshot, bring_config_entry.entry_id
)
@pytest.mark.usefixtures("mock_uuid")
async def test_add_item(
hass: HomeAssistant,
bring_config_entry: MockConfigEntry,
mock_bring_client: AsyncMock,
) -> None:
"""Test add item to list."""
bring_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(bring_config_entry.entry_id)
await hass.async_block_till_done()
assert bring_config_entry.state is ConfigEntryState.LOADED
await hass.services.async_call(
TODO_DOMAIN,
TodoServices.ADD_ITEM,
service_data={ATTR_ITEM: "Äpfel", ATTR_DESCRIPTION: "rot"},
target={ATTR_ENTITY_ID: "todo.einkauf"},
blocking=True,
)
mock_bring_client.save_item.assert_called_once_with(
"e542eef6-dba7-4c31-a52c-29e6ab9d83a5",
"Äpfel",
"rot",
"b669ad23-606a-4652-b302-995d34b1cb1c",
)
async def test_add_item_exception(
hass: HomeAssistant,
bring_config_entry: MockConfigEntry,
mock_bring_client: AsyncMock,
) -> None:
"""Test add item to list with exception."""
bring_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(bring_config_entry.entry_id)
await hass.async_block_till_done()
assert bring_config_entry.state is ConfigEntryState.LOADED
mock_bring_client.save_item.side_effect = BringRequestException
with pytest.raises(
HomeAssistantError, match="Failed to save item Äpfel to Bring! list"
):
await hass.services.async_call(
TODO_DOMAIN,
TodoServices.ADD_ITEM,
service_data={ATTR_ITEM: "Äpfel", ATTR_DESCRIPTION: "rot"},
target={ATTR_ENTITY_ID: "todo.einkauf"},
blocking=True,
)
@pytest.mark.usefixtures("mock_uuid")
async def test_update_item(
hass: HomeAssistant,
bring_config_entry: MockConfigEntry,
mock_bring_client: AsyncMock,
) -> None:
"""Test update item."""
bring_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(bring_config_entry.entry_id)
await hass.async_block_till_done()
assert bring_config_entry.state is ConfigEntryState.LOADED
await hass.services.async_call(
TODO_DOMAIN,
TodoServices.UPDATE_ITEM,
service_data={
ATTR_ITEM: "b5d0790b-5f32-4d5c-91da-e29066f167de",
ATTR_RENAME: "Paprika",
ATTR_DESCRIPTION: "Rot",
},
target={ATTR_ENTITY_ID: "todo.einkauf"},
blocking=True,
)
mock_bring_client.batch_update_list.assert_called_once_with(
"e542eef6-dba7-4c31-a52c-29e6ab9d83a5",
{
"itemId": "Paprika",
"spec": "Rot",
"uuid": "b5d0790b-5f32-4d5c-91da-e29066f167de",
},
BringItemOperation.ADD,
)
async def test_update_item_exception(
hass: HomeAssistant,
bring_config_entry: MockConfigEntry,
mock_bring_client: AsyncMock,
) -> None:
"""Test update item with exception."""
bring_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(bring_config_entry.entry_id)
await hass.async_block_till_done()
assert bring_config_entry.state is ConfigEntryState.LOADED
mock_bring_client.batch_update_list.side_effect = BringRequestException
with pytest.raises(
HomeAssistantError, match="Failed to update item Paprika to Bring! list"
):
await hass.services.async_call(
TODO_DOMAIN,
TodoServices.UPDATE_ITEM,
service_data={
ATTR_ITEM: "b5d0790b-5f32-4d5c-91da-e29066f167de",
ATTR_RENAME: "Paprika",
ATTR_DESCRIPTION: "Rot",
},
target={ATTR_ENTITY_ID: "todo.einkauf"},
blocking=True,
)
@pytest.mark.usefixtures("mock_uuid")
async def test_rename_item(
hass: HomeAssistant,
bring_config_entry: MockConfigEntry,
mock_bring_client: AsyncMock,
) -> None:
"""Test rename item."""
bring_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(bring_config_entry.entry_id)
await hass.async_block_till_done()
assert bring_config_entry.state is ConfigEntryState.LOADED
await hass.services.async_call(
TODO_DOMAIN,
TodoServices.UPDATE_ITEM,
service_data={
ATTR_ITEM: "b5d0790b-5f32-4d5c-91da-e29066f167de",
ATTR_RENAME: "Gurke",
ATTR_DESCRIPTION: "",
},
target={ATTR_ENTITY_ID: "todo.einkauf"},
blocking=True,
)
mock_bring_client.batch_update_list.assert_called_once_with(
"e542eef6-dba7-4c31-a52c-29e6ab9d83a5",
[
{
"itemId": "Paprika",
"spec": "",
"uuid": "b5d0790b-5f32-4d5c-91da-e29066f167de",
"operation": BringItemOperation.REMOVE,
},
{
"itemId": "Gurke",
"spec": "",
"uuid": "b669ad23-606a-4652-b302-995d34b1cb1c",
"operation": BringItemOperation.ADD,
},
],
)
async def test_rename_item_exception(
hass: HomeAssistant,
bring_config_entry: MockConfigEntry,
mock_bring_client: AsyncMock,
) -> None:
"""Test rename item with exception."""
bring_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(bring_config_entry.entry_id)
await hass.async_block_till_done()
assert bring_config_entry.state is ConfigEntryState.LOADED
mock_bring_client.batch_update_list.side_effect = BringRequestException
with pytest.raises(
HomeAssistantError, match="Failed to rename item Gurke to Bring! list"
):
await hass.services.async_call(
TODO_DOMAIN,
TodoServices.UPDATE_ITEM,
service_data={
ATTR_ITEM: "b5d0790b-5f32-4d5c-91da-e29066f167de",
ATTR_RENAME: "Gurke",
ATTR_DESCRIPTION: "",
},
target={ATTR_ENTITY_ID: "todo.einkauf"},
blocking=True,
)
@pytest.mark.usefixtures("mock_uuid")
async def test_delete_items(
hass: HomeAssistant,
bring_config_entry: MockConfigEntry,
mock_bring_client: AsyncMock,
) -> None:
"""Test delete item."""
bring_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(bring_config_entry.entry_id)
await hass.async_block_till_done()
assert bring_config_entry.state is ConfigEntryState.LOADED
await hass.services.async_call(
TODO_DOMAIN,
TodoServices.REMOVE_ITEM,
service_data={ATTR_ITEM: "b5d0790b-5f32-4d5c-91da-e29066f167de"},
target={ATTR_ENTITY_ID: "todo.einkauf"},
blocking=True,
)
mock_bring_client.batch_update_list.assert_called_once_with(
"e542eef6-dba7-4c31-a52c-29e6ab9d83a5",
[
{
"itemId": "b5d0790b-5f32-4d5c-91da-e29066f167de",
"spec": "",
"uuid": "b5d0790b-5f32-4d5c-91da-e29066f167de",
},
],
BringItemOperation.REMOVE,
)
async def test_delete_items_exception(
hass: HomeAssistant,
bring_config_entry: MockConfigEntry,
mock_bring_client: AsyncMock,
) -> None:
"""Test delete item."""
bring_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(bring_config_entry.entry_id)
await hass.async_block_till_done()
assert bring_config_entry.state is ConfigEntryState.LOADED
mock_bring_client.batch_update_list.side_effect = BringRequestException
with pytest.raises(
HomeAssistantError,
match=re.escape("Failed to delete 1 item(s) from Bring! list"),
):
await hass.services.async_call(
TODO_DOMAIN,
TodoServices.REMOVE_ITEM,
service_data={ATTR_ITEM: "b5d0790b-5f32-4d5c-91da-e29066f167de"},
target={ATTR_ENTITY_ID: "todo.einkauf"},
blocking=True,
)