refactor(api): yun8_sync 并入 tests 分层框架(接口层 apis/yun8 + 用例层 api + Allure)

- 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 兼容
This commit is contained in:
xst
2026-09-08 11:38:17 +08:00
parent e62e3224bf
commit 52b73f456c
26 changed files with 2147 additions and 1786 deletions
+114
View File
@@ -0,0 +1,114 @@
"""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}"