"""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}"