104 lines
3.7 KiB
Python
104 lines
3.7 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from gyxx_flow.adapters.native import DeferredModuleCommandStep
|
|
from gyxx_flow.catalog import WorkflowCatalog
|
|
from gyxx_flow.modules.shop_intelligence import (
|
|
COLLECTION_TIMEOUT_SECONDS,
|
|
SHOP_RESOURCE,
|
|
SHOP_WORKFLOW_IDS,
|
|
ShopIntelligenceModule,
|
|
)
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def test_shop_module_registers_all_catalog_workflows() -> None:
|
|
module = ShopIntelligenceModule.from_catalog(
|
|
WorkflowCatalog.load(PROJECT_ROOT / "config")
|
|
)
|
|
|
|
assert tuple(item.workflow_id for item in module.workflow_definitions()) == SHOP_WORKFLOW_IDS
|
|
|
|
|
|
def test_shop_workflows_use_project_owned_module_commands() -> None:
|
|
module = ShopIntelligenceModule.from_catalog(
|
|
WorkflowCatalog.load(PROJECT_ROOT / "config")
|
|
)
|
|
|
|
for definition in module.workflow_definitions():
|
|
assert definition.steps
|
|
for step in definition.steps:
|
|
assert isinstance(step.action, DeferredModuleCommandStep)
|
|
if definition.workflow_id == "shop.jd_self_operated.daily":
|
|
expected_timeout = {"brand": 600, "product": 1_800}[step.step_id]
|
|
elif definition.workflow_id == "shop.douyin_price_appeal":
|
|
expected_timeout = 1_800
|
|
else:
|
|
expected_timeout = COLLECTION_TIMEOUT_SECONDS
|
|
assert step.timeout_seconds == expected_timeout
|
|
assert step.max_attempts == 1
|
|
assert step.resources == (f"{SHOP_RESOURCE}:{step.step_id}",)
|
|
assert step.production_sink is True
|
|
|
|
|
|
def test_shop_metrics_platforms_are_independent_graph_steps() -> None:
|
|
module = ShopIntelligenceModule.from_catalog(
|
|
WorkflowCatalog.load(PROJECT_ROOT / "config")
|
|
)
|
|
metrics = next(
|
|
item
|
|
for item in module.workflow_definitions()
|
|
if item.workflow_id == "shop.metrics.weekly"
|
|
)
|
|
|
|
assert tuple(step.step_id for step in metrics.steps) == ("jd", "dy", "tm")
|
|
assert all(not step.depends_on for step in metrics.steps)
|
|
|
|
|
|
def test_douyin_price_appeal_reuses_weekly_browser_resource() -> None:
|
|
module = ShopIntelligenceModule.from_catalog(
|
|
WorkflowCatalog.load(PROJECT_ROOT / "config")
|
|
)
|
|
definitions = {item.workflow_id: item for item in module.workflow_definitions()}
|
|
metrics_douyin = next(
|
|
step for step in definitions["shop.metrics.weekly"].steps if step.step_id == "dy"
|
|
)
|
|
appeal = definitions["shop.douyin_price_appeal"].steps[0]
|
|
|
|
assert appeal.step_id == "dy"
|
|
assert appeal.action.command_entry == (
|
|
"collectors/dy_store_competitor_store_scraping.py"
|
|
)
|
|
assert appeal.action.command_args == ("--price-appeal", "--execute")
|
|
assert appeal.resources == metrics_douyin.resources == (
|
|
f"{SHOP_RESOURCE}:dy",
|
|
)
|
|
assert appeal.replay_policy == "idempotent"
|
|
|
|
|
|
def test_jd_self_operated_preserves_source_runner_graph_contract() -> None:
|
|
module = ShopIntelligenceModule.from_catalog(
|
|
WorkflowCatalog.load(PROJECT_ROOT / "config")
|
|
)
|
|
workflow = next(
|
|
item
|
|
for item in module.workflow_definitions()
|
|
if item.workflow_id == "shop.jd_self_operated.daily"
|
|
)
|
|
brand, product = workflow.steps
|
|
|
|
assert (brand.step_id, product.step_id) == ("brand", "product")
|
|
assert brand.action.command_entry == (
|
|
"collectors/jd_self_operated_brand_daily.py"
|
|
)
|
|
assert product.action.command_entry == (
|
|
"collectors/jd_self_operated_product_daily.py"
|
|
)
|
|
assert brand.timeout_seconds == 600
|
|
assert product.timeout_seconds == 1_800
|
|
assert brand.depends_on == ()
|
|
assert product.depends_on == ("brand",)
|
|
assert product.run_after_failure is True
|