diff --git a/scripts/fill_redefine_cart.py b/scripts/fill_redefine_cart.py index 595a5fc..7ba2bd1 100644 --- a/scripts/fill_redefine_cart.py +++ b/scripts/fill_redefine_cart.py @@ -104,65 +104,7 @@ def _fill(plan: FillPlan, menu_url: str) -> int: return 1 page = browser.new_page() try: - page.goto(menu_url, wait_until="domcontentloaded", timeout=60000) - cart = _cart_request(page, "GET", "/api/cart") - existing = cart_lines(cart) - if existing: - print("The cart already has items:") - for line in existing: - print(f" {line.name} x{line.quantity}") - if not _confirm_clear(): - print("Exiting without adding.") - return 1 - cleared = _cart_request(page, "POST", "/api/cart/clear", {}) - if cart_lines(cleared): - print( - "The cart still has items after clear. Exiting without adding." - ) - return 1 - - failed: list[str] = [] - posted = [] - for item in plan.adds: - try: - _cart_request( - page, - "POST", - "/api/cart/add", - { - "uuid": item.uuid, - "quantity": item.quantity, - "properties": None, - }, - ) - except RuntimeError as exc: - failed.append(f"{item.name}: {exc}") - print(f"Failed: {item.name}: {exc}") - continue - posted.append(item) - - added = 0 - try: - current = cart_lines(_cart_request(page, "GET", "/api/cart")) - except RuntimeError as exc: - failed.append(f"Could not read the cart: {exc}") - print(f"Failed: Could not read the cart: {exc}") - else: - for item in posted: - found = quantity_for(current, item.uuid) - if found != item.quantity: - message = ( - f"{item.name}: requested {item.quantity}, cart has {found}" - ) - failed.append(message) - print(f"Failed: {message}") - else: - added += 1 - origin = _origin(page.url or menu_url) - page.goto(f"{origin}/cart", wait_until="domcontentloaded", timeout=60000) - print(f"Added {added}. Failed {len(failed)}. Skipped {len(plan.skipped)}.") - _wait_for_review(page) - return 1 if failed or plan.skipped else 0 + return _fill_page(page, plan, menu_url) finally: try: browser.close() @@ -170,6 +112,73 @@ def _fill(plan: FillPlan, menu_url: str) -> int: pass +def _fill_page(page, plan: FillPlan, menu_url: str) -> int: + failed: list[str] = [] + added = 0 + ready = False + try: + page.goto(menu_url, wait_until="domcontentloaded", timeout=60000) + cart = _cart_request(page, "GET", "/api/cart") + existing = cart_lines(cart) + if existing: + print("The cart already has items:") + for line in existing: + print(f" {line.name} x{line.quantity}") + if not _confirm_clear(): + print("Exiting without adding.") + return 1 + cleared = _cart_request(page, "POST", "/api/cart/clear", {}) + if cart_lines(cleared): + print("The cart still has items after clear. Exiting without adding.") + return 1 + + posted = [] + for item in plan.adds: + try: + _cart_request( + page, + "POST", + "/api/cart/add", + { + "uuid": item.uuid, + "quantity": item.quantity, + "properties": None, + }, + ) + except RuntimeError as exc: + failed.append(f"{item.name}: {exc}") + print(f"Failed: {item.name}: {exc}") + continue + posted.append(item) + + try: + current = cart_lines(_cart_request(page, "GET", "/api/cart")) + except RuntimeError as exc: + failed.append(f"Could not read the cart: {exc}") + print(f"Failed: Could not read the cart: {exc}") + else: + for item in posted: + found = quantity_for(current, item.uuid) + if found != item.quantity: + message = ( + f"{item.name}: requested {item.quantity}, cart has {found}" + ) + failed.append(message) + print(f"Failed: {message}") + else: + added += 1 + origin = _origin(page.url or menu_url) + page.goto(f"{origin}/cart", wait_until="domcontentloaded", timeout=60000) + ready = True + except (RuntimeError, PlaywrightError) as exc: + failed.append(str(exc)) + print(f"Failed: {exc}") + print(f"Added {added}. Failed {len(failed)}. Skipped {len(plan.skipped)}.") + if ready: + _wait_for_review(page) + return 1 if failed or plan.skipped else 0 + + def _cart_request(page, method: str, path: str, body: object | None = None) -> object: result = page.evaluate(_CART_JS, {"method": method, "path": path, "body": body}) if not isinstance(result, dict) or not result.get("ok"): diff --git a/tests/test_fill_cart.py b/tests/test_fill_cart.py index 1edc16f..a9b4038 100644 --- a/tests/test_fill_cart.py +++ b/tests/test_fill_cart.py @@ -1,8 +1,19 @@ """CSV parsing and menu matching for the Redefine cart filler.""" +import importlib.util +from pathlib import Path + +from playwright.sync_api import Error as PlaywrightError + from scraper.fill_cart import build_plan, cart_lines, error_text, quantity_for from scraper.parse_menu import extract_catalog_products +_SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "fill_redefine_cart.py" +_spec = importlib.util.spec_from_file_location("fill_redefine_cart", _SCRIPT) +fill_redefine_cart = importlib.util.module_from_spec(_spec) +assert _spec.loader is not None +_spec.loader.exec_module(fill_redefine_cart) + PRODUCTS = [ { "name": "Korean Steak Bowl", @@ -123,6 +134,63 @@ def test_cart_lines_use_the_product_uuid_and_quantity(): assert quantity_for(lines, "line-1") == 0 +class _CartPage: + def __init__(self, responses): + self._responses = list(responses) + + def goto(self, url, **kwargs): + return None + + def evaluate(self, script, payload): + response = self._responses.pop(0) + if isinstance(response, Exception): + raise response + return response + + +def test_fill_page_prints_a_summary_when_the_window_closes_mid_add(capsys): + plan = build_plan("Item,Quantity\nKorean Steak Bowl,1\n", PRODUCTS) + page = _CartPage( + [ + {"ok": True, "status": 200, "data": {"items": []}}, + PlaywrightError("Target page, context or browser has been closed"), + ] + ) + + code = fill_redefine_cart._fill_page( + page, plan, "https://www.redefinemeals.com/menu" + ) + output = capsys.readouterr().out + + assert code == 1 + assert "Traceback" not in output + assert "Added 0. Failed 1. Skipped 0." in output + assert "Cart is ready" not in output + + +def test_fill_page_prints_a_summary_when_the_first_cart_read_fails(capsys): + plan = build_plan("Item,Quantity\nKorean Steak Bowl,1\n", PRODUCTS) + page = _CartPage( + [ + { + "ok": False, + "status": 500, + "data": {"message": "cart unavailable", "trace": [{"file": "x"}]}, + } + ] + ) + + code = fill_redefine_cart._fill_page( + page, plan, "https://www.redefinemeals.com/menu" + ) + output = capsys.readouterr().out + + assert code == 1 + assert "HTTP 500: cart unavailable" in output + assert "x" not in output.split("Failed:", 1)[-1] + assert "Added 0. Failed 1. Skipped 0." in output + + def test_error_text_keeps_the_message_and_drops_the_trace(): body = { "message": "Product is not available.",