mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Don't fetch unchanged OurGroceries lists (#105998)
This commit is contained in:
parent
33bcf70bf3
commit
3e07cf50ce
@ -24,16 +24,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
data = entry.data
|
||||
og = OurGroceries(data[CONF_USERNAME], data[CONF_PASSWORD])
|
||||
lists = []
|
||||
try:
|
||||
await og.login()
|
||||
lists = (await og.get_my_lists())["shoppingLists"]
|
||||
except (AsyncIOTimeoutError, ClientError) as error:
|
||||
raise ConfigEntryNotReady from error
|
||||
except InvalidLoginException:
|
||||
return False
|
||||
|
||||
coordinator = OurGroceriesDataUpdateCoordinator(hass, og, lists)
|
||||
coordinator = OurGroceriesDataUpdateCoordinator(hass, og)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
|
||||
|
@ -20,13 +20,11 @@ _LOGGER = logging.getLogger(__name__)
|
||||
class OurGroceriesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]):
|
||||
"""Class to manage fetching OurGroceries data."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, og: OurGroceries, lists: list[dict]
|
||||
) -> None:
|
||||
def __init__(self, hass: HomeAssistant, og: OurGroceries) -> None:
|
||||
"""Initialize global OurGroceries data updater."""
|
||||
self.og = og
|
||||
self.lists = lists
|
||||
self._ids = [sl["id"] for sl in lists]
|
||||
self.lists: list[dict] = []
|
||||
self._cache: dict[str, dict] = {}
|
||||
interval = timedelta(seconds=SCAN_INTERVAL)
|
||||
super().__init__(
|
||||
hass,
|
||||
@ -35,13 +33,16 @@ class OurGroceriesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]):
|
||||
update_interval=interval,
|
||||
)
|
||||
|
||||
async def _update_list(self, list_id: str, version_id: str) -> None:
|
||||
old_version = self._cache.get(list_id, {}).get("list", {}).get("versionId", "")
|
||||
if old_version == version_id:
|
||||
return
|
||||
self._cache[list_id] = await self.og.get_list_items(list_id=list_id)
|
||||
|
||||
async def _async_update_data(self) -> dict[str, dict]:
|
||||
"""Fetch data from OurGroceries."""
|
||||
return dict(
|
||||
zip(
|
||||
self._ids,
|
||||
await asyncio.gather(
|
||||
*[self.og.get_list_items(list_id=id) for id in self._ids]
|
||||
),
|
||||
)
|
||||
self.lists = (await self.og.get_my_lists())["shoppingLists"]
|
||||
await asyncio.gather(
|
||||
*[self._update_list(sl["id"], sl["versionId"]) for sl in self.lists]
|
||||
)
|
||||
return self._cache
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""Tests for the OurGroceries integration."""
|
||||
|
||||
|
||||
def items_to_shopping_list(items: list) -> dict[dict[list]]:
|
||||
def items_to_shopping_list(items: list, version_id: str = "1") -> dict[dict[list]]:
|
||||
"""Convert a list of items into a shopping list."""
|
||||
return {"list": {"items": items}}
|
||||
return {"list": {"versionId": version_id, "items": items}}
|
||||
|
@ -46,7 +46,7 @@ def mock_ourgroceries(items: list[dict]) -> AsyncMock:
|
||||
og = AsyncMock()
|
||||
og.login.return_value = True
|
||||
og.get_my_lists.return_value = {
|
||||
"shoppingLists": [{"id": "test_list", "name": "Test List"}]
|
||||
"shoppingLists": [{"id": "test_list", "name": "Test List", "versionId": "1"}]
|
||||
}
|
||||
og.get_list_items.return_value = items_to_shopping_list(items)
|
||||
return og
|
||||
|
@ -17,6 +17,10 @@ from . import items_to_shopping_list
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
|
||||
def _mock_version_id(og: AsyncMock, version: int) -> None:
|
||||
og.get_my_lists.return_value["shoppingLists"][0]["versionId"] = str(version)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("items", "expected_state"),
|
||||
[
|
||||
@ -57,8 +61,10 @@ async def test_add_todo_list_item(
|
||||
|
||||
ourgroceries.add_item_to_list = AsyncMock()
|
||||
# Fake API response when state is refreshed after create
|
||||
_mock_version_id(ourgroceries, 2)
|
||||
ourgroceries.get_list_items.return_value = items_to_shopping_list(
|
||||
[{"id": "12345", "name": "Soda"}]
|
||||
[{"id": "12345", "name": "Soda"}],
|
||||
version_id="2",
|
||||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
@ -95,6 +101,7 @@ async def test_update_todo_item_status(
|
||||
ourgroceries.toggle_item_crossed_off = AsyncMock()
|
||||
|
||||
# Fake API response when state is refreshed after crossing off
|
||||
_mock_version_id(ourgroceries, 2)
|
||||
ourgroceries.get_list_items.return_value = items_to_shopping_list(
|
||||
[{"id": "12345", "name": "Soda", "crossedOffAt": 1699107501}]
|
||||
)
|
||||
@ -118,6 +125,7 @@ async def test_update_todo_item_status(
|
||||
assert state.state == "0"
|
||||
|
||||
# Fake API response when state is refreshed after reopen
|
||||
_mock_version_id(ourgroceries, 2)
|
||||
ourgroceries.get_list_items.return_value = items_to_shopping_list(
|
||||
[{"id": "12345", "name": "Soda"}]
|
||||
)
|
||||
@ -166,6 +174,7 @@ async def test_update_todo_item_summary(
|
||||
ourgroceries.change_item_on_list = AsyncMock()
|
||||
|
||||
# Fake API response when state is refreshed update
|
||||
_mock_version_id(ourgroceries, 2)
|
||||
ourgroceries.get_list_items.return_value = items_to_shopping_list(
|
||||
[{"id": "12345", "name": "Milk"}]
|
||||
)
|
||||
@ -204,6 +213,7 @@ async def test_remove_todo_item(
|
||||
|
||||
ourgroceries.remove_item_from_list = AsyncMock()
|
||||
# Fake API response when state is refreshed after remove
|
||||
_mock_version_id(ourgroceries, 2)
|
||||
ourgroceries.get_list_items.return_value = items_to_shopping_list([])
|
||||
|
||||
await hass.services.async_call(
|
||||
@ -224,6 +234,25 @@ async def test_remove_todo_item(
|
||||
assert state.state == "0"
|
||||
|
||||
|
||||
async def test_version_id_optimization(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
setup_integration: None,
|
||||
ourgroceries: AsyncMock,
|
||||
) -> None:
|
||||
"""Test that list items aren't being retrieved if version id stays the same."""
|
||||
state = hass.states.get("todo.test_list")
|
||||
assert state.state == "0"
|
||||
assert ourgroceries.get_list_items.call_count == 1
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("todo.test_list")
|
||||
assert state.state == "0"
|
||||
assert ourgroceries.get_list_items.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("exception"),
|
||||
[
|
||||
@ -242,6 +271,7 @@ async def test_coordinator_error(
|
||||
state = hass.states.get("todo.test_list")
|
||||
assert state.state == "0"
|
||||
|
||||
_mock_version_id(ourgroceries, 2)
|
||||
ourgroceries.get_list_items.side_effect = exception
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
|
Loading…
x
Reference in New Issue
Block a user