From b5cfb5b205847620fd567f76abf8126c77f698a2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 06:04:27 +0000 Subject: [PATCH 1/3] Use rohlik-api 0.3.0: shared shop list and minimum order value - Bump rohlik-api to 0.3.0 (manifest and test requirements). - Take the shop presets (base URL, name, currency, timezone) from rohlik_api.SITES instead of a local copy in const.py. The codes stored on config entries (cz, de, at, hu, ro) are the same, so existing entries are unaffected. - Cart Total sensor: new "Minimum Order Price" attribute from Cart.minimum_order_price, present only when the shop reports it. "Can Order" stays as is; it also needs checkout details, so it is not a minimum check. - Cart to-do items show each item's own currency, falling back to the shop's. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0135WLPbnfESWnAJmw4P8uRH --- custom_components/rohlikcz/const.py | 23 +++---------- custom_components/rohlikcz/manifest.json | 2 +- custom_components/rohlikcz/sensor.py | 7 +++- custom_components/rohlikcz/todo.py | 2 +- readme.md | 2 +- requirements_test.txt | 2 +- tests/test_init.py | 41 +++++++++++++++++++++++- 7 files changed, 54 insertions(+), 25 deletions(-) diff --git a/custom_components/rohlikcz/const.py b/custom_components/rohlikcz/const.py index cc9d5e7..7e5fa9b 100644 --- a/custom_components/rohlikcz/const.py +++ b/custom_components/rohlikcz/const.py @@ -4,35 +4,20 @@ from __future__ import annotations from aiohttp import ClientTimeout -from dataclasses import dataclass from typing import Final +from rohlik_api import SITES, Site + HTTP_TIMEOUT: Final = ClientTimeout(total=10) DOMAIN = "rohlikcz" """ Sites """ - - -@dataclass(frozen=True) -class Site: - """A Rohlík Group shop. All of them run the same backend API.""" - - base_url: str - name: str - currency: str - timezone: str - +# The shops (base URL, currency, timezone) come from rohlik-api's SITES, keyed +# by the same codes stored in config entries: cz, de, at, hu, ro. CONF_SITE = "site" DEFAULT_SITE = "cz" -SITES: Final[dict[str, Site]] = { - "cz": Site("https://www.rohlik.cz", "Rohlík.cz", "CZK", "Europe/Prague"), - "de": Site("https://www.knuspr.de", "Knuspr.de", "EUR", "Europe/Berlin"), - "at": Site("https://www.gurkerl.at", "Gurkerl.at", "EUR", "Europe/Vienna"), - "hu": Site("https://www.kifli.hu", "Kifli.hu", "HUF", "Europe/Budapest"), - "ro": Site("https://www.sezamo.ro", "Sezamo.ro", "RON", "Europe/Bucharest"), -} def get_site(key: str | None) -> Site: diff --git a/custom_components/rohlikcz/manifest.json b/custom_components/rohlikcz/manifest.json index 838b8c3..eb16d09 100644 --- a/custom_components/rohlikcz/manifest.json +++ b/custom_components/rohlikcz/manifest.json @@ -7,6 +7,6 @@ "documentation": "https://github.com/dvejsada/HA-RohlikCZ", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/dvejsada/HA-RohlikCZ/issues", - "requirements": ["rohlik-api==0.2.0"], + "requirements": ["rohlik-api==0.3.0"], "version": "1.0.0-beta3" } diff --git a/custom_components/rohlikcz/sensor.py b/custom_components/rohlikcz/sensor.py index 2260b12..5ab40da 100644 --- a/custom_components/rohlikcz/sensor.py +++ b/custom_components/rohlikcz/sensor.py @@ -1007,10 +1007,15 @@ def extra_state_attributes(self) -> Mapping[str, Any] | None: """Returns cart details.""" cart = self._rohlik_account.data.get('cart') if cart: - return { + attrs: dict[str, Any] = { "Total items": cart.total_items, "Can Order": cart.can_make_order, } + # The shop's minimum order value. "Can Order" is not a minimum + # check: it also needs checkout details such as a delivery slot. + if cart.minimum_order_price is not None: + attrs["Minimum Order Price"] = cart.minimum_order_price + return attrs return None @property diff --git a/custom_components/rohlikcz/todo.py b/custom_components/rohlikcz/todo.py index 8874cb9..63734e6 100644 --- a/custom_components/rohlikcz/todo.py +++ b/custom_components/rohlikcz/todo.py @@ -63,7 +63,7 @@ def todo_items(self) -> list[TodoItem] | None: items = [] for product in cart.products: # Format the summary to include relevant information - summary = f"{product.name} ({product.quantity}) - {product.price} {self._rohlik_hub.currency}" + summary = f"{product.name} ({product.quantity}) - {product.price} {product.currency or self._rohlik_hub.currency}" # Use cart_item_id as the unique identifier for cart items items.append( diff --git a/readme.md b/readme.md index a0776d6..cb18fc8 100644 --- a/readme.md +++ b/readme.md @@ -117,7 +117,7 @@ Enabling analytics triggers a one-time download of your full order history (this | **Credit Balance** | Your current account credit (in the shop's currency) | | **Reusable Bags** | Number of reusable bags on your account | | **Premium Days Remaining** | Days left in your premium subscription *(premium users only)* | -| **Cart Total** | Current total value of your shopping cart | +| **Cart Total** | Current total value of your shopping cart; the `Minimum Order Price` attribute holds the shop's minimum order value (compare it with the total — `Can Order` also needs checkout details such as a delivery slot) | | **Last Updated** | Timestamp of the last successful data refresh | | **Slot Express Time** | Timestamp of the next available express delivery slot | | **Slot Standard Time** | Timestamp of the nearest standard delivery slot | diff --git a/requirements_test.txt b/requirements_test.txt index f790e71..dcaffee 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,3 +1,3 @@ pytest-homeassistant-custom-component aioresponses -rohlik-api==0.2.0 +rohlik-api==0.3.0 diff --git a/tests/test_init.py b/tests/test_init.py index c5aaa7f..5cb5528 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -14,7 +14,7 @@ from homeassistant.util import dt as dt_util from pytest_homeassistant_custom_component.common import MockConfigEntry -from rohlik_api import APIRequestFailedError, InvalidCredentialsError +from rohlik_api import APIRequestFailedError, Cart, InvalidCredentialsError from custom_components.rohlikcz.const import CONF_ANALYTICS, CONF_SITE, DOMAIN from custom_components.rohlikcz.hub import OrderStore, RohlikAccount @@ -107,6 +107,45 @@ async def test_setup_without_site_is_rohlik_cz(hass: HomeAssistant) -> None: assert hass.states.get(entity_id).attributes["unit_of_measurement"] == "CZK" +async def test_cart_minimum_order_price_attribute(hass: HomeAssistant) -> None: + """The cart sensor exposes the shop's minimum order value when reported.""" + data = sample_api_data() + data["cart"] = Cart( + total_price=250.0, + total_items=1, + can_make_order=False, + products=[], + minimum_order_price=299.0, + currency="CZK", + ) + entry = _entry() + entry.add_to_hass(hass) + + with _patch_get_data(return_value=data): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + ent_reg = er.async_get(hass) + entity_id = ent_reg.async_get_entity_id("sensor", DOMAIN, "123456_cart_price") + attrs = hass.states.get(entity_id).attributes + assert attrs["Minimum Order Price"] == 299.0 + assert attrs["Can Order"] is False + + +async def test_cart_without_minimum_order_price(hass: HomeAssistant) -> None: + """No attribute when the API does not report a minimum.""" + entry = _entry() + entry.add_to_hass(hass) + + with _patch_get_data(return_value=sample_api_data()): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + ent_reg = er.async_get(hass) + entity_id = ent_reg.async_get_entity_id("sensor", DOMAIN, "123456_cart_price") + assert "Minimum Order Price" not in hass.states.get(entity_id).attributes + + async def test_czech_language_keeps_kc_unit(hass: HomeAssistant) -> None: """Czech installs keep the "Kč" unit their statistics were recorded in.""" hass.config.language = "cs" From f6f43953f49db1c03fe17ce9b07bab5d604b7141 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 06:21:56 +0000 Subject: [PATCH 2/3] Address review: import Site from rohlik_api, test shop labels and todo currency - hub.py imports Site directly from rohlik_api rather than through const. - Test that the translated shop options match rohlik_api.SITES, so a shop added in a later rohlik-api release cannot appear without a label. - Test the cart to-do item currency and its fallback to the shop's. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0135WLPbnfESWnAJmw4P8uRH --- custom_components/rohlikcz/hub.py | 4 ++-- tests/test_config_flow.py | 12 +++++++++++- tests/test_init.py | 31 ++++++++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/custom_components/rohlikcz/hub.py b/custom_components/rohlikcz/hub.py index 8ad60df..451f0c4 100644 --- a/custom_components/rohlikcz/hub.py +++ b/custom_components/rohlikcz/hub.py @@ -16,9 +16,9 @@ from homeassistant.helpers.storage import Store from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.util import dt as dt_util -from rohlik_api import InvalidCredentialsError, RohlikAPI, RohlikAPIError +from rohlik_api import InvalidCredentialsError, RohlikAPI, RohlikAPIError, Site -from .const import DOMAIN, Site, get_site +from .const import DOMAIN, get_site #: How often the integration refreshes data from the Rohlik API. UPDATE_INTERVAL = timedelta(seconds=600) diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index d6d4679..93657fd 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -1,6 +1,8 @@ """Tests for the Rohlik.cz config and reauth flows.""" from __future__ import annotations +import json +from pathlib import Path from unittest.mock import AsyncMock, patch from homeassistant.config_entries import SOURCE_USER @@ -9,7 +11,7 @@ from homeassistant.data_entry_flow import FlowResultType from pytest_homeassistant_custom_component.common import MockConfigEntry -from rohlik_api import InvalidCredentialsError, RohlikAPIError +from rohlik_api import SITES, InvalidCredentialsError, RohlikAPIError from custom_components.rohlikcz.config_flow import validate_input from custom_components.rohlikcz.const import CONF_SITE, DOMAIN @@ -317,3 +319,11 @@ async def test_reauth_wrong_account(hass: HomeAssistant) -> None: ) assert result["type"] == FlowResultType.ABORT assert result["reason"] == "wrong_account" + + +def test_every_site_has_a_translated_option() -> None: + """The shop list comes from rohlik-api; each shop needs a selector label.""" + translations = Path(__file__).parent.parent / "custom_components" / "rohlikcz" / "translations" + for path in translations.glob("*.json"): + options = json.loads(path.read_text(encoding="utf-8"))["selector"]["site"]["options"] + assert set(options) == set(SITES), path.name diff --git a/tests/test_init.py b/tests/test_init.py index 5cb5528..d56a60a 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -14,7 +14,7 @@ from homeassistant.util import dt as dt_util from pytest_homeassistant_custom_component.common import MockConfigEntry -from rohlik_api import APIRequestFailedError, Cart, InvalidCredentialsError +from rohlik_api import APIRequestFailedError, Cart, CartItem, InvalidCredentialsError from custom_components.rohlikcz.const import CONF_ANALYTICS, CONF_SITE, DOMAIN from custom_components.rohlikcz.hub import OrderStore, RohlikAccount @@ -146,6 +146,35 @@ async def test_cart_without_minimum_order_price(hass: HomeAssistant) -> None: assert "Minimum Order Price" not in hass.states.get(entity_id).attributes +async def test_cart_todo_uses_item_currency(hass: HomeAssistant) -> None: + """Cart todo items show each line's own currency, else the shop's.""" + data = sample_api_data() + data["cart"] = Cart( + total_price=3.5, + total_items=2, + can_make_order=False, + products=[ + CartItem(id="1", cart_item_id="10", name="Milk", quantity=1, price=1.5, currency="EUR"), + CartItem(id="2", cart_item_id="20", name="Bread", quantity=1, price=2.0), + ], + currency="EUR", + ) + entry = _entry() + entry.add_to_hass(hass) + + with _patch_get_data(return_value=data): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + ent_reg = er.async_get(hass) + entity_id = ent_reg.async_get_entity_id("todo", DOMAIN, "123456-cart") + response = await hass.services.async_call( + "todo", "get_items", {"entity_id": entity_id}, blocking=True, return_response=True + ) + summaries = [item["summary"] for item in response[entity_id]["items"]] + assert summaries == ["Milk (1) - 1.5 EUR", "Bread (1) - 2.0 CZK"] + + async def test_czech_language_keeps_kc_unit(hass: HomeAssistant) -> None: """Czech installs keep the "Kč" unit their statistics were recorded in.""" hass.config.language = "cs" From 2bda36c7fe2d3ce5f40a41000c9f3033311e28e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 06:21:56 +0000 Subject: [PATCH 3/3] Bump version to 1.0.0-beta4 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0135WLPbnfESWnAJmw4P8uRH --- custom_components/rohlikcz/manifest.json | 2 +- readme.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/rohlikcz/manifest.json b/custom_components/rohlikcz/manifest.json index eb16d09..0dd6823 100644 --- a/custom_components/rohlikcz/manifest.json +++ b/custom_components/rohlikcz/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_polling", "issue_tracker": "https://github.com/dvejsada/HA-RohlikCZ/issues", "requirements": ["rohlik-api==0.3.0"], - "version": "1.0.0-beta3" + "version": "1.0.0-beta4" } diff --git a/readme.md b/readme.md index cb18fc8..7bac059 100644 --- a/readme.md +++ b/readme.md @@ -13,7 +13,7 @@ Bring your **[Rohlík.cz](https://www.rohlik.cz)** grocery deliveries into Home > This integration uses a reverse-engineered API from the Rohlík.cz website. It is **not** officially supported by Rohlík.cz. Use it at your own risk. > [!NOTE] -> The current release is **1.0.0-beta3**, a pre-release of the upcoming 1.0.0. It is feature-complete but still being tested — see [Installing the beta](#installing-the-beta) below, and please [report anything you run into](https://github.com/dvejsada/HA-RohlikCZ/issues). +> The current release is **1.0.0-beta4**, a pre-release of the upcoming 1.0.0. It is feature-complete but still being tested — see [Installing the beta](#installing-the-beta) below, and please [report anything you run into](https://github.com/dvejsada/HA-RohlikCZ/issues). --- @@ -46,7 +46,7 @@ HACS hides pre-releases by default, so the 1.0.0 beta will not show up until you 1. Open **Rohlík.cz** in HACS. 2. Click **⋮ → Redownload**. 3. Enable **Show beta versions**. -4. Pick `v1.0.0-beta3` and download it. +4. Pick `v1.0.0-beta4` and download it. 5. Restart Home Assistant. ### Option 2 — Manual Installation