feat(trading): support multi-item purchase intents
This commit is contained in:
@@ -11,6 +11,7 @@ clear_cart 与 _dump_debug_snapshot 用不依赖 Playwright 的 fake page 覆盖
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -39,6 +40,7 @@ from app.trading.worker.site_interact import (
|
||||
_DELETE_BUTTON_SELECTOR,
|
||||
_extract_error_message,
|
||||
_extract_purchase_fields,
|
||||
_normalize_intent_items,
|
||||
_OrderListAccumulator,
|
||||
_parse_checkout_summary,
|
||||
_parse_initial_state,
|
||||
@@ -70,6 +72,82 @@ def _wrap_state(state: dict[str, Any]) -> str:
|
||||
)
|
||||
|
||||
|
||||
# ---- 下单意图兼容 ----
|
||||
|
||||
|
||||
def test_normalize_intent_items_accepts_legacy_single_item():
|
||||
assert _normalize_intent_items({
|
||||
"item_url": "https://item.rakuten.co.jp/shop/x/",
|
||||
"quantity": 2,
|
||||
}) == [{
|
||||
"item_url": "https://item.rakuten.co.jp/shop/x/",
|
||||
"quantity": 2,
|
||||
"variant_id": None,
|
||||
"choice": None,
|
||||
}]
|
||||
|
||||
|
||||
def test_normalize_intent_items_accepts_multiple_items_and_string_urls():
|
||||
assert _normalize_intent_items({
|
||||
"items": [
|
||||
{"item_url": "https://item.rakuten.co.jp/shop/x/", "quantity": 2},
|
||||
"https://item.rakuten.co.jp/shop/y/",
|
||||
]
|
||||
}) == [
|
||||
{
|
||||
"item_url": "https://item.rakuten.co.jp/shop/x/",
|
||||
"quantity": 2,
|
||||
"variant_id": None,
|
||||
"choice": None,
|
||||
},
|
||||
{
|
||||
"item_url": "https://item.rakuten.co.jp/shop/y/",
|
||||
"quantity": 1,
|
||||
"variant_id": None,
|
||||
"choice": None,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def test_normalize_intent_items_rejects_empty_items():
|
||||
with pytest.raises(InvalidRequestError):
|
||||
_normalize_intent_items({"items": []})
|
||||
|
||||
|
||||
async def test_add_to_cart_processes_all_items_and_keeps_legacy_state():
|
||||
site = SiteInteractor.__new__(SiteInteractor)
|
||||
site._lock = asyncio.Lock()
|
||||
site._per_task_state = {}
|
||||
calls: list[dict[str, Any]] = []
|
||||
|
||||
async def fake_add(**kwargs):
|
||||
calls.append(kwargs)
|
||||
index = len(calls)
|
||||
return {
|
||||
"item_id": str(index),
|
||||
"shop_bid": "shop",
|
||||
"basket_domain": "https://basket",
|
||||
"response_html": f"html-{index}",
|
||||
"screenshot": b"",
|
||||
}
|
||||
|
||||
site._add_to_cart_with_fields = fake_add
|
||||
snapshot = await site.add_to_cart(_make_task(intent={
|
||||
"items": [
|
||||
{"item_url": "https://item.rakuten.co.jp/shop/x/"},
|
||||
{"item_url": "https://item.rakuten.co.jp/shop/y/", "quantity": 3},
|
||||
]
|
||||
}))
|
||||
assert [call["item_url"] for call in calls] == [
|
||||
"https://item.rakuten.co.jp/shop/x/",
|
||||
"https://item.rakuten.co.jp/shop/y/",
|
||||
]
|
||||
assert calls[1]["quantity"] == 3
|
||||
assert site._per_task_state["t1"]["item_id"] == "1"
|
||||
assert site._per_task_state["t1"]["item_ids"] == ["1", "2"]
|
||||
assert snapshot.html == "html-2"
|
||||
|
||||
|
||||
# ---- _parse_initial_state ----
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user