test(base): 基础数据管理模块集成用例(48 例)与审计盲区修复

- 覆盖:401/引用校验/删除保护/子表全量替换/类别与组合唯一/归属变更留痕/审计双记录
- 缺陷修复:personnel/product/customer 三 service 的前置校验移入审计上下文,
  校验失败(400)同样留痕(成功与失败都记的既定原则)
- conftest 兜底清理扩展到 M2 五表(留痕无外键,按 operator_name + 前缀双轨清理)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
liuwenxinandClaude Code committed 2026-10-13 11:22:34 +08:00
1 parent 15de81dd2c
commit ca88ee7a0b
12 files changed
+1090 -29

No files matched your search

+56 -2
View File
@@ -7,6 +7,9 @@
- 测试账号/角色统一 ``t_`` 前缀;工厂夹具清理自己创建的对象,另有
autouse 兜底按前缀全量清理(用例经**接口**创建的数据不在工厂名下,
失败中断时会残留并影响后续用例的 total 断言)
- M2 基础数据(人员姓名/产品名/公司名/测试岗位编码)同用 ``t_`` 前缀,
兜底清理按前缀删除;归属留痕对客户/人员 ID 无外键固化、不随主表删除
消失,必须显式清理,否则残留会影响后续用例对留痕条数的断言
- 操作日志的清理同时按 username 与 user_id —— 账号被删除后其 ``user_id``
会被 SET NULL,只能靠冗余的 username 追溯,清理逻辑必须覆盖两种状态
"""
@@ -14,12 +17,24 @@
import uuid
import pytest
from sqlalchemy import delete, select
from sqlalchemy import delete, select, update
from app.core.constants import ROLE_ADMIN, ROLE_USER
from app.core.db import get_session_factory
from app.core.security import create_access_token, hash_password
from app.models import OperationLog, Role, User, UserRole
from app.models import (
BusinessRole,
Customer,
OperationLog,
OwnershipChangeLog,
Personnel,
PersonnelPlan,
PersonnelRegion,
Product,
Role,
User,
UserRole,
)
TEST_PASSWORD = "TestPwd@2026"
# 模块级预计算一次:bcrypt(rounds=12)单次约 0.2–0.4 秒,逐用例重复哈希
@@ -53,6 +68,45 @@ def _purge_test_data() -> None:
if role_ids:
session.execute(delete(UserRole).where(UserRole.role_id.in_(role_ids)))
session.execute(delete(Role).where(Role.id.in_(role_ids)))
# ---- M2 基础数据:客户/归属留痕/人员/产品/测试岗位 ----
# 外键均为 RESTRICT,删除顺序不能倒:留痕无外键先按 customer_id 清,
# 再删客户(子表)→ 人员(先置空 manager_id 自引用、清子表)→ 产品
personnel_ids = list(
session.execute(
select(Personnel.id).where(Personnel.personnel_name.like("t\\_%"))
).scalars()
)
customer_ids = list(
session.execute(
select(Customer.id).where(Customer.company_name.like("t\\_%"))
).scalars()
)
if customer_ids:
session.execute(
delete(OwnershipChangeLog).where(OwnershipChangeLog.customer_id.in_(customer_ids))
)
session.execute(delete(Customer).where(Customer.id.in_(customer_ids)))
# 客户被物理删除后其 customer_id 已无从收集,按操作人用户名兜底一条
session.execute(
delete(OwnershipChangeLog).where(OwnershipChangeLog.operator_name.like("t\\_%"))
)
if personnel_ids:
session.execute(
update(Personnel)
.where(Personnel.manager_id.in_(personnel_ids))
.values(manager_id=None)
)
session.execute(
delete(PersonnelRegion).where(PersonnelRegion.personnel_id.in_(personnel_ids))
)
session.execute(
delete(PersonnelPlan).where(PersonnelPlan.personnel_id.in_(personnel_ids))
)
session.execute(delete(Personnel).where(Personnel.id.in_(personnel_ids)))
session.execute(delete(Product).where(Product.product_name.like("t\\_%")))
# 人员已先删(business_role_id 外键 RESTRICT),直建的测试岗位此时可安全删
session.execute(delete(BusinessRole).where(BusinessRole.role_code.like("t\\_%")))
session.commit()