Files
opsxsql/tests/test_audit_rule.py
T
logan 88e5f4e7ee 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 依赖
2026-05-25 23:32:15 +08:00

760 lines
30 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""审核规则管理端点全面测试。
覆盖所有 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