- tests/core: 新增 BaseHttpClient 统一 HTTP 封装(直连绕行/超时/日志/自定义编码); db_helper 追加 Db 长连接类(事务/mogrify/自动重连),既有函数不动 - tests/apis/yun8: 21 个 step 方法按域拆分为 5 个 Mixin(driver/accounting/ audit_payment/receipt/third_party),Yun8Pipeline 24 步编排原样迁移, SQL 全部参数化并集中至 sql_templates - tests/api: 5 阶段有序 P0 E2E 用例(业务失败由 print 改为真实报错)+ README 迁移(删除明文密码段,修正步骤编号漂移) - yun8_sync 目录退役:配置并入 test_config.yaml 的 yun8_api 段; 造数 CLI 保留(python -m tests.apis.yun8.cli,兼容旧 config.yaml 结构) - pytest.ini 注册 api/yun8_sync marker;requirements.txt 补 requests>=2.28 - 静态验证: compileall / 全仓 collect 24 条 / marker 过滤 / CLI --help / 客户端与 pipeline 离线实例化 / SQL 占位符核对 / cross_end 兼容
205 lines
7.3 KiB
Python
205 lines
7.3 KiB
Python
"""yun8 运输流程造数 CLI。
|
|
|
|
旧 yun8_sync/main.py 的等价入口,复用 tests/apis/yun8 的 API 层与编排层:
|
|
python -m tests.apis.yun8.cli --stage all --repeat 10 --delay 5
|
|
|
|
注意:
|
|
- --dry-run 仅跳过回单/ETC 的 SQL 写入;HTTP 步骤全部真实执行,
|
|
driver 阶段的 reset SQL 也不受 dry-run 影响(与旧工具行为一致)。
|
|
- 数据库连接读取 tests/config/test_config.yaml 的 database 段。
|
|
|
|
阶段选择:
|
|
all 完整流程 (默认): 司机端 → 核算 → 付款 → 回单SQL → 上报
|
|
driver 仅司机端 (步骤 1-5)
|
|
accounting 仅运费核算 (步骤 6-9)
|
|
payment 仅审核付款 (步骤 10-16)
|
|
receipt 仅回单文件+SQL+上报 (步骤 14-17)
|
|
"""
|
|
|
|
import argparse
|
|
import sys
|
|
import time
|
|
import traceback
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
from tests.core.config_loader import load_config
|
|
from tests.core.db_helper import Db
|
|
|
|
from tests.apis.yun8.pipeline import Yun8Pipeline
|
|
|
|
STAGE_HELP = """阶段选择:
|
|
all 完整流程 (默认): 司机端 → 核算 → 付款 → 回单SQL → 上报
|
|
driver 仅司机端 (步骤 1-5)
|
|
accounting 仅运费核算 (步骤 6-9)
|
|
payment 仅审核付款 (步骤 10-16)
|
|
receipt 仅回单文件+SQL+上报 (步骤 14-17)
|
|
"""
|
|
|
|
|
|
def _load_yun8_section(path: str | Path | None) -> dict:
|
|
"""加载 yun8_api 配置段。
|
|
|
|
- path 为空: 读 tests/config/test_config.yaml 的 yun8_api 段
|
|
- path 指向的 YAML 含 yun8_api 键: 直接取该段
|
|
- 否则按旧 yun8_sync/config.yaml 结构兼容解析(database 段忽略)
|
|
"""
|
|
if path is None:
|
|
return load_config()["yun8_api"]
|
|
|
|
with open(path, "r", encoding="utf-8") as fh:
|
|
raw = yaml.safe_load(fh)
|
|
|
|
if "yun8_api" in raw:
|
|
return raw["yun8_api"]
|
|
|
|
# 旧版结构兼容: api/auth/driver_params/... 平铺
|
|
api = raw.get("api", {})
|
|
section = {
|
|
"base_url": api.get("base_url", ""),
|
|
"verify_ssl": api.get("verify_ssl", False),
|
|
"timeout_seconds": api.get("timeout_seconds", 60),
|
|
"common_headers": api.get("common_headers", {}),
|
|
"driver_headers": api.get("driver", {}),
|
|
"admin_headers": api.get("admin", {}),
|
|
"tokens": dict(raw.get("auth", {})),
|
|
}
|
|
for key in ("driver_params", "accounting", "payment", "etc_invoice",
|
|
"sql", "dianyuntong"):
|
|
if key in raw:
|
|
section[key] = raw[key]
|
|
return section
|
|
|
|
|
|
def run_one_cycle(section: dict, stage: str, driver_token: str,
|
|
admin_token: str, dry_run: bool) -> bool:
|
|
"""执行一次完整流水线,返回 True 表示成功(异常直接向上抛)。"""
|
|
db = Db()
|
|
pipe = Yun8Pipeline(section, db)
|
|
try:
|
|
pipe.set_auth(driver_token, admin_token)
|
|
|
|
if stage in ("all", "driver"):
|
|
pipe.run_driver_flow()
|
|
|
|
# reset_order_detail 模式:仅跑司机端,跳过后续全部步骤
|
|
dp = section.get("driver_params", {})
|
|
if dp.get("reset_order_detail", False):
|
|
print(f"\n [stop] reset_order_detail=true,跳过步骤 06-21")
|
|
return True
|
|
|
|
if stage in ("all", "accounting"):
|
|
pipe.run_accounting_flow()
|
|
|
|
if stage in ("all", "payment"):
|
|
pipe.run_audit_payment_flow()
|
|
|
|
ac = section.get("accounting", {})
|
|
if ac.get("execute_after_audit", True):
|
|
if stage in ("all", "receipt"):
|
|
pipe.run_receipt_file_flow(dry_run=dry_run)
|
|
# ETC 开票 SQL 写入 (步骤 22-23)
|
|
pipe.run_etc_invoice_flow(dry_run=dry_run)
|
|
else:
|
|
if stage in ("all", "receipt"):
|
|
print(f"\n [stop] execute_after_audit=false,跳过 receipt 阶段")
|
|
|
|
return True
|
|
finally:
|
|
pipe.close()
|
|
db.close()
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="yun8 运输流程完整自动化",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog=STAGE_HELP,
|
|
)
|
|
parser.add_argument("--config", default=None,
|
|
help="配置文件路径 (默认: tests/config/test_config.yaml 的 yun8_api 段)")
|
|
parser.add_argument("--auth-driver", default="",
|
|
help="司机端 Bearer Token (含 'Bearer ' 前缀)")
|
|
parser.add_argument("--auth-admin", default="",
|
|
help="管理端 Bearer Token (含 'Bearer ' 前缀)")
|
|
parser.add_argument("--stage", default="all",
|
|
choices=["all", "driver", "accounting", "payment", "receipt"],
|
|
help="运行阶段 (默认: all)")
|
|
parser.add_argument("--dry-run", action="store_true",
|
|
help="仅预览 SQL,不实际写入数据库 (注意: HTTP 步骤仍真实执行)")
|
|
parser.add_argument("--repeat", type=int, default=1,
|
|
help="循环执行次数 (默认: 1)")
|
|
parser.add_argument("--delay", type=int, default=0,
|
|
help="每次循环之间的等待秒数 (默认: 0)")
|
|
args = parser.parse_args()
|
|
|
|
# 加载配置
|
|
section = _load_yun8_section(args.config)
|
|
|
|
# 读取 Token(命令行优先)
|
|
tokens = section.get("tokens", {})
|
|
driver_token = args.auth_driver or tokens.get("driver_token", "")
|
|
admin_token = args.auth_admin or tokens.get("admin_token", "")
|
|
|
|
print("=" * 60)
|
|
print(f" yun8 运输流程自动化")
|
|
print(f" config: {args.config or 'tests/config/test_config.yaml (yun8_api)'}")
|
|
print(f" stage: {args.stage}")
|
|
print(f" dry-run: {args.dry_run}")
|
|
print(f" repeat: {args.repeat}")
|
|
print(f" delay: {args.delay}s")
|
|
print(f" 开始时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
print("=" * 60)
|
|
|
|
ok_count = 0
|
|
fail_count = 0
|
|
cycle_start = time.time()
|
|
|
|
for i in range(1, args.repeat + 1):
|
|
if args.repeat > 1:
|
|
print(f"\n{'#' * 60}")
|
|
print(f"# 第 {i}/{args.repeat} 次循环")
|
|
print(f"# 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
print(f"{'#' * 60}")
|
|
|
|
try:
|
|
run_one_cycle(section, args.stage, driver_token,
|
|
admin_token, args.dry_run)
|
|
ok_count += 1
|
|
if args.repeat > 1:
|
|
print(f"\n--- 第 {i}/{args.repeat} 次循环: 成功 ---")
|
|
|
|
except KeyboardInterrupt:
|
|
print(f"\n 用户中断,已执行 {i-1} 次")
|
|
fail_count += (args.repeat - i + 1)
|
|
break
|
|
except Exception as exc:
|
|
fail_count += 1
|
|
print(f"\n--- 第 {i}/{args.repeat} 次循环: 失败 ---")
|
|
print(f" 错误: {exc}")
|
|
traceback.print_exc()
|
|
# 失败后继续下一次(不中断循环)
|
|
|
|
# 循环间隔
|
|
if i < args.repeat and args.delay > 0:
|
|
print(f" 等待 {args.delay}s 后进入下一次循环...")
|
|
time.sleep(args.delay)
|
|
|
|
# 汇总
|
|
elapsed = time.time() - cycle_start
|
|
print(f"\n{'=' * 60}")
|
|
print(f" 全部循环完成")
|
|
print(f" 成功: {ok_count} 失败: {fail_count} 总计: {args.repeat}")
|
|
print(f" 耗时: {elapsed:.0f}s")
|
|
print(f" 结束时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
print(f"{'=' * 60}")
|
|
|
|
if fail_count > 0:
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|