Allow adding items Picnic shopping cart by searching (#102862)

Co-authored-by: Robert Resch <robert@resch.dev>
This commit is contained in:
Duco Sebel 2023-11-24 13:06:32 +01:00 committed by GitHub
parent adc56b6b67
commit 62473936e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 204 additions and 12 deletions

View File

@ -66,7 +66,7 @@ async def handle_add_product(
product_id = call.data.get("product_id")
if not product_id:
product_id = await hass.async_add_executor_job(
_product_search, api_client, cast(str, call.data["product_name"])
product_search, api_client, cast(str, call.data["product_name"])
)
if not product_id:
@ -77,7 +77,7 @@ async def handle_add_product(
)
def _product_search(api_client: PicnicAPI, product_name: str) -> None | str:
def product_search(api_client: PicnicAPI, product_name: str) -> None | str:
"""Query the api client for the product name."""
search_result = api_client.search(product_name)

View File

@ -4,7 +4,12 @@ from __future__ import annotations
import logging
from typing import Any, cast
from homeassistant.components.todo import TodoItem, TodoItemStatus, TodoListEntity
from homeassistant.components.todo import (
TodoItem,
TodoItemStatus,
TodoListEntity,
TodoListEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
@ -15,6 +20,7 @@ from homeassistant.helpers.update_coordinator import (
)
from .const import CONF_COORDINATOR, DOMAIN
from .services import product_search
_LOGGER = logging.getLogger(__name__)
@ -27,19 +33,20 @@ async def async_setup_entry(
"""Set up the Picnic shopping cart todo platform config entry."""
picnic_coordinator = hass.data[DOMAIN][config_entry.entry_id][CONF_COORDINATOR]
# Add an entity shopping card
async_add_entities([PicnicCart(picnic_coordinator, config_entry)])
async_add_entities([PicnicCart(hass, picnic_coordinator, config_entry)])
class PicnicCart(TodoListEntity, CoordinatorEntity):
"""A Picnic Shopping Cart TodoListEntity."""
_attr_has_entity_name = True
_attr_translation_key = "shopping_cart"
_attr_icon = "mdi:cart"
_attr_supported_features = TodoListEntityFeature.CREATE_TODO_ITEM
_attr_translation_key = "shopping_cart"
def __init__(
self,
hass: HomeAssistant,
coordinator: DataUpdateCoordinator[Any],
config_entry: ConfigEntry,
) -> None:
@ -51,6 +58,7 @@ class PicnicCart(TodoListEntity, CoordinatorEntity):
manufacturer="Picnic",
model=config_entry.unique_id,
)
self.hass = hass
self._attr_unique_id = f"{config_entry.unique_id}-cart"
@property
@ -73,3 +81,18 @@ class PicnicCart(TodoListEntity, CoordinatorEntity):
)
return items
async def async_create_todo_item(self, item: TodoItem) -> None:
"""Add item to shopping cart."""
product_id = await self.hass.async_add_executor_job(
product_search, self.coordinator.picnic_api_client, item.summary
)
if not product_id:
raise ValueError("No product found or no product ID given")
await self.hass.async_add_executor_job(
self.coordinator.picnic_api_client.add_product, product_id, 1
)
await self.coordinator.async_refresh()

View File

@ -1,4 +1,5 @@
"""Conftest for Picnic tests."""
from collections.abc import Awaitable, Callable
import json
from unittest.mock import MagicMock, patch
@ -9,6 +10,9 @@ from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry, load_fixture
from tests.typing import WebSocketGenerator
ENTITY_ID = "todo.mock_title_shopping_cart"
@pytest.fixture
@ -50,3 +54,42 @@ async def init_integration(
await hass.async_block_till_done()
return mock_config_entry
@pytest.fixture
def ws_req_id() -> Callable[[], int]:
"""Fixture for incremental websocket requests."""
id = 0
def next_id() -> int:
nonlocal id
id += 1
return id
return next_id
@pytest.fixture
async def get_items(
hass_ws_client: WebSocketGenerator, ws_req_id: Callable[[], int]
) -> Callable[[], Awaitable[dict[str, str]]]:
"""Fixture to fetch items from the todo websocket."""
async def get() -> list[dict[str, str]]:
# Fetch items using To-do platform
client = await hass_ws_client()
id = ws_req_id()
await client.send_json(
{
"id": id,
"type": "todo/item/list",
"entity_id": ENTITY_ID,
}
)
resp = await client.receive_json()
assert resp.get("id") == id
assert resp.get("success")
return resp.get("result", {}).get("items", [])
return get

View File

@ -0,0 +1,55 @@
# serializer version: 1
# name: test_cart_list_with_items
list([
dict({
'status': 'needs_action',
'summary': 'Knoflook (2 stuks)',
'uid': '763-s1001194',
}),
dict({
'status': 'needs_action',
'summary': 'Picnic magere melk (2 x 1 liter)',
'uid': '765_766-s1046297',
}),
dict({
'status': 'needs_action',
'summary': 'Picnic magere melk (1 liter)',
'uid': '767-s1010532',
}),
dict({
'status': 'needs_action',
'summary': 'Robijn wascapsules wit (40 wasbeurten)',
'uid': '774_775-s1018253',
}),
dict({
'status': 'needs_action',
'summary': 'Robijn wascapsules kleur (15 wasbeurten)',
'uid': '774_775-s1007025',
}),
dict({
'status': 'needs_action',
'summary': 'Chinese wokgroenten (600 gram)',
'uid': '776_777_778_779_780-s1012699',
}),
dict({
'status': 'needs_action',
'summary': 'Picnic boerderij-eitjes (6 stuks M/L)',
'uid': '776_777_778_779_780-s1003425',
}),
dict({
'status': 'needs_action',
'summary': 'Picnic witte snelkookrijst (400 gram)',
'uid': '776_777_778_779_780-s1016692',
}),
dict({
'status': 'needs_action',
'summary': 'Conimex kruidenmix nasi (20 gram)',
'uid': '776_777_778_779_780-s1012503',
}),
dict({
'status': 'needs_action',
'summary': 'Conimex satésaus mild kant & klaar (400 gram)',
'uid': '776_777_778_779_780-s1005028',
}),
])
# ---

View File

@ -1,18 +1,31 @@
"""Tests for Picnic Tasks todo platform."""
from unittest.mock import MagicMock
from unittest.mock import MagicMock, Mock
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.todo import DOMAIN
from homeassistant.core import HomeAssistant
from .conftest import ENTITY_ID
from tests.common import MockConfigEntry
async def test_cart_list_with_items(hass: HomeAssistant, init_integration) -> None:
async def test_cart_list_with_items(
hass: HomeAssistant,
init_integration,
get_items,
snapshot: SnapshotAssertion,
) -> None:
"""Test loading of shopping cart."""
state = hass.states.get("todo.mock_title_shopping_cart")
state = hass.states.get(ENTITY_ID)
assert state
assert state.state == "10"
assert snapshot == await get_items()
async def test_cart_list_empty_items(
hass: HomeAssistant, mock_picnic_api: MagicMock, mock_config_entry: MockConfigEntry
@ -23,7 +36,7 @@ async def test_cart_list_empty_items(
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("todo.mock_title_shopping_cart")
state = hass.states.get(ENTITY_ID)
assert state
assert state.state == "0"
@ -37,7 +50,7 @@ async def test_cart_list_unexpected_response(
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("todo.mock_title_shopping_cart")
state = hass.states.get(ENTITY_ID)
assert state is None
@ -50,5 +63,63 @@ async def test_cart_list_null_response(
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("todo.mock_title_shopping_cart")
state = hass.states.get(ENTITY_ID)
assert state is None
async def test_create_todo_list_item(
hass: HomeAssistant, init_integration: MockConfigEntry, mock_picnic_api: MagicMock
) -> None:
"""Test for creating a picnic cart item."""
assert len(mock_picnic_api.get_cart.mock_calls) == 1
mock_picnic_api.search = Mock()
mock_picnic_api.search.return_value = [
{
"items": [
{
"id": 321,
"name": "Picnic Melk",
"unit_quantity": "2 liter",
}
]
}
]
mock_picnic_api.add_product = Mock()
await hass.services.async_call(
DOMAIN,
"add_item",
{"item": "Melk"},
target={"entity_id": ENTITY_ID},
blocking=True,
)
args = mock_picnic_api.search.call_args
assert args
assert args[0][0] == "Melk"
args = mock_picnic_api.add_product.call_args
assert args
assert args[0][0] == "321"
assert args[0][1] == 1
assert len(mock_picnic_api.get_cart.mock_calls) == 2
async def test_create_todo_list_item_not_found(
hass: HomeAssistant, init_integration: MockConfigEntry, mock_picnic_api: MagicMock
) -> None:
"""Test for creating a picnic cart item when ID is not found."""
mock_picnic_api.search = Mock()
mock_picnic_api.search.return_value = [{"items": []}]
with pytest.raises(ValueError):
await hass.services.async_call(
DOMAIN,
"add_item",
{"item": "Melk"},
target={"entity_id": ENTITY_ID},
blocking=True,
)