Files
gyxx-flow/tests/test_scheduler_cli.py
T
wangyunlong 622a609800 feat(product-commerce): 系列聚合去重与采集开关两层语义
配置来源收敛
- 系列平台编码 tm/jd/dy/jd_self 与 9 类飞书地址改为整系列并集去重后挂锚点,
  子款编码保留在 series.platform_ids 备查;多表冲突保留最高优先级并告警
- 写表脚本(当日/天猫主图/京东主图)去掉本地 JSON 回退,地址缺失或读取失败
  直接 fail closed,避免库与表地址不一致被静默掩盖
- 测试注入 records_provider 时禁用配置缓存读写,防止假数据写入生产快照

采集开关(dim_style.collect_enabled)
- 锚点为系列总开关:false 则整系列不采
- 锚点为 true 时,子款自身 false 仅把自己的 ERP 编码排除出聚合,其余子款照常
- 排除结果记录在 series.erp_excluded 并在运行时提示

聚水潭全店铺日报防错
- 列布局 fail closed、行 td 数校验、满页判定 + 总页数兜底、值域交叉校验
- 新增单日总量闸:超近 14 天中位数 8 倍拒绝落库

其他
- 新增电商费用归集配置 ad_cost_rollup.json 与积分兑换周报采集
- 店铺情报、供应链到货同步、通知路由与相关配置/文档同步更新
- 忽略本地 dsh workbench 脚手架(含 node_modules)
2026-10-30 11:49:31 +08:00

78 lines
2.7 KiB
Python

from __future__ import annotations
import io
import json
from pathlib import Path
from gyxx_flow.catalog import WorkflowCatalog
from gyxx_flow.cli import EXIT_USAGE, build_parser, main
from gyxx_flow.core.config import Settings
from gyxx_flow.workflow import WorkflowDefinition
from gyxx_flow.workflow.registry import WorkflowRegistry
PROJECT_ROOT = Path(__file__).resolve().parents[1]
def test_schedule_cli_exposes_only_project_owned_service_commands() -> None:
parser = build_parser()
command_action = next(action for action in parser._actions if action.dest == "command")
schedule_parser = command_action.choices["schedule"]
schedule_action = next(
action for action in schedule_parser._actions if action.dest == "schedule_command"
)
assert set(schedule_action.choices) == {"run", "status"}
def test_run_rejects_implicit_scheduled_execution(tmp_path: Path) -> None:
catalog = WorkflowCatalog.load(PROJECT_ROOT / "config")
registry = WorkflowRegistry(catalog)
registry.register(WorkflowDefinition("product.daily", ()))
output = io.StringIO()
exit_code = main(
["run", "product.daily", "--scheduled"],
registry=registry,
settings=Settings(project_root=PROJECT_ROOT, data_root=tmp_path),
stdout=output,
stderr=io.StringIO(),
)
assert exit_code == EXIT_USAGE
assert output.getvalue() == ""
def test_python_scheduler_status_is_project_owned_and_cross_platform(tmp_path: Path) -> None:
output = io.StringIO()
settings = Settings(project_root=PROJECT_ROOT, data_root=tmp_path / "data")
exit_code = main(
["schedule", "status"], settings=settings,
stdout=output, stderr=io.StringIO(),
)
assert exit_code == 0
payload = json.loads(output.getvalue())
assert payload["timezone"] == "Asia/Shanghai"
assert payload["scheduled_count"] == 40
assert payload["state"]["schema_version"] == 1
def test_python_scheduler_dry_run_never_launches_or_writes_state(tmp_path: Path) -> None:
output = io.StringIO()
settings = Settings(project_root=PROJECT_ROOT, data_root=tmp_path / "data")
exit_code = main(
["schedule", "run", "--dry-run", "--once", "--misfire-grace-seconds", "3456000"],
settings=settings, stdout=output, stderr=io.StringIO(),
)
assert exit_code == 0
payload = json.loads(output.getvalue())
assert payload["mode"] == "dry-run"
assert "content.metrics.daily" in payload["due_or_started"]
assert "content.metrics.backfill" not in payload["due_or_started"]
assert "content.relogin.weekly" not in payload["due_or_started"]
assert "product.market_rank" in payload["due_or_started"]
assert not (settings.data_root / "state" / "scheduler" / "state.json").exists()