feat: Go后端LDAP真实对接 — AES-256-GCM密码加解密 + 登录LDAP fallback

- 新增 crypto.go: AES-256-GCM 加解密,与 Python backend 兼容
- auth.go: Login 添加 LDAP fallback(本地优先→LDAP绑定→自动创建本地用户)
- ldap.go: 实现真实 LDAP 连接/用户搜索/密码验证/OU列表/用户列表
- 所有 LDAP 操作解密 admin_password 后使用
- 新增/更新密码存储时加密
- router.go: 补充 delete-user 路由
- 添加 go-ldap/v3 依赖
This commit is contained in:
logan committed 2026-05-25 23:32:15 +08:00
1 parent 622923821b
commit 88e5f4e7ee
71 files changed
+20390 -64

No files matched your search

+759
View File
@@ -0,0 +1,759 @@
"""审核规则管理端点全面测试。
覆盖所有 9 个端点的正常/异常路径,包括:
- 权限校验(admin 可读写, dba 只读, developer/guest 禁止访问)
- 列表过滤(rule_type, severity, db_type, is_enabled)
- 创建/更新/删除/切换启用状态
- 统计摘要
- 种子数据(V1 seed/{db_type} 和 V2 seed-v2)
"""
import pytest
from tests.conftest import auth_header
# ── 通用测试载荷 ──────────────────────────────────────────────
VALID_RULE_PAYLOAD = {
"name": "禁止SELECT星号",
"description": "禁止使用 SELECT * 查询",
"rule_type": "performance",
"severity": "warning",
"db_type": "mysql",
"pattern": r"SELECT\s+\*",
"value": None,
"is_enabled": True,
"message": "禁止使用 SELECT *,请明确指定字段",
}
PREFIX = "/api/v1/audit-rules"
# ══════════════════════════════════════════════════════════════
# 一、列表端点 GET /api/v1/audit-rules
# ══════════════════════════════════════════════════════════════
class TestListAuditRules:
"""GET /audit-rules — 列表 & 过滤"""
@pytest.mark.asyncio
async def test_admin列表返回200(self, client, admin_token):
"""admin 角色可正常获取列表"""
resp = await client.get(PREFIX, headers=auth_header(admin_token))
assert resp.status_code == 200
assert isinstance(resp.json(), list)
@pytest.mark.asyncio
async def test_dba列表返回200(self, client, dba_token):
"""dba 角色可正常获取列表(只读权限)"""
resp = await client.get(PREFIX, headers=auth_header(dba_token))
assert resp.status_code == 200
@pytest.mark.asyncio
async def test_developer无权限返回403(self, client, developer_token):
"""developer 角色无权限"""
resp = await client.get(PREFIX, headers=auth_header(developer_token))
assert resp.status_code == 403
@pytest.mark.asyncio
async def test_guest无权限返回403(self, client, guest_token):
"""guest 角色无权限"""
resp = await client.get(PREFIX, headers=auth_header(guest_token))
assert resp.status_code == 403
@pytest.mark.asyncio
async def test无token返回401(self, client):
"""未携带 JWT 应返回 401"""
resp = await client.get(PREFIX)
assert resp.status_code == 401
@pytest.mark.asyncio
async def test按rule_type过滤(self, client, admin_token):
"""通过 rule_type 查询参数过滤"""
# 创建一条 performance 规则
await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "perf-rule"},
headers=auth_header(admin_token),
)
resp = await client.get(
f"{PREFIX}?rule_type=performance", headers=auth_header(admin_token)
)
assert resp.status_code == 200
for rule in resp.json():
assert rule["rule_type"] == "performance"
@pytest.mark.asyncio
async def test按severity过滤(self, client, admin_token):
"""通过 severity 查询参数过滤"""
await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "sev-rule", "severity": "error"},
headers=auth_header(admin_token),
)
resp = await client.get(
f"{PREFIX}?severity=error", headers=auth_header(admin_token)
)
assert resp.status_code == 200
for rule in resp.json():
assert rule["severity"] == "error"
@pytest.mark.asyncio
async def test按db_type过滤(self, client, admin_token):
"""通过 db_type 过滤,应同时包含 db_type='all' 的规则"""
await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "mysql-rule", "db_type": "mysql"},
headers=auth_header(admin_token),
)
resp = await client.get(
f"{PREFIX}?db_type=mysql", headers=auth_header(admin_token)
)
assert resp.status_code == 200
for rule in resp.json():
assert rule["db_type"] in ("mysql", "all")
@pytest.mark.asyncio
async def test按is_enabled过滤(self, client, admin_token):
"""通过 is_enabled 过滤已启用/已禁用规则"""
# 创建一条规则后禁用
create = await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "disabled-rule"},
headers=auth_header(admin_token),
)
rule_id = create.json()["id"]
await client.post(
f"{PREFIX}/{rule_id}/toggle", headers=auth_header(admin_token)
)
# 只查已禁用的
resp = await client.get(
f"{PREFIX}?is_enabled=false", headers=auth_header(admin_token)
)
assert resp.status_code == 200
for rule in resp.json():
assert rule["is_enabled"] is False
@pytest.mark.asyncio
async def test多条件联合过滤(self, client, admin_token):
"""同时传入多个过滤参数"""
await client.post(
PREFIX,
json={
**VALID_RULE_PAYLOAD,
"name": "multi-filter",
"rule_type": "DDL",
"severity": "error",
},
headers=auth_header(admin_token),
)
resp = await client.get(
f"{PREFIX}?rule_type=DDL&severity=error&is_enabled=true",
headers=auth_header(admin_token),
)
assert resp.status_code == 200
for rule in resp.json():
assert rule["rule_type"] == "DDL"
assert rule["severity"] == "error"
assert rule["is_enabled"] is True
@pytest.mark.asyncio
async def test空数据库返回空列表(self, client, admin_token):
"""无数据时返回空列表"""
resp = await client.get(PREFIX, headers=auth_header(admin_token))
assert resp.status_code == 200
assert resp.json() == []
# ══════════════════════════════════════════════════════════════
# 二、创建端点 POST /api/v1/audit-rules
# ══════════════════════════════════════════════════════════════
class TestCreateAuditRule:
"""POST /audit-rules — 创建规则"""
@pytest.mark.asyncio
async def test创建成功返回201(self, client, admin_token):
"""正常创建,返回 201 + 完整对象"""
resp = await client.post(
PREFIX, json=VALID_RULE_PAYLOAD, headers=auth_header(admin_token)
)
assert resp.status_code == 201
data = resp.json()
assert data["id"] is not None
assert data["name"] == VALID_RULE_PAYLOAD["name"]
assert data["rule_type"] == "performance"
assert data["severity"] == "warning"
assert data["is_enabled"] is True
assert "created_at" in data
assert "updated_at" in data
@pytest.mark.asyncio
async def test重复名称返回400(self, client, admin_token):
"""同名规则不允许重复创建"""
payload = {**VALID_RULE_PAYLOAD, "name": "dup-name"}
await client.post(PREFIX, json=payload, headers=auth_header(admin_token))
resp = await client.post(PREFIX, json=payload, headers=auth_header(admin_token))
assert resp.status_code == 400
assert "已存在" in resp.json()["detail"]
@pytest.mark.asyncio
async def test_dba创建返回403(self, client, dba_token):
"""dba 只有只读权限,不能创建"""
resp = await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "dba-create"},
headers=auth_header(dba_token),
)
assert resp.status_code == 403
@pytest.mark.asyncio
async def test_developer创建返回403(self, client, developer_token):
"""developer 无权限"""
resp = await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "dev-create"},
headers=auth_header(developer_token),
)
assert resp.status_code == 403
@pytest.mark.asyncio
async def test创建时仅必填字段(self, client, admin_token):
"""只提供 name/rule_type/message 三个必填字段也可成功"""
resp = await client.post(
PREFIX,
json={
"name": "minimal-rule",
"rule_type": "syntax",
"message": "语法检查",
},
headers=auth_header(admin_token),
)
assert resp.status_code == 201
data = resp.json()
assert data["severity"] == "warning" # 默认值
assert data["db_type"] == "mysql" # 默认值
assert data["is_enabled"] is True # 默认值
@pytest.mark.asyncio
async def test创建全部rule_type合法(self, client, admin_token):
"""所有合法 rule_type 枚举均可创建"""
for rt in ("syntax", "performance", "safety", "naming", "DDL", "DML"):
resp = await client.post(
PREFIX,
json={
"name": f"rt-{rt}",
"rule_type": rt,
"message": f"类型{rt}规则",
},
headers=auth_header(admin_token),
)
assert resp.status_code == 201, f"rule_type={rt} 创建失败"
@pytest.mark.asyncio
async def test创建全部severity合法(self, client, admin_token):
"""所有合法 severity 枚举均可创建"""
for sev in ("error", "warning", "info"):
resp = await client.post(
PREFIX,
json={
"name": f"sev-{sev}",
"rule_type": "syntax",
"severity": sev,
"message": f"严重度{sev}",
},
headers=auth_header(admin_token),
)
assert resp.status_code == 201, f"severity={sev} 创建失败"
@pytest.mark.asyncio
async def test非法rule_type返回422(self, client, admin_token):
"""非法 rule_type 应触发 Pydantic 校验错误"""
resp = await client.post(
PREFIX,
json={
"name": "bad-type",
"rule_type": "invalid_type",
"message": "测试",
},
headers=auth_header(admin_token),
)
assert resp.status_code == 422
@pytest.mark.asyncio
async def test缺少必填字段返回422(self, client, admin_token):
"""缺少 name 字段"""
resp = await client.post(
PREFIX,
json={"rule_type": "syntax", "message": "测试"},
headers=auth_header(admin_token),
)
assert resp.status_code == 422
@pytest.mark.asyncio
async def test创建带value和pattern(self, client, admin_token):
"""附带 pattern 和 value 的规则可正常创建"""
resp = await client.post(
PREFIX,
json={
"name": "pattern-rule",
"rule_type": "safety",
"pattern": r"DROP\s+TABLE",
"value": "100",
"message": "禁止删表",
},
headers=auth_header(admin_token),
)
assert resp.status_code == 201
assert resp.json()["pattern"] == r"DROP\s+TABLE"
assert resp.json()["value"] == "100"
# ══════════════════════════════════════════════════════════════
# 三、统计摘要 GET /api/v1/audit-rules/stats/summary
# ══════════════════════════════════════════════════════════════
class TestRuleStats:
"""GET /audit-rules/stats/summary — 统计摘要"""
@pytest.mark.asyncio
async def test空数据库统计(self, client, admin_token):
"""无数据时 total=0"""
resp = await client.get(f"{PREFIX}/stats/summary", headers=auth_header(admin_token))
assert resp.status_code == 200
data = resp.json()
assert data["total"] == 0
assert data["enabled"] == 0
assert data["disabled"] == 0
@pytest.mark.asyncio
async def test统计数据正确(self, client, admin_token):
"""创建规则后统计数据应更新"""
# 创建2条启用
await client.post(
PREFIX,
json={"name": "stat1", "rule_type": "DDL", "message": "t1"},
headers=auth_header(admin_token),
)
await client.post(
PREFIX,
json={"name": "stat2", "rule_type": "DML", "message": "t2"},
headers=auth_header(admin_token),
)
# 创建1条后禁用
create = await client.post(
PREFIX,
json={"name": "stat3", "rule_type": "DDL", "message": "t3"},
headers=auth_header(admin_token),
)
rid = create.json()["id"]
await client.post(f"{PREFIX}/{rid}/toggle", headers=auth_header(admin_token))
resp = await client.get(f"{PREFIX}/stats/summary", headers=auth_header(admin_token))
assert resp.status_code == 200
data = resp.json()
assert data["total"] == 3
assert data["enabled"] == 2
assert data["disabled"] == 1
assert data["by_type"]["DDL"] == 2
assert data["by_type"]["DML"] == 1
@pytest.mark.asyncio
async def test_dba可查看统计(self, client, dba_token):
"""dba 有只读权限"""
resp = await client.get(f"{PREFIX}/stats/summary", headers=auth_header(dba_token))
assert resp.status_code == 200
@pytest.mark.asyncio
async def test_developer不可查看统计(self, client, developer_token):
"""developer 无权限"""
resp = await client.get(f"{PREFIX}/stats/summary", headers=auth_header(developer_token))
assert resp.status_code == 403
# ══════════════════════════════════════════════════════════════
# 四、种子数据 V1 POST /api/v1/audit-rules/seed/{db_type}
# ══════════════════════════════════════════════════════════════
class TestSeedAuditRules:
"""POST /audit-rules/seed/{db_type} — 批量种子数据"""
@pytest.mark.asyncio
async def test_mysql种子数据创建成功(self, client, admin_token):
"""MySQL 种子数据可正常导入"""
resp = await client.post(f"{PREFIX}/seed/mysql", headers=auth_header(admin_token))
assert resp.status_code == 200
assert isinstance(resp.json(), list)
assert len(resp.json()) > 0
@pytest.mark.asyncio
async def test_postgres种子数据创建成功(self, client, admin_token):
"""PostgreSQL 种子数据可正常导入"""
resp = await client.post(f"{PREFIX}/seed/postgres", headers=auth_header(admin_token))
assert resp.status_code == 200
assert len(resp.json()) > 0
@pytest.mark.asyncio
async def test_redis种子数据创建成功(self, client, admin_token):
"""Redis 种子数据可正常导入"""
resp = await client.post(f"{PREFIX}/seed/redis", headers=auth_header(admin_token))
assert resp.status_code == 200
assert len(resp.json()) > 0
@pytest.mark.asyncio
async def test不支持的db_type返回400(self, client, admin_token):
"""不支持的数据库类型应返回 400"""
resp = await client.post(f"{PREFIX}/seed/oracle", headers=auth_header(admin_token))
assert resp.status_code == 400
assert "不支持的数据库类型" in resp.json()["detail"]
@pytest.mark.asyncio
async def test_dba无种子写入权限(self, client, dba_token):
"""dba 无写权限"""
resp = await client.post(f"{PREFIX}/seed/mysql", headers=auth_header(dba_token))
assert resp.status_code == 403
@pytest.mark.asyncio
async def test重复种子数据跳过已存在(self, client, admin_token):
"""第二次导入相同种子时,已存在的规则会被跳过"""
r1 = await client.post(f"{PREFIX}/seed/mysql", headers=auth_header(admin_token))
r2 = await client.post(f"{PREFIX}/seed/mysql", headers=auth_header(admin_token))
assert r1.status_code == 200
assert r2.status_code == 200
# 第二次不应创建新规则(列表应为空)
assert len(r2.json()) == 0
# ══════════════════════════════════════════════════════════════
# 五、获取单条 GET /api/v1/audit-rules/{rule_id}
# ══════════════════════════════════════════════════════════════
class TestGetAuditRule:
"""GET /audit-rules/{rule_id} — 获取单条"""
@pytest.mark.asyncio
async def test获取存在的规则(self, client, admin_token):
"""正常获取已创建的规则"""
create = await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "get-one"},
headers=auth_header(admin_token),
)
rule_id = create.json()["id"]
resp = await client.get(
f"{PREFIX}/{rule_id}", headers=auth_header(admin_token)
)
assert resp.status_code == 200
assert resp.json()["name"] == "get-one"
@pytest.mark.asyncio
async def test获取不存在的规则返回404(self, client, admin_token):
"""不存在的 ID 返回 404"""
resp = await client.get(f"{PREFIX}/99999", headers=auth_header(admin_token))
assert resp.status_code == 404
assert "不存在" in resp.json()["detail"]
@pytest.mark.asyncio
async def test_dba可获取规则(self, client, admin_token, dba_token):
"""dba 有只读权限"""
create = await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "dba-get"},
headers=auth_header(admin_token),
)
rule_id = create.json()["id"]
resp = await client.get(
f"{PREFIX}/{rule_id}", headers=auth_header(dba_token)
)
assert resp.status_code == 200
@pytest.mark.asyncio
async def test_developer不可获取规则(self, client, admin_token, developer_token):
"""developer 无权限"""
create = await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "dev-get"},
headers=auth_header(admin_token),
)
rule_id = create.json()["id"]
resp = await client.get(
f"{PREFIX}/{rule_id}", headers=auth_header(developer_token)
)
assert resp.status_code == 403
# ══════════════════════════════════════════════════════════════
# 六、更新 PUT /api/v1/audit-rules/{rule_id}
# ══════════════════════════════════════════════════════════════
class TestUpdateAuditRule:
"""PUT /audit-rules/{rule_id} — 更新规则"""
@pytest.mark.asyncio
async def test更新部分字段成功(self, client, admin_token):
"""只更新 severity 和 message"""
create = await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "update-partial"},
headers=auth_header(admin_token),
)
rule_id = create.json()["id"]
resp = await client.put(
f"{PREFIX}/{rule_id}",
json={"severity": "error", "message": "更新后的消息"},
headers=auth_header(admin_token),
)
assert resp.status_code == 200
data = resp.json()
assert data["severity"] == "error"
assert data["message"] == "更新后的消息"
# 未修改的字段保持不变
assert data["name"] == "update-partial"
assert data["rule_type"] == "performance"
@pytest.mark.asyncio
async def test更新不存在的规则返回404(self, client, admin_token):
"""更新不存在的 ID"""
resp = await client.put(
f"{PREFIX}/99999",
json={"severity": "info"},
headers=auth_header(admin_token),
)
assert resp.status_code == 404
@pytest.mark.asyncio
async def test_dba无更新权限返回403(self, client, admin_token, dba_token):
"""dba 无写权限"""
create = await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "dba-update"},
headers=auth_header(admin_token),
)
rule_id = create.json()["id"]
resp = await client.put(
f"{PREFIX}/{rule_id}",
json={"severity": "info"},
headers=auth_header(dba_token),
)
assert resp.status_code == 403
@pytest.mark.asyncio
async def test更新全部字段(self, client, admin_token):
"""一次更新所有可修改字段"""
create = await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "full-update"},
headers=auth_header(admin_token),
)
rule_id = create.json()["id"]
resp = await client.put(
f"{PREFIX}/{rule_id}",
json={
"name": "full-update-v2",
"description": "新描述",
"rule_type": "safety",
"severity": "error",
"db_type": "postgres",
"pattern": r"ALTER\s+TABLE",
"value": "200",
"is_enabled": False,
"message": "全新的消息",
},
headers=auth_header(admin_token),
)
assert resp.status_code == 200
data = resp.json()
assert data["name"] == "full-update-v2"
assert data["description"] == "新描述"
assert data["rule_type"] == "safety"
assert data["severity"] == "error"
assert data["db_type"] == "postgres"
assert data["pattern"] == r"ALTER\s+TABLE"
assert data["value"] == "200"
assert data["is_enabled"] is False
assert data["message"] == "全新的消息"
@pytest.mark.asyncio
async def test更新is_enabled字段(self, client, admin_token):
"""通过 PUT 单独更新 is_enabled"""
create = await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "toggle-via-put"},
headers=auth_header(admin_token),
)
rule_id = create.json()["id"]
assert create.json()["is_enabled"] is True
resp = await client.put(
f"{PREFIX}/{rule_id}",
json={"is_enabled": False},
headers=auth_header(admin_token),
)
assert resp.status_code == 200
assert resp.json()["is_enabled"] is False
# ══════════════════════════════════════════════════════════════
# 七、删除 DELETE /api/v1/audit-rules/{rule_id}
# ══════════════════════════════════════════════════════════════
class TestDeleteAuditRule:
"""DELETE /audit-rules/{rule_id} — 删除规则"""
@pytest.mark.asyncio
async def test删除成功(self, client, admin_token):
"""删除后 GET 应返回 404"""
create = await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "del-me"},
headers=auth_header(admin_token),
)
rule_id = create.json()["id"]
resp = await client.delete(
f"{PREFIX}/{rule_id}", headers=auth_header(admin_token)
)
assert resp.status_code == 200
assert resp.json()["message"] == "规则已删除"
# 确认已删除
get = await client.get(
f"{PREFIX}/{rule_id}", headers=auth_header(admin_token)
)
assert get.status_code == 404
@pytest.mark.asyncio
async def test删除不存在的规则返回404(self, client, admin_token):
"""删除不存在的 ID"""
resp = await client.delete(
f"{PREFIX}/99999", headers=auth_header(admin_token)
)
assert resp.status_code == 404
@pytest.mark.asyncio
async def test_dba无删除权限返回403(self, client, admin_token, dba_token):
"""dba 无删除权限"""
create = await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "dba-del"},
headers=auth_header(admin_token),
)
rule_id = create.json()["id"]
resp = await client.delete(
f"{PREFIX}/{rule_id}", headers=auth_header(dba_token)
)
assert resp.status_code == 403
# ══════════════════════════════════════════════════════════════
# 八、切换启用 POST /api/v1/audit-rules/{rule_id}/toggle
# ══════════════════════════════════════════════════════════════
class TestToggleAuditRule:
"""POST /audit-rules/{rule_id}/toggle — 切换启用/禁用"""
@pytest.mark.asyncio
async def test切换启用到禁用再恢复(self, client, admin_token):
"""连续 toggle 两次:启用→禁用→启用"""
create = await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "toggle-twice"},
headers=auth_header(admin_token),
)
rule_id = create.json()["id"]
assert create.json()["is_enabled"] is True
# 第一次 toggle:禁用
r1 = await client.post(
f"{PREFIX}/{rule_id}/toggle", headers=auth_header(admin_token)
)
assert r1.status_code == 200
assert r1.json()["is_enabled"] is False
# 第二次 toggle:恢复启用
r2 = await client.post(
f"{PREFIX}/{rule_id}/toggle", headers=auth_header(admin_token)
)
assert r2.status_code == 200
assert r2.json()["is_enabled"] is True
@pytest.mark.asyncio
async def test切换不存在的规则返回404(self, client, admin_token):
"""不存在的 ID 返回 404"""
resp = await client.post(
f"{PREFIX}/99999/toggle", headers=auth_header(admin_token)
)
assert resp.status_code == 404
@pytest.mark.asyncio
async def test_dba可切换规则(self, client, admin_token, dba_token):
"""dba 有 toggle 权限(只读类操作中 toggle 是特殊写操作,需确认)"""
create = await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "dba-toggle"},
headers=auth_header(admin_token),
)
rule_id = create.json()["id"]
resp = await client.post(
f"{PREFIX}/{rule_id}/toggle", headers=auth_header(dba_token)
)
# 端点 require_role("admin", "dba") — dba 有权限
assert resp.status_code == 200
assert resp.json()["is_enabled"] is False
@pytest.mark.asyncio
async def test_developer不可切换(self, client, admin_token, developer_token):
"""developer 无权限"""
create = await client.post(
PREFIX,
json={**VALID_RULE_PAYLOAD, "name": "dev-toggle"},
headers=auth_header(admin_token),
)
rule_id = create.json()["id"]
resp = await client.post(
f"{PREFIX}/{rule_id}/toggle", headers=auth_header(developer_token)
)
assert resp.status_code == 403
# ══════════════════════════════════════════════════════════════
# 九、V2 种子数据 POST /api/v1/audit-rules/seed-v2
# ══════════════════════════════════════════════════════════════
class TestSeedV2AuditRules:
"""POST /audit-rules/seed-v2 — V2 种子数据"""
@pytest.mark.asyncio
async def test_v2种子数据创建成功(self, client, admin_token):
"""V2 种子数据可正常导入"""
resp = await client.post(f"{PREFIX}/seed-v2", headers=auth_header(admin_token))
assert resp.status_code == 200
assert isinstance(resp.json(), list)
assert len(resp.json()) > 0
@pytest.mark.asyncio
async def test_v2种子数据去重(self, client, admin_token):
"""重复导入 V2 种子时应跳过已存在的规则"""
await client.post(f"{PREFIX}/seed-v2", headers=auth_header(admin_token))
resp = await client.post(f"{PREFIX}/seed-v2", headers=auth_header(admin_token))
assert resp.status_code == 200
# 第二次不会创建新规则
assert len(resp.json()) == 0
@pytest.mark.asyncio
async def test_dba无v2种子写入权限(self, client, dba_token):
"""dba 无写权限"""
resp = await client.post(f"{PREFIX}/seed-v2", headers=auth_header(dba_token))
assert resp.status_code == 403
@pytest.mark.asyncio
async def test_developer无v2种子权限(self, client, developer_token):
"""developer 无权限"""
resp = await client.post(f"{PREFIX}/seed-v2", headers=auth_header(developer_token))
assert resp.status_code == 403