refactor(test): 集成工厂上提共用,submit_change 拆校验段,审计重试断言对齐全局兜底
- tests/integration/factories.py:回款/发货两文件逐字重复的 _customer/_bitem/_bpayload/_create_business 上提(t_ 前缀与默认行 口径单点维护);test_audit_retry 的持续冲突断言由 500 更新为 400「数据完整性冲突」——全局 IntegrityError handler(批 6)收敛 行为属预期改进,非回归 - param_service.submit_change 拆出 _normalize_items(校验段独立, 主函数降至 50 行约定内);e2e 六个列表页 helper 收口 common.py (base_data 版为准:分组参数化、:text-is 精确匹配),两脚本变薄 - 全量回归 625 passed(基线 603 + 新增 22);e2e 四链路(登录 8/ 系统管理 13/基础数据 13/业务 16)全过 - 测试 README 补 2026-10-15 运行记录与 10 条评审缺陷(预览千分比/ 审计假成功/字典序 MAX/无锁编辑/删产品 500/schema 口径/quantize/ 负天数外插/评级并发/大小写不对称) Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
1 parent
0ef3adec21
commit
f75a9d534e
6 files changed
+141
-147
No files matched your search
@@ -0,0 +1,60 @@
|
||||
"""集成测试共用工厂:客户 → 业务 → 明细的最小造数链。
|
||||
|
||||
回款/发货/业务三个测试文件此前各持一份逐字相同的工厂(口径漂移已经
|
||||
发生:同一 _customer 三份、payload 细节靠复制维持),收口后「t_ 前缀、
|
||||
默认明细行、业务日期取今日」单点维护;conftest 兜底清理按 t_ 前缀与
|
||||
YW/SH/HK 编号前缀覆盖本模块全部造数。
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from datetime import date
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
_CUSTOMERS = "/api/v1/base/customers"
|
||||
_BUSINESSES = "/api/v1/business/businesses"
|
||||
|
||||
|
||||
def customer(client: TestClient, headers: dict) -> dict:
|
||||
"""建一名 t_ 前缀测试客户(业务的必选引用)。"""
|
||||
resp = client.post(
|
||||
_CUSTOMERS,
|
||||
json={
|
||||
"company_name": f"t_公司_{uuid.uuid4().hex[:8]}",
|
||||
"owner_name": "张老板",
|
||||
"customer_type": "personal",
|
||||
},
|
||||
headers=headers,
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
return resp.json()["data"]
|
||||
|
||||
|
||||
def business_item(**overrides: object) -> dict:
|
||||
"""业务明细行入参(默认:数量 100 × 单价 100.00 = 销售额 10000)。"""
|
||||
body: dict = {
|
||||
"product_name": f"t_产品_{uuid.uuid4().hex[:8]}",
|
||||
"product_category": "municipal_water",
|
||||
"quantity": "100",
|
||||
"sale_price": "100.00",
|
||||
}
|
||||
body.update(overrides)
|
||||
return body
|
||||
|
||||
|
||||
def business_payload(customer_id: int, **overrides: object) -> dict:
|
||||
body: dict = {
|
||||
"customer_id": customer_id,
|
||||
"business_date": str(date.today()),
|
||||
"items": [business_item()],
|
||||
}
|
||||
body.update(overrides)
|
||||
return body
|
||||
|
||||
|
||||
def create_business(client: TestClient, headers: dict) -> dict:
|
||||
"""建一名 t_ 客户并挂一笔默认业务(发货/回款须先有业务)。"""
|
||||
cust = customer(client, headers)
|
||||
resp = client.post(_BUSINESSES, json=business_payload(cust["id"]), headers=headers)
|
||||
assert resp.status_code == 200, resp.text
|
||||
return resp.json()["data"]
|
||||
@@ -152,8 +152,10 @@ class TestAuditRetry:
|
||||
second = client.post(
|
||||
_BUSINESSES, json=_business_payload(customer_id), headers=normal["headers"]
|
||||
)
|
||||
# 未捕获 IntegrityError 由全局异常兜底收敛 500
|
||||
assert second.status_code == 500, second.text
|
||||
# 未捕获 IntegrityError 由全局 handler 收敛 400(数据完整性冲突)——
|
||||
# 完整性冲突对用户是「操作被拒绝」而非「服务端异常」,400 更贴近事实
|
||||
assert second.status_code == 400, second.text
|
||||
assert "完整性冲突" in second.json()["message"]
|
||||
# 修复前的症状:return _retry_create 无界递归直至 RecursionError,
|
||||
# 每层各写一条记录;修复后重试有上限,最终失败仅一条失败日志
|
||||
assert _log_count(normal["username"], success=False) == base_fail + 1
|
||||
|
||||
@@ -26,57 +26,13 @@ from app.core.constants import ACTION_CREATE, MODULE_RECEIPT
|
||||
from app.core.db import get_session_factory
|
||||
from app.models import OperationLog, Receipt
|
||||
from app.models.receipt import SETTLEMENT_DONE
|
||||
from tests.integration.factories import create_business as _create_business
|
||||
|
||||
pytestmark = pytest.mark.integration
|
||||
|
||||
_BUSINESSES = "/api/v1/business/businesses"
|
||||
_SHIPMENTS = "/api/v1/business/shipments"
|
||||
_RECEIPTS = "/api/v1/business/receipts"
|
||||
_CUSTOMERS = "/api/v1/base/customers"
|
||||
|
||||
|
||||
def _customer(client: TestClient, headers: dict) -> dict:
|
||||
resp = client.post(
|
||||
_CUSTOMERS,
|
||||
json={
|
||||
"company_name": f"t_公司_{uuid.uuid4().hex[:8]}",
|
||||
"owner_name": "张老板",
|
||||
"customer_type": "personal",
|
||||
},
|
||||
headers=headers,
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
return resp.json()["data"]
|
||||
|
||||
|
||||
def _bitem(**overrides: object) -> dict:
|
||||
"""业务明细行入参(发货创建的门槛:业务须有明细 ≥1 行)。"""
|
||||
body: dict = {
|
||||
"product_name": f"t_产品_{uuid.uuid4().hex[:8]}",
|
||||
"product_category": "municipal_water",
|
||||
"quantity": "100",
|
||||
"sale_price": "100.00",
|
||||
}
|
||||
body.update(overrides)
|
||||
return body
|
||||
|
||||
|
||||
def _bpayload(customer_id: int, **overrides: object) -> dict:
|
||||
body: dict = {
|
||||
"customer_id": customer_id,
|
||||
"business_date": str(date.today()),
|
||||
"items": [_bitem()],
|
||||
}
|
||||
body.update(overrides)
|
||||
return body
|
||||
|
||||
|
||||
def _create_business(client: TestClient, headers: dict) -> dict:
|
||||
"""建一名 t_ 客户并挂一笔默认业务(回款/发货单挂在业务上)。"""
|
||||
customer = _customer(client, headers)
|
||||
resp = client.post(_BUSINESSES, json=_bpayload(customer["id"]), headers=headers)
|
||||
assert resp.status_code == 200, resp.text
|
||||
return resp.json()["data"]
|
||||
|
||||
|
||||
def _sitem(**overrides: object) -> dict:
|
||||
|
||||
@@ -25,6 +25,7 @@ from sqlalchemy import select
|
||||
from app.core.constants import ACTION_CREATE, MODULE_SHIPMENT
|
||||
from app.core.db import get_session_factory
|
||||
from app.models import OperationLog
|
||||
from tests.integration.factories import create_business as _create_business
|
||||
|
||||
pytestmark = pytest.mark.integration
|
||||
|
||||
@@ -32,51 +33,6 @@ _BUSINESSES = "/api/v1/business/businesses"
|
||||
_SHIPMENTS = "/api/v1/business/shipments"
|
||||
_RECEIPTS = "/api/v1/business/receipts"
|
||||
_PERSONNEL = "/api/v1/base/personnel"
|
||||
_CUSTOMERS = "/api/v1/base/customers"
|
||||
|
||||
|
||||
def _customer(client: TestClient, headers: dict) -> dict:
|
||||
resp = client.post(
|
||||
_CUSTOMERS,
|
||||
json={
|
||||
"company_name": f"t_公司_{uuid.uuid4().hex[:8]}",
|
||||
"owner_name": "张老板",
|
||||
"customer_type": "personal",
|
||||
},
|
||||
headers=headers,
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
return resp.json()["data"]
|
||||
|
||||
|
||||
def _bitem(**overrides: object) -> dict:
|
||||
"""业务明细行入参(发货创建的门槛:业务须有明细 ≥1 行)。"""
|
||||
body: dict = {
|
||||
"product_name": f"t_产品_{uuid.uuid4().hex[:8]}",
|
||||
"product_category": "municipal_water",
|
||||
"quantity": "100",
|
||||
"sale_price": "100.00",
|
||||
}
|
||||
body.update(overrides)
|
||||
return body
|
||||
|
||||
|
||||
def _bpayload(customer_id: int, **overrides: object) -> dict:
|
||||
body: dict = {
|
||||
"customer_id": customer_id,
|
||||
"business_date": str(date.today()),
|
||||
"items": [_bitem()],
|
||||
}
|
||||
body.update(overrides)
|
||||
return body
|
||||
|
||||
|
||||
def _create_business(client: TestClient, headers: dict) -> dict:
|
||||
"""建一名 t_ 客户并挂一笔默认业务(业务须先存在,发货单挂在业务上)。"""
|
||||
customer = _customer(client, headers)
|
||||
resp = client.post(_BUSINESSES, json=_bpayload(customer["id"]), headers=headers)
|
||||
assert resp.status_code == 200, resp.text
|
||||
return resp.json()["data"]
|
||||
|
||||
|
||||
def _item(**overrides: object) -> dict:
|
||||
|
||||
Reference in new issue
Block a user