Report the item a cart action actually acted on
Validate / hassfest (push) Failing after 6s
Validate / HACS (push) Failing after 16s
Validate / pytest (push) Failing after 7s

_cart_action scraped the first .item-name off the item page, which belongs to
whatever is in the mini-cart rather than the item being added — so an add
announced someone else's groceries in its event payload. Callers pass the name
they resolved instead; verified by an add/remove round trip against a live
order.
This commit is contained in:
flan
2026-08-03 20:23:49 +00:00
parent 5e9d721a0f
commit 0a218acd45
2 changed files with 13 additions and 9 deletions
+11 -7
View File
@@ -323,7 +323,7 @@ class FreshHarvestActions:
return await self._client.async_fetch(SHOP_ITEM.format(item_id=item_id))
async def async_add_item(
self, item_id: int | str, dry_run: bool = True
self, item_id: int | str, dry_run: bool = True, name: str | None = None
) -> ActionResult:
"""Add one of an item to the open order.
@@ -331,14 +331,16 @@ class FreshHarvestActions:
absence *is* the out-of-stock signal — no separate stock lookup can go
stale behind our back.
"""
return await self._cart_action("add", item_id, dry_run)
return await self._cart_action("add", item_id, dry_run, name)
async def async_remove_item(
self, item_id: int | str, dry_run: bool = True
self, item_id: int | str, dry_run: bool = True, name: str | None = None
) -> ActionResult:
return await self._cart_action("remove", item_id, dry_run)
return await self._cart_action("remove", item_id, dry_run, name)
async def _cart_action(self, mode: str, item_id, dry_run: bool) -> ActionResult:
async def _cart_action(
self, mode: str, item_id, dry_run: bool, name: str | None = None
) -> ActionResult:
page = await self._item_page(item_id)
m = re.search(r'orderManage\("%s","([^"]+)"' % mode, page)
if not m:
@@ -346,14 +348,16 @@ class FreshHarvestActions:
f"item {item_id} cannot be {mode}ed right now — the page offers "
"no control for it, which usually means it is out of stock"
)
name = BeautifulSoup(page, "html.parser").select_one(".item-name")
# Do NOT scrape a name off this page: the first `.item-name` belongs to
# whatever is in the mini-cart, not the item being acted on, so an add
# would announce someone else's groceries. Callers know the real name.
url = AJAX_ORDER_MANAGE.format(
mode=mode, hash=m.group(1), ts=int(time.time() * 1000)
)
result = ActionResult(
action=f"{mode}_item",
ok=True,
target=name.get_text(" ", strip=True) if name else str(item_id),
target=name or str(item_id),
submitted={"url": url},
dry_run=dry_run,
detail=f"{mode} item {item_id}",
+2 -2
View File
@@ -151,7 +151,7 @@ class FreshHarvestBox(FreshHarvestEntity, TodoListEntity):
item_id, name = await self._algolia_lookup(query)
try:
result = await self.coordinator.actions.async_add_item(
item_id, dry_run=False
item_id, dry_run=False, name=name
)
except FreshHarvestError as err:
self._fire(EVENT_ACTION, "add_item", False, query, str(err))
@@ -166,7 +166,7 @@ class FreshHarvestBox(FreshHarvestEntity, TodoListEntity):
item_id, name = await self._algolia_lookup(uid)
try:
await self.coordinator.actions.async_remove_item(
item_id, dry_run=False
item_id, dry_run=False, name=name
)
except FreshHarvestError as err:
self._fire(EVENT_ACTION, "remove_item", False, uid, str(err))