feat(crawler): expand pixelrag marketplace automation
This commit is contained in:
@@ -69,6 +69,32 @@ def test_pchome_search_scans_multiple_pages_until_limit(monkeypatch):
|
||||
assert [product.product_id for product in products] == ["A001", "A002", "A003"]
|
||||
|
||||
|
||||
def test_pchome_search_auto_queues_pixelrag_manifest_on_empty_result(monkeypatch, tmp_path):
|
||||
from services import pixelrag_crawler_integration_service as pixelrag
|
||||
from services.pchome_crawler import PChomeCrawler
|
||||
|
||||
monkeypatch.setattr(pixelrag, "DEFAULT_MANIFEST_QUEUE_ROOT", str(tmp_path))
|
||||
crawler = PChomeCrawler(timeout=1, delay=0, max_retries=0)
|
||||
|
||||
class FakeSession:
|
||||
headers = {}
|
||||
|
||||
def get(self, url, params=None, timeout=None):
|
||||
return _FakeResponse({"Prods": []})
|
||||
|
||||
crawler.session = FakeSession()
|
||||
|
||||
success, message, products = crawler.search_products("找不到的商品", limit=3, max_pages=1)
|
||||
|
||||
assert success is False
|
||||
assert message == "沒有找到符合的商品"
|
||||
assert products == []
|
||||
action = crawler.last_visual_evidence_action
|
||||
assert action["status"] == "manifest_persisted"
|
||||
assert action["manifest"]["capture_target"]["crawler"] == "PChomeCrawler.search_products"
|
||||
assert action["persistence"]["path"].startswith(str(tmp_path))
|
||||
|
||||
|
||||
def test_pchome_get_retries_transient_timeout():
|
||||
from services.pchome_crawler import PChomeCrawler
|
||||
|
||||
|
||||
@@ -124,6 +124,67 @@ def test_visual_fallback_selector_routes_parser_empty_and_low_confidence_cases()
|
||||
assert clean_parser["fallback_reason"] == "structured_evidence_sufficient"
|
||||
|
||||
|
||||
def test_pixelrag_ecommerce_expansion_plan_includes_shopee_and_coupang():
|
||||
from services.pixelrag_crawler_integration_service import (
|
||||
ECOMMERCE_EXPANSION_POLICY,
|
||||
build_pixelrag_ecommerce_expansion_plan,
|
||||
)
|
||||
|
||||
payload = build_pixelrag_ecommerce_expansion_plan()
|
||||
platforms = {item["platform"]: item for item in payload["platforms"]}
|
||||
|
||||
assert payload["policy"] == ECOMMERCE_EXPANSION_POLICY
|
||||
assert payload["status"] == "marketplace_profiles_ready"
|
||||
assert platforms["shopee_tw"]["manifest_ready"] is True
|
||||
assert platforms["coupang_tw"]["manifest_ready"] is True
|
||||
assert "price" in platforms["shopee_tw"]["data_targets"]
|
||||
assert payload["next_actions"][0]["target_platforms"] == ["shopee_tw", "coupang_tw"]
|
||||
assert payload["controlled_apply"]["production_price_write"] is False
|
||||
|
||||
|
||||
def test_marketplace_search_manifest_builds_shopee_visual_capture_target():
|
||||
from services.pixelrag_crawler_integration_service import build_pixelrag_marketplace_search_manifest
|
||||
|
||||
payload = build_pixelrag_marketplace_search_manifest(
|
||||
platform="shopee_tw",
|
||||
keyword="防曬乳",
|
||||
page_size={"width": 1440, "height": 1024},
|
||||
tile_size={"width": 512, "height": 512},
|
||||
)
|
||||
|
||||
assert payload["success"] is True
|
||||
assert payload["capture_target"]["platform"] == "shopee_tw"
|
||||
assert payload["capture_target"]["url"].startswith("https://shopee.tw/search?keyword=")
|
||||
assert "%E9%98%B2%E6%9B%AC%E4%B9%B3" in payload["capture_target"]["url"]
|
||||
assert payload["marketplace_profile"]["display_name"] == "Shopee Taiwan"
|
||||
assert payload["tile_plan"]["tile_count"] == 6
|
||||
|
||||
|
||||
def test_crawler_failure_action_persists_manifest_queue(tmp_path):
|
||||
from services.pixelrag_crawler_integration_service import build_pixelrag_crawler_failure_action
|
||||
|
||||
payload = build_pixelrag_crawler_failure_action(
|
||||
url="https://24h.pchome.com.tw/search/?q=test",
|
||||
platform="pchome",
|
||||
crawler="PChomeCrawler.search_products",
|
||||
parser_success=False,
|
||||
parsed_item_count=0,
|
||||
missing_fields=["price", "product_id"],
|
||||
failure_reason="search API returned no product IDs",
|
||||
persist_manifest=True,
|
||||
queue_root=tmp_path,
|
||||
)
|
||||
|
||||
assert payload["success"] is True
|
||||
assert payload["status"] == "manifest_persisted"
|
||||
assert payload["next_machine_action"] == "run_pixelrag_visual_capture_worker"
|
||||
assert payload["controlled_apply"]["artifact_write"] is True
|
||||
manifest_path = payload["persistence"]["path"]
|
||||
queued = json.loads(open(manifest_path, encoding="utf-8").read())
|
||||
assert queued["capture_target"]["platform"] == "pchome"
|
||||
assert queued["queue_policy"] == "read_only_pixelrag_crawler_failure_action_v1"
|
||||
|
||||
|
||||
def test_visual_evidence_manifest_builds_tile_plan_without_runtime_writes():
|
||||
from services.pixelrag_crawler_integration_service import (
|
||||
RESULT_MANIFEST_READY,
|
||||
@@ -226,6 +287,17 @@ def test_pixelrag_ai_automation_route_returns_assessment_and_manifest():
|
||||
):
|
||||
manifest_response = routes.ai_automation_pixelrag_crawler_integration_api.__wrapped__()
|
||||
manifest = manifest_response.get_json()
|
||||
with app.test_request_context(
|
||||
"/api/ai-automation/pixelrag-crawler-integration?expansion_plan=true"
|
||||
):
|
||||
expansion_response = routes.ai_automation_pixelrag_crawler_integration_api.__wrapped__()
|
||||
expansion = expansion_response.get_json()
|
||||
with app.test_request_context(
|
||||
"/api/ai-automation/pixelrag-crawler-integration"
|
||||
"?platform=shopee_tw&marketplace_keyword=防曬乳"
|
||||
):
|
||||
marketplace_response = routes.ai_automation_pixelrag_crawler_integration_api.__wrapped__()
|
||||
marketplace = marketplace_response.get_json()
|
||||
|
||||
assert assessment["policy"] == "read_only_pixelrag_crawler_integration_assessment_v1"
|
||||
assert assessment["target_platforms"] == ["momo"]
|
||||
@@ -233,6 +305,10 @@ def test_pixelrag_ai_automation_route_returns_assessment_and_manifest():
|
||||
assert manifest["policy"] == "read_only_pixelrag_visual_evidence_manifest_v1"
|
||||
assert manifest["result"] == "PIXELRAG_VISUAL_EVIDENCE_MANIFEST_READY"
|
||||
assert manifest["capture_target"]["platform"] == "pchome"
|
||||
assert expansion["policy"] == "read_only_pixelrag_ecommerce_expansion_plan_v1"
|
||||
assert expansion["next_actions"][0]["target_platforms"] == ["shopee_tw", "coupang_tw"]
|
||||
assert marketplace["capture_target"]["platform"] == "shopee_tw"
|
||||
assert marketplace["marketplace_profile"]["keyword"] == "防曬乳"
|
||||
|
||||
|
||||
def _write_capture_receipt(root, *, platform="pchome", manifest_id="manifest-1"):
|
||||
@@ -303,6 +379,30 @@ def test_pixelrag_visual_evidence_readback_reports_latest_receipt(tmp_path):
|
||||
assert payload["controlled_apply"]["db_write"] is False
|
||||
|
||||
|
||||
def test_pixelrag_visual_evidence_readback_warns_on_access_denied_page(tmp_path):
|
||||
from services.pixelrag_crawler_integration_service import build_pixelrag_visual_evidence_readback
|
||||
|
||||
receipt_path = _write_capture_receipt(tmp_path, platform="coupang_tw", manifest_id="denied")
|
||||
receipt = json.loads(receipt_path.read_text(encoding="utf-8"))
|
||||
receipt["http_status"] = 403
|
||||
receipt["page_metrics"] = {
|
||||
"final_url": "https://www.tw.coupang.com/search?q=iphone",
|
||||
"title": "Access Denied",
|
||||
}
|
||||
receipt_path.write_text(json.dumps(receipt), encoding="utf-8")
|
||||
|
||||
payload = build_pixelrag_visual_evidence_readback(
|
||||
artifact_root=tmp_path,
|
||||
platform="coupang_tw",
|
||||
manifest_id="denied",
|
||||
)
|
||||
|
||||
assert payload["status"] == "warning"
|
||||
assert payload["summary"]["visual_barrier_detected"] is True
|
||||
assert payload["summary"]["visual_barrier_reason"] == "http_status_403"
|
||||
assert payload["next_machine_action"] == "run_platform_probe_or_use_structured_api"
|
||||
|
||||
|
||||
def test_pixelrag_visual_evidence_readback_warns_when_missing(tmp_path):
|
||||
from services.pixelrag_crawler_integration_service import build_pixelrag_visual_evidence_readback
|
||||
|
||||
@@ -467,3 +567,34 @@ def test_pixelrag_python_capture_worker_dry_run_plans_artifact_outputs(tmp_path)
|
||||
assert payload["controlled_apply"]["artifact_write"] is False
|
||||
assert payload["tile_plan"]["planned_tile_count"] == 4
|
||||
assert payload["capture_target"]["platform"] == "pchome"
|
||||
|
||||
|
||||
def test_pixelrag_python_capture_worker_accepts_shopee_manifest(tmp_path):
|
||||
from services.pixelrag_crawler_integration_service import build_pixelrag_marketplace_search_manifest
|
||||
|
||||
manifest = build_pixelrag_marketplace_search_manifest(
|
||||
platform="shopee_tw",
|
||||
keyword="防曬乳",
|
||||
page_size={"width": 1024, "height": 1024},
|
||||
tile_size={"width": 512, "height": 512},
|
||||
)
|
||||
completed = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"scripts/ops/capture_pixelrag_visual_evidence.py",
|
||||
"--manifest-json",
|
||||
json.dumps(manifest),
|
||||
"--output-dir",
|
||||
str(tmp_path),
|
||||
"--dry-run",
|
||||
],
|
||||
capture_output=True,
|
||||
check=False,
|
||||
text=True,
|
||||
)
|
||||
|
||||
assert completed.returncode == 0
|
||||
payload = json.loads(completed.stdout)
|
||||
assert payload["status"] == "dry_run_ready"
|
||||
assert payload["capture_target"]["platform"] == "shopee_tw"
|
||||
assert payload["tile_plan"]["planned_tile_count"] == 4
|
||||
|
||||
Reference in New Issue
Block a user