- 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 兼容
115 lines
6.2 KiB
Python
115 lines
6.2 KiB
Python
"""yun8 运输流程 API 造数 — 24 步全链路 E2E。
|
||
|
||
⚠️ 此套件会真实调用测试环境接口并写入数据库(造运单/打款/回单/ETC 发票),
|
||
不属于默认冒烟扫描范围,请显式按 marker 运行:
|
||
pytest tests/api -m yun8_sync -v
|
||
|
||
5 个阶段用例按定义顺序执行(同一类内定义序即执行序,勿并行),
|
||
共享一套 Yun8Pipeline(Postman Collection Variables 上下文):
|
||
阶段 A driver → 阶段 B accounting → 阶段 C audit_payment
|
||
→ 阶段 D receipt → 阶段 E etc
|
||
前置阶段失败时,后续阶段自动 skip(与 CLI 一次循环失败即中止语义一致)。
|
||
|
||
Usage:
|
||
pytest tests/api/test_yun8_transport_flow.py -v
|
||
"""
|
||
|
||
import allure
|
||
import pytest
|
||
|
||
pytestmark = [pytest.mark.api, pytest.mark.yun8_sync, pytest.mark.p0]
|
||
|
||
|
||
@allure.epic("运八网络货运平台")
|
||
@allure.feature("yun8 运输流程 API 造数 — 24 步全链路")
|
||
class TestYun8TransportFlow:
|
||
"""运输流程全链路:接口驱动 + SQL 造数(详见 tests/api/README.md)。"""
|
||
|
||
# ── 中断守卫:前置阶段失败则跳过 ──────────────────────────────
|
||
def _guard(self, yun8_context: dict):
|
||
if yun8_context["aborted"]:
|
||
pytest.skip(f"前置阶段失败: {yun8_context['aborted']}")
|
||
|
||
def _run(self, yun8_context: dict, stage: str, fn, *args, **kwargs):
|
||
"""执行编排方法,失败时记录中断原因后原样抛出。"""
|
||
try:
|
||
return fn(*args, **kwargs)
|
||
except Exception as exc:
|
||
yun8_context["aborted"] = f"{stage}: {exc}"
|
||
raise
|
||
|
||
@staticmethod
|
||
def _execute_after_audit(yun8_pipeline) -> bool:
|
||
return yun8_pipeline.config.get("accounting", {}).get(
|
||
"execute_after_audit", True)
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 阶段 A-E
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
@allure.story("阶段A — 司机端 (Step 01-05)")
|
||
@allure.title("TC-Y8-001 司机端 查询货源→接单→装货→卸货")
|
||
def test_stage_a_driver_flow(self, yun8_pipeline, yun8_context):
|
||
"""查询货源→接单→查询待运输单→装货→卸货,产出 code/orderCode。"""
|
||
self._guard(yun8_context)
|
||
with allure.step("执行司机端流程 run_driver_flow (Step 01-05)"):
|
||
vars_ = self._run(yun8_context, "阶段A",
|
||
yun8_pipeline.run_driver_flow)
|
||
assert vars_.get("mainOrderCode"), "mainOrderCode 为空"
|
||
assert vars_.get("code"), "待运输单 code 为空"
|
||
assert vars_.get("orderCode"), "orderCode 为空"
|
||
|
||
@allure.story("阶段B — 运费核算 (Step 06-09)")
|
||
@allure.title("TC-Y8-002 核算查询→详情→试算→提交")
|
||
def test_stage_b_accounting_flow(self, yun8_pipeline, yun8_context):
|
||
"""查询运单→运单详情→核算试算→核算提交,产出 30+ 核算字段。"""
|
||
self._guard(yun8_context)
|
||
with allure.step("执行运费核算流程 run_accounting_flow (Step 06-09)"):
|
||
vars_ = self._run(yun8_context, "阶段B",
|
||
yun8_pipeline.run_accounting_flow)
|
||
assert vars_.get("deliveryCashFee") != "", "deliveryCashFee 为空"
|
||
assert vars_.get("shipmentActualCash") != "", "shipmentActualCash 为空"
|
||
|
||
@allure.story("阶段C — 审核 & 付款 (Step 10-16)")
|
||
@allure.title("TC-Y8-003 审核通过→油卡充值→司机打款→货主打款")
|
||
def test_stage_c_audit_payment_flow(self, yun8_pipeline, yun8_context):
|
||
"""查询审核→审核通过→(分账)→油卡充值→财务打款→货主打款。
|
||
|
||
成功判定 = 编排全程无 Yun8StepError / HTTP 异常。
|
||
execute_after_audit=false 时编排内部自行跳过 12-16 步。
|
||
"""
|
||
self._guard(yun8_context)
|
||
with allure.step("执行审核付款流程 run_audit_payment_flow (Step 10-16)"):
|
||
self._run(yun8_context, "阶段C",
|
||
yun8_pipeline.run_audit_payment_flow)
|
||
|
||
@allure.story("阶段D — 回单文件 + SQL (Step 17-21)")
|
||
@allure.title("TC-Y8-004 提取payNo→回单INSERT→上报运单")
|
||
def test_stage_d_receipt_flow(self, yun8_pipeline, yun8_context):
|
||
"""payNo 提取→打款记录分页→文件名前缀→回单 INSERT→上报运单。"""
|
||
self._guard(yun8_context)
|
||
if not self._execute_after_audit(yun8_pipeline):
|
||
pytest.skip("execute_after_audit=false,receipt 阶段被配置跳过")
|
||
with allure.step("执行回单文件流程 run_receipt_file_flow (Step 17-21)"):
|
||
results = self._run(yun8_context, "阶段D",
|
||
yun8_pipeline.run_receipt_file_flow,
|
||
dry_run=False)
|
||
assert yun8_pipeline.vars.get("payNo"), "payNo 为空"
|
||
assert results, "无任何打款记录可处理(remit_records 为空)"
|
||
inserted = [r for r in results if r.get("status") == "inserted"]
|
||
assert inserted, f"没有成功插入的回单记录: {results}"
|
||
|
||
@allure.story("阶段E — ETC 开票 SQL (Step 22-24)")
|
||
@allure.title("TC-Y8-005 ETC发票INSERT→推送记录INSERT→开票状态更新")
|
||
def test_stage_e_etc_invoice_flow(self, yun8_pipeline, yun8_context):
|
||
"""写入 base_etc_invoice × N → tms_order_etc_push → 更新开票状态。"""
|
||
self._guard(yun8_context)
|
||
if not self._execute_after_audit(yun8_pipeline):
|
||
pytest.skip("execute_after_audit=false,ETC 阶段被配置跳过")
|
||
with allure.step("执行 ETC 开票流程 run_etc_invoice_flow (Step 22-24)"):
|
||
results = self._run(yun8_context, "阶段E",
|
||
yun8_pipeline.run_etc_invoice_flow,
|
||
dry_run=False)
|
||
errors = [r for r in results if r.get("status") == "error"]
|
||
assert not errors, f"ETC SQL 写入存在失败项: {errors}"
|