fix: 审核引擎优化 + 执行结果结构化展示
后端审核规则修复: - Redis SEC-001: SET/DEL等常规命令不再误报AUTH要求,改为仅对管理级命令WARNING - Redis SEC-003: 降级为INFO,排除已由CMD-001拦截的FLUSHDB/FLUSHALL - Redis key提取: 排除CONFIG/ACL等管理命令,不再把子命令参数误认为key名 - MySQL SEC-006: 新增GRANT ALL/REVOKE ALL全局权限拦截 - MySQL SEC-007: 新增LOAD DATA/LOAD XML拦截 - MySQL SEC-008: 新增SLEEP()/BENCHMARK()时间盲注拦截 - MySQL SEC-009: 新增INTO OUTFILE/DUMPFILE文件写入拦截 前端执行结果结构化: - TicketDetailPage执行结果从原始JSON改为结构化展示 - 解析JSON展示: 状态徽章+影响行数+耗时+验证状态 - Errors/Warnings逐条渲染: 红色错误框+黄色警告框+具体消息 - 删除废弃的executeStats/renderExecuteStats 其他: 审核规则6分组/慢查询移除/查询数据重命名/AuditLogs修复 测试: 1040 passed, TSC零错误
This commit is contained in:
1 parent
9f56e866f2
commit
4ac2528f8e
19 files changed
+1202
-325
No files matched your search
@@ -0,0 +1,204 @@
|
||||
"""数据库类型说明 API 测试 — db_types 端点。
|
||||
|
||||
覆盖:
|
||||
- GET /db-types — 列表概览
|
||||
- GET /db-types/{db_type} — 详细信息
|
||||
- GET /db-types/{db_type}/commands — 常用命令
|
||||
- GET /db-types/{db_type}/tips — 使用技巧
|
||||
- 404 错误处理
|
||||
"""
|
||||
import pytest
|
||||
from httpx import AsyncClient, ASGITransport
|
||||
|
||||
from app.main import app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def auth_headers():
|
||||
"""返回认证头。"""
|
||||
from app.core.security import create_access_token
|
||||
token = create_access_token({"sub": "1", "role": "admin"})
|
||||
return {"Authorization": "Bearer " + token}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_db_types(client, admin_token):
|
||||
"""测试获取所有数据库类型概览。"""
|
||||
resp = await client.get("/api/v1/db-types", headers={"Authorization": "Bearer " + admin_token})
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert "items" in data
|
||||
assert data["total"] == 4
|
||||
|
||||
types = {item["type"] for item in data["items"]}
|
||||
assert types == {"mysql", "postgres", "redis", "mongo"}
|
||||
|
||||
# 检查每个条目的结构
|
||||
for item in data["items"]:
|
||||
assert "type" in item
|
||||
assert "name" in item
|
||||
assert "icon" in item
|
||||
assert "description" in item
|
||||
assert "category" in item
|
||||
assert "version" in item
|
||||
assert "supported_operations" in item
|
||||
assert item["supported_operations"] > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_mysql_detail(client, admin_token):
|
||||
"""测试获取 MySQL 详细信息。"""
|
||||
resp = await client.get("/api/v1/db-types/mysql", headers={"Authorization": "Bearer " + admin_token})
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["type"] == "mysql"
|
||||
assert data["name"] == "MySQL"
|
||||
assert data["category"] == "关系型"
|
||||
assert len(data["supported_query_types"]) >= 4
|
||||
assert len(data["security_rules"]) >= 1
|
||||
assert len(data["tips"]) >= 1
|
||||
assert len(data["common_commands"]) >= 1
|
||||
assert len(data["limitations"]) >= 1
|
||||
|
||||
# 检查查询类型结构
|
||||
for qt in data["supported_query_types"]:
|
||||
assert "type" in qt
|
||||
assert "description" in qt
|
||||
assert "example" in qt
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_postgres_detail(client, admin_token):
|
||||
"""测试获取 PostgreSQL 详细信息。"""
|
||||
resp = await client.get("/api/v1/db-types/postgres", headers={"Authorization": "Bearer " + admin_token})
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["type"] == "postgres"
|
||||
assert data["name"] == "PostgreSQL"
|
||||
assert data["category"] == "关系型"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_redis_detail(client, admin_token):
|
||||
"""测试获取 Redis 详细信息。"""
|
||||
resp = await client.get("/api/v1/db-types/redis", headers={"Authorization": "Bearer " + admin_token})
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["type"] == "redis"
|
||||
assert data["name"] == "Redis"
|
||||
assert data["category"] == "键值型"
|
||||
assert len(data["supported_query_types"]) >= 5
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_mongo_detail(client, admin_token):
|
||||
"""测试获取 MongoDB 详细信息。"""
|
||||
resp = await client.get("/api/v1/db-types/mongo", headers={"Authorization": "Bearer " + admin_token})
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["type"] == "mongo"
|
||||
assert data["name"] == "MongoDB"
|
||||
assert data["category"] == "文档型"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unknown_db_type(client, admin_token):
|
||||
"""测试获取不支持的数据库类型 — 404。"""
|
||||
resp = await client.get("/api/v1/db-types/oracle", headers={"Authorization": "Bearer " + admin_token})
|
||||
|
||||
assert resp.status_code == 404
|
||||
assert "不支持的数据库类型" in resp.json()["detail"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_commands(client, admin_token):
|
||||
"""测试获取常用命令列表。"""
|
||||
resp = await client.get("/api/v1/db-types/mysql/commands", headers={"Authorization": "Bearer " + admin_token})
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert "items" in data
|
||||
assert data["total"] >= 5
|
||||
|
||||
for cmd in data["items"]:
|
||||
assert "name" in cmd
|
||||
assert "command" in cmd
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_commands_redis(client, admin_token):
|
||||
"""测试获取 Redis 常用命令列表。"""
|
||||
resp = await client.get("/api/v1/db-types/redis/commands", headers={"Authorization": "Bearer " + admin_token})
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["total"] >= 5
|
||||
|
||||
cmd_names = {cmd["name"] for cmd in data["items"]}
|
||||
assert "查看键数量" in cmd_names or "查看服务器信息" in cmd_names
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_commands_unknown(client, admin_token):
|
||||
"""测试获取不支持类型的命令 — 404。"""
|
||||
resp = await client.get("/api/v1/db-types/cassandra/commands", headers={"Authorization": "Bearer " + admin_token})
|
||||
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_tips(client, admin_token):
|
||||
"""测试获取使用技巧。"""
|
||||
resp = await client.get("/api/v1/db-types/mysql/tips", headers={"Authorization": "Bearer " + admin_token})
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert "items" in data
|
||||
assert data["total"] >= 3
|
||||
# 每条 tip 是字符串
|
||||
for tip in data["items"]:
|
||||
assert isinstance(tip, str)
|
||||
assert len(tip) > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_tips_mongo(client, admin_token):
|
||||
"""测试获取 MongoDB 使用技巧。"""
|
||||
resp = await client.get("/api/v1/db-types/mongo/tips", headers={"Authorization": "Bearer " + admin_token})
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["total"] >= 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_tips_unknown(client, admin_token):
|
||||
"""测试获取不支持类型的技巧 — 404。"""
|
||||
resp = await client.get("/api/v1/db-types/sqlite/tips", headers={"Authorization": "Bearer " + admin_token})
|
||||
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_db_types_require_auth(client):
|
||||
"""测试未认证请求被拒绝。"""
|
||||
resp = await client.get("/api/v1/db-types")
|
||||
|
||||
assert resp.status_code in (401, 403)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_structure_consistency(client, admin_token):
|
||||
"""测试所有类型的列表条目结构一致。"""
|
||||
resp = await client.get("/api/v1/db-types", headers={"Authorization": "Bearer " + admin_token})
|
||||
|
||||
data = resp.json()
|
||||
required_fields = {"type", "name", "icon", "description", "category", "version", "supported_operations", "security_rules_count"}
|
||||
for item in data["items"]:
|
||||
assert required_fields.issubset(set(item.keys())), \
|
||||
"Missing fields: " + str(required_fields - set(item.keys()))
|
||||
Reference in new issue
Block a user