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:
@@ -0,0 +1,188 @@
|
||||
# yun8 运输流程 API 造数(接口自动化)
|
||||
|
||||
将 `yun8.postman_collection.json` 中 **"运输流程"** 全部 18 个接口 + SQL 写入实现为
|
||||
pytest 用例 + 造数 CLI,与 `tests/` 框架分层对齐(`apis/` 接口层 ≙ `pages/` PO 层)。
|
||||
|
||||
> ⚠️ 本套件会**真实调用测试环境接口并写入数据库**(造运单、打款、回单、ETC 发票),
|
||||
> 使用 `yun8_sync` marker 隔离,不会混入默认冒烟扫描。
|
||||
|
||||
---
|
||||
|
||||
## 分层结构
|
||||
|
||||
```
|
||||
tests/
|
||||
├── apis/yun8/ # 接口层(≙ pages/ PO 层)
|
||||
│ ├── client.py # Yun8ApiClient 门面: 司机/管理/滇运通 3 套会话
|
||||
│ ├── driver_api.py # step01-05 司机端接口
|
||||
│ ├── accounting_api.py # step06-09 运费核算
|
||||
│ ├── audit_payment_api.py # step10-16 审核 & 付款
|
||||
│ ├── receipt_api.py # step17-20 回单/上报
|
||||
│ ├── third_party_api.py # step21 滇运通(独立 host/token)
|
||||
│ ├── sql_templates.py # 全部 SQL(参数化,%s 占位)
|
||||
│ ├── pipeline.py # Yun8Pipeline 24 步编排(Postman 变量上下文)
|
||||
│ └── cli.py # 造数 CLI(python -m tests.apis.yun8.cli)
|
||||
├── api/ # 用例层
|
||||
│ ├── conftest.py # yun8_config / yun8_db / yun8_pipeline fixtures
|
||||
│ └── test_yun8_transport_flow.py # P0 E2E: 5 个有序阶段用例
|
||||
└── config/test_config.yaml # yun8_api 配置段(数据库复用 database 段)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 流程概览
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 阶段 A: 司机端 (Step 01-05) │
|
||||
│ 查询货源 → 接单 → 查询待运输单 → 装货 → 卸货 │
|
||||
│ 变量: mainOrderCode → code / vehicleCode / orderCode │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 阶段 B: 运费核算 (Step 06-09) │
|
||||
│ 查询运单 → 运单详情 → 核算试算 → 核算提交 │
|
||||
│ 变量: 30+ 核算字段 + ticketImageUrls │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 阶段 C: 审核 & 付款 (Step 10-16) │
|
||||
│ 查询审核 → 审核通过 → 队长分账(可选) → 油卡充值 → 司机打款 → 货主打款│
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 阶段 D: 回单文件 + SQL (Step 17-21) │
|
||||
│ 运单上传(payNo) → 打款记录 → 文件名前缀 → INSERT → 上报运单 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 阶段 E: ETC 开票 SQL (Step 22-24) │
|
||||
│ 写入 base_etc_invoice (多条) → 写入 tms_order_etc_push │
|
||||
│ → 更新 tms_order_detail 开票状态 │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 上下游变量传递
|
||||
|
||||
| 步骤 | 产出变量 | 消费步骤 |
|
||||
|------|---------|---------|
|
||||
| 01 查询货源 | `mainOrderCode`, `residueNumber` | 02, 09 |
|
||||
| 03 查询待运输单 | `code`, `vehicleCode`, `orderCode` | 04-24 |
|
||||
| 06 运费核算 | 30+ 字段 (branchCode, oilFee, ...) | 09, 12 |
|
||||
| 07 运单详情 | `deliveryCashFee`, `driverActualFreight`, ... | 09, 15 |
|
||||
| 08 核算试算 | `ticketImageUrls` | 09 |
|
||||
| 17 运单上传 | `payNo` (sendMsg 正则提取) | SQL |
|
||||
| 18 打款记录 | 所有 `code` | 19 |
|
||||
| 19 文件名前缀 | `filePrefix` | SQL |
|
||||
|
||||
---
|
||||
|
||||
## 使用
|
||||
|
||||
### pytest 用例(5 阶段有序 E2E,带 Allure 报告与断言)
|
||||
|
||||
```bash
|
||||
# 完整流程(5 个阶段用例,前置失败自动 skip 后续)
|
||||
pytest tests/api -m yun8_sync -v
|
||||
|
||||
# 生成 Allure 报告
|
||||
allure generate output/allure-results -o output/allure-report --clean
|
||||
```
|
||||
|
||||
阶段与用例对应: A 司机端 / B 运费核算 / C 审核付款 / D 回单+SQL / E ETC 开票。
|
||||
`execute_after_audit=false` 时 D、E 阶段自动 skip。
|
||||
|
||||
### 造数 CLI(循环造数 / 分阶段运行)
|
||||
|
||||
```bash
|
||||
# 完整流程
|
||||
python -m tests.apis.yun8.cli --auth-driver "Bearer xxx" --auth-admin "Bearer yyy"
|
||||
|
||||
# 仅司机端 (Step 01-05) / 仅核算 (06-09) / 仅审核付款 (10-16) / 仅回单+SQL+上报 (17-24)
|
||||
python -m tests.apis.yun8.cli --stage driver
|
||||
python -m tests.apis.yun8.cli --stage accounting
|
||||
python -m tests.apis.yun8.cli --stage payment
|
||||
python -m tests.apis.yun8.cli --stage receipt
|
||||
|
||||
# 循环执行 10 次,每次间隔 5 秒
|
||||
python -m tests.apis.yun8.cli --repeat 10 --delay 5
|
||||
|
||||
# 仅 receipt 阶段循环 100 次
|
||||
python -m tests.apis.yun8.cli --stage receipt --repeat 100 --delay 2
|
||||
|
||||
# 预览 SQL 不写库(配合 random 重量模式,每次自动换新重量)
|
||||
python -m tests.apis.yun8.cli --dry-run
|
||||
python -m tests.apis.yun8.cli --repeat 5 --dry-run
|
||||
```
|
||||
|
||||
> ⚠️ **`--dry-run` 不是只读模式**:仅跳过回单/ETC 的 SQL 写入(打印 mogrify 渲染的
|
||||
> SQL),HTTP 步骤仍真实执行,driver 阶段的 reset SQL 也不受 dry-run 影响。
|
||||
|
||||
> 独立运行 `payment` 或 `receipt` 阶段时,会自动从 admin 端补全前置数据
|
||||
> (code/orderCode),无需手动指定。
|
||||
|
||||
### 配置
|
||||
|
||||
统一在 `tests/config/test_config.yaml` 的 **`yun8_api:`** 段维护:
|
||||
|
||||
- `tokens`: 司机端/管理端 Bearer Token(CLI `--auth-driver` / `--auth-admin` 可覆盖)
|
||||
- `driver_params`: 重量模式(`fixed` 固定值 / `random` 每次随机 1.0~10.0)、
|
||||
装卸货经纬度覆盖、待运输单重置、装卸货照片 URL
|
||||
- `accounting`: 核算固定参数 + `execute_after_audit`(是否执行 12-21 步)
|
||||
- `payment`: 打款参数;`etc_invoice`: ETC 发票条数
|
||||
- `dianyuntong.enabled`: 滇运通审核开关(默认关闭)
|
||||
|
||||
> `branchCode` / `businessTypeCode` / `payeeCode` / `shipmentPhone` /
|
||||
> `shipmentPartyCode` 优先从 step06 返回值读取,配置仅作兜底。
|
||||
|
||||
---
|
||||
|
||||
## 故障处理
|
||||
|
||||
| 现象 | 处理方式 |
|
||||
|------|---------|
|
||||
| WAF 拦截 (SQL注入误判) | 已内置:直连服务器(trust_env=False)+ 手工 query string 编码 |
|
||||
| "运单正被处理中" | 已内置:自动重试 5 次,间隔递增 5~13s |
|
||||
| "货运单不存在" | Token 过期或运单状态不对,检查 admin token |
|
||||
| 业务 code≠0 | **会直接失败**(迁移后行为):CLI 计入失败循环,pytest 用例 RED |
|
||||
| 提取不到 payNo | **会直接失败**(迁移后行为):不再写空 front_seq_no 的脏行 |
|
||||
| 独立阶段提示 code 为空 | 已内置:自动从 admin 端补全 |
|
||||
| 日志 | `output/logs/test_run_*.log`(DEBUG 全量 URL,含 WAF 排查线索) |
|
||||
|
||||
---
|
||||
|
||||
## 接口对照表
|
||||
|
||||
(Step 编号与 `tests/apis/yun8` 代码中的 stepNN 方法一一对应)
|
||||
|
||||
| Step | 名称 | 方法 | 路径 | Auth |
|
||||
|------|------|------|------|------|
|
||||
| 01 | 查询货源 | GET | `/ntocc-tms-api/order/orderHallList` | Driver |
|
||||
| 02 | 接单 | POST | `/ntocc-tms-api/orderDetail/receive` | Driver |
|
||||
| 03 | 查询待运输单 | GET | `/ntocc-tms-api/orderDetail` | Driver |
|
||||
| 04 | 装货 | PUT | `/ntocc-tms-api/orderDetail/fill` | Driver |
|
||||
| 05 | 卸货 | PUT | `/ntocc-tms-api/orderDetail/uploadTicketImage` | Driver |
|
||||
| 06 | 运费核算查询运单 | GET | `/ntocc-tms-api/orderDetail/orderDetailHandlePageList` | Admin |
|
||||
| 07 | 查询运单详情 | POST | `/ntocc-tms-api/orderDetail/getOrderDetailExpensesParamList` | Admin |
|
||||
| 08 | 核算试算 | PUT | `/ntocc-tms-api/orderDetail/calculateOrderDetail` | Admin |
|
||||
| 09 | 核算提交 | PUT | `/ntocc-tms-api/orderDetail/orderHandleEditSubmitHandle` | Admin |
|
||||
| 10 | 查询运单审核 | GET | `/ntocc-tms-api/orderDetail` (status=6&isSettle=1) | Admin |
|
||||
| 11 | 核算审核通过 | POST | `/ntocc-tms-api/orderDetail/auditOrderDetail` | Admin |
|
||||
| 12 | 队长分账预览 | GET | `/ntocc-tms-api/orderDetail/captainSplitAdjustPreview/{code}` | Admin |
|
||||
| 13 | 队长分账调整 | POST | `/ntocc-tms-api/orderDetail/adjustCaptainSplit` | Admin |
|
||||
| 14 | 单油卡充值 | POST | `/ntocc-tms-api/finance/singleOil` | Admin |
|
||||
| 15 | 垫资-财务打款 | POST | `/ntocc-tms-api/arpaTransfer/payToDriver` | Admin |
|
||||
| 16 | 垫资-货主打款 | POST | `/ntocc-tms-api/orderDetail/applicationForPayment` | Admin |
|
||||
| 17 | 查询运单上传(提取 payNo) | GET | `/ntocc-basic-api/dytBusiness/orderDetail` (status=5) | Admin |
|
||||
| 18 | 查询打款记录 | GET | `/ntocc-tms-api/remitDefinite` | Admin |
|
||||
| 19 | 查询文件名前缀 | GET | `/ntocc-tms-api/centralizedPayment/downLoadElectronicReceipt` | Admin |
|
||||
| 20 | 上报运单 | POST | `/ntocc-basic-api/dytBusiness/reportOrderDetail` | Admin |
|
||||
| 21 | 滇运通审核 | POST | `https://yndyt.cxtech.vip/ntocc-tms-api/orderDetail/updateOrderDetailExt` | 滇运通 |
|
||||
| — | 回单 SQL INSERT | — | `ntocc.base_gd_mft_file` | — |
|
||||
| 22 | ETC 开票信息写入 | SQL INSERT | `ntocc.base_etc_invoice` | — |
|
||||
| 23 | ETC 推送记录写入 | SQL INSERT | `ntocc.tms_order_etc_push` | — |
|
||||
| 24 | 运单 ETC 开票状态更新 | SQL UPDATE | `ntocc.tms_order_detail` | — |
|
||||
|
||||
> 步骤 22 每个运单插入 `etc_invoice.count` 条发票(config.yaml 配置,默认 3),每条的
|
||||
> **发票号码**(随机 20 位数字)、**金额**(随机分配,**所有发票合计 ≤ 运费×50%**,运费
|
||||
> 优先取 `driverActualFreight`、缺失时取 `shipmentActualFreight`,超限或运费未知则跳过
|
||||
> 发票写入)、**进站/出站**(按广东→河北路段链取相邻两站)、**时间**(交易时间在运单窗口
|
||||
> 内逐条推进)各不相同;税额、开票方、发票类型、URL 等其余字段用固定值。
|
||||
> 步骤 22/23 的 `order_code`/`order_no` 取步骤 03 的 `orderCode`,`push_order_no` 为其后
|
||||
> 追加 `-1`,`order_detail_code` 取步骤 03 的 `code`,两个表的 `code` 按现有数据规则
|
||||
> (32 位十六进制)自动生成,随 receipt 阶段执行(`--dry-run` 仅预览不写库)。
|
||||
> 步骤 23 的推送记录固定写入 `invoice_pull_status=2`、`start_status=1`、`end_status=1`、
|
||||
> `start_result='成功'`、`end_result='成功'`;步骤 24 将步骤 03 `orderCode` 对应运单的
|
||||
> `etc_invoice_status` 置为 3。
|
||||
@@ -0,0 +1 @@
|
||||
"""API 接口用例层 — 对应 Web 端 tests/web/、App 端 tests/app/ 的分域组织。"""
|
||||
@@ -0,0 +1,44 @@
|
||||
"""tests/api conftest — yun8 运输流程 API 造数 fixtures。
|
||||
|
||||
Usage:
|
||||
def test_xxx(yun8_config, yun8_pipeline, yun8_context):
|
||||
yun8_pipeline.run_driver_flow()
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def yun8_config(config):
|
||||
"""yun8 运输流程 API 配置段(test_config.yaml 的 yun8_api)。"""
|
||||
return config["yun8_api"]
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def yun8_db(yun8_config):
|
||||
"""class 级数据库长连接(造数 SQL 复用)。"""
|
||||
from tests.core.db_helper import Db
|
||||
db = Db()
|
||||
yield db
|
||||
db.close()
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def yun8_pipeline(yun8_config, yun8_db):
|
||||
"""class 级 Yun8Pipeline — 5 个阶段用例共享同一份 Postman 变量上下文。
|
||||
|
||||
注意: 根 conftest 的 logger fixture 是 function 级,
|
||||
这里直接用 get_logger(pipeline 内部亦如此)。
|
||||
"""
|
||||
from tests.apis.yun8.pipeline import Yun8Pipeline
|
||||
pipe = Yun8Pipeline(yun8_config, yun8_db)
|
||||
pipe.set_auth(yun8_config["tokens"]["driver_token"],
|
||||
yun8_config["tokens"]["admin_token"])
|
||||
yield pipe
|
||||
pipe.close()
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def yun8_context():
|
||||
"""跨阶段共享上下文:记录中断原因,供后续阶段 skip。"""
|
||||
return {"aborted": None}
|
||||
@@ -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}"
|
||||
Reference in New Issue
Block a user