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:
1 parent
622923821b
commit
88e5f4e7ee
71 files changed
+20390
-64
No files matched your search
@@ -0,0 +1,768 @@
|
||||
"""查询历史和快捷查询 持久化测试 — 验证 SQLite 存储。
|
||||
|
||||
测试范围:
|
||||
- 查询历史记录和查看(CRUD + 分页 + 过滤)
|
||||
- 收藏切换
|
||||
- 快捷查询 CRUD
|
||||
- 查询统计
|
||||
- 权限控制
|
||||
- 预置模板
|
||||
"""
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.security import create_access_token, hash_password
|
||||
from app.models.user import User
|
||||
from app.models.query_history import QueryHistory, QuickQueryModel
|
||||
from app.services.query_history_service import QueryHistoryService
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def admin_headers(admin_token: str) -> dict:
|
||||
return {"Authorization": f"Bearer {admin_token}"}
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def developer_headers(developer_token: str) -> dict:
|
||||
return {"Authorization": f"Bearer {developer_token}"}
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def guest_headers(guest_token: str) -> dict:
|
||||
return {"Authorization": f"Bearer {guest_token}"}
|
||||
|
||||
|
||||
# ── 查询历史记录 ──
|
||||
|
||||
|
||||
class TestQueryHistoryRecord:
|
||||
"""查询历史记录功能测试。"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_record_query(self, db: AsyncSession):
|
||||
"""测试记录一条查询。"""
|
||||
entry = await QueryHistoryService.record_query(
|
||||
db=db,
|
||||
user_id=1,
|
||||
username="admin",
|
||||
instance_id=1,
|
||||
instance_name="测试MySQL",
|
||||
db_type="mysql",
|
||||
database_name="test_db",
|
||||
query_content="SELECT * FROM users LIMIT 10",
|
||||
query_type="SELECT",
|
||||
execution_time_ms=50,
|
||||
result_rows=10,
|
||||
)
|
||||
await db.commit()
|
||||
assert entry.id > 0
|
||||
assert entry.user_id == 1
|
||||
assert entry.db_type == "mysql"
|
||||
assert entry.query_type == "SELECT"
|
||||
assert entry.execution_time_ms == 50
|
||||
assert entry.result_rows == 10
|
||||
assert entry.is_success is True
|
||||
assert entry.error_message is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_record_failed_query(self, db: AsyncSession):
|
||||
"""测试记录失败的查询。"""
|
||||
entry = await QueryHistoryService.record_query(
|
||||
db=db,
|
||||
user_id=1,
|
||||
username="admin",
|
||||
instance_id=1,
|
||||
instance_name="测试MySQL",
|
||||
db_type="mysql",
|
||||
database_name="test_db",
|
||||
query_content="SELECT * FROM nonexistent",
|
||||
query_type="SELECT",
|
||||
execution_time_ms=10,
|
||||
result_rows=0,
|
||||
is_success=False,
|
||||
error_message="Table doesn't exist",
|
||||
)
|
||||
await db.commit()
|
||||
assert entry.is_success is False
|
||||
assert entry.error_message == "Table doesn't exist"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_record_multiple_db_types(self, db: AsyncSession):
|
||||
"""测试记录不同数据库类型的查询。"""
|
||||
for db_type, query, q_type in [
|
||||
("mysql", "SELECT 1", "SELECT"),
|
||||
("postgres", "SELECT version()", "SELECT"),
|
||||
("redis", "GET mykey", "GET"),
|
||||
("mongo", '{"collection": "users", "operation": "find"}', "find"),
|
||||
]:
|
||||
entry = await QueryHistoryService.record_query(
|
||||
db=db,
|
||||
user_id=1,
|
||||
username="admin",
|
||||
instance_id=1,
|
||||
instance_name="测试",
|
||||
db_type=db_type,
|
||||
database_name="test",
|
||||
query_content=query,
|
||||
query_type=q_type,
|
||||
execution_time_ms=10,
|
||||
result_rows=1,
|
||||
)
|
||||
await db.commit()
|
||||
assert entry.db_type == db_type
|
||||
assert entry.query_type == q_type
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_record_query_long_content_truncated(self, db: AsyncSession):
|
||||
"""测试超长查询内容被截断。"""
|
||||
long_sql = "SELECT * FROM t WHERE id = " + "1" * 3000
|
||||
entry = await QueryHistoryService.record_query(
|
||||
db=db,
|
||||
user_id=1,
|
||||
username="admin",
|
||||
instance_id=1,
|
||||
instance_name="测试",
|
||||
db_type="mysql",
|
||||
database_name="test",
|
||||
query_content=long_sql,
|
||||
query_type="SELECT",
|
||||
execution_time_ms=10,
|
||||
result_rows=0,
|
||||
)
|
||||
await db.commit()
|
||||
assert len(entry.query_content) == 2000
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_record_query_max_history_per_user(self, db: AsyncSession):
|
||||
"""测试每个用户最多保留500条记录。"""
|
||||
# 创建超过限制的记录
|
||||
for i in range(510):
|
||||
await QueryHistoryService.record_query(
|
||||
db=db,
|
||||
user_id=99,
|
||||
username="test_user",
|
||||
instance_id=1,
|
||||
instance_name="测试",
|
||||
db_type="mysql",
|
||||
database_name="test",
|
||||
query_content=f"SELECT {i}",
|
||||
query_type="SELECT",
|
||||
execution_time_ms=i,
|
||||
result_rows=0,
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
from sqlalchemy import select, func
|
||||
count_stmt = select(func.count()).select_from(QueryHistory).where(
|
||||
QueryHistory.user_id == 99
|
||||
)
|
||||
total = (await db.execute(count_stmt)).scalar()
|
||||
assert total <= 500
|
||||
|
||||
|
||||
# ── 查询历史查看 ──
|
||||
|
||||
|
||||
class TestQueryHistoryView:
|
||||
"""查询历史查看功能测试。"""
|
||||
|
||||
async def _setup_history(self, db: AsyncSession):
|
||||
"""准备测试数据。"""
|
||||
entries = [
|
||||
("SELECT * FROM users", "mysql", 1, 50, 10),
|
||||
("SELECT * FROM orders", "mysql", 1, 30, 20),
|
||||
("GET mykey", "redis", 2, 5, 1),
|
||||
("SELECT version()", "postgres", 3, 15, 1),
|
||||
]
|
||||
for sql, db_type, inst_id, time_ms, rows in entries:
|
||||
await QueryHistoryService.record_query(
|
||||
db=db,
|
||||
user_id=1,
|
||||
username="admin",
|
||||
instance_id=inst_id,
|
||||
instance_name=f"测试{db_type}",
|
||||
db_type=db_type,
|
||||
database_name="test",
|
||||
query_content=sql,
|
||||
query_type="SELECT" if db_type != "redis" else "GET",
|
||||
execution_time_ms=time_ms,
|
||||
result_rows=rows,
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_history_default(self, db: AsyncSession):
|
||||
"""测试默认查看历史。"""
|
||||
await self._setup_history(db)
|
||||
result = await QueryHistoryService.get_history(db=db, user_id=1)
|
||||
assert result["total"] == 4
|
||||
assert result["page"] == 1
|
||||
assert result["page_size"] == 20
|
||||
assert len(result["items"]) == 4
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_history_filter_by_db_type(self, db: AsyncSession):
|
||||
"""测试按数据库类型筛选。"""
|
||||
await self._setup_history(db)
|
||||
result = await QueryHistoryService.get_history(db=db, user_id=1, db_type="mysql")
|
||||
assert result["total"] == 2
|
||||
for item in result["items"]:
|
||||
assert item["db_type"] == "mysql"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_history_filter_by_instance(self, db: AsyncSession):
|
||||
"""测试按实例ID筛选。"""
|
||||
await self._setup_history(db)
|
||||
result = await QueryHistoryService.get_history(db=db, user_id=1, instance_id=2)
|
||||
assert result["total"] == 1
|
||||
assert result["items"][0]["db_type"] == "redis"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_history_filter_by_keyword(self, db: AsyncSession):
|
||||
"""测试按关键词筛选。"""
|
||||
await self._setup_history(db)
|
||||
result = await QueryHistoryService.get_history(db=db, user_id=1, keyword="users")
|
||||
assert result["total"] == 1
|
||||
assert "users" in result["items"][0]["query_content"].lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_history_pagination(self, db: AsyncSession):
|
||||
"""测试分页。"""
|
||||
await self._setup_history(db)
|
||||
result = await QueryHistoryService.get_history(db=db, user_id=1, page=1, page_size=2)
|
||||
assert result["total"] == 4
|
||||
assert len(result["items"]) == 2
|
||||
|
||||
result2 = await QueryHistoryService.get_history(db=db, user_id=1, page=2, page_size=2)
|
||||
assert result2["total"] == 4
|
||||
assert len(result2["items"]) == 2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_history_other_user_empty(self, db: AsyncSession):
|
||||
"""测试查看其他用户的历史为空。"""
|
||||
await self._setup_history(db)
|
||||
result = await QueryHistoryService.get_history(db=db, user_id=999)
|
||||
assert result["total"] == 0
|
||||
assert result["items"] == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_history_sorted_by_time_desc(self, db: AsyncSession):
|
||||
"""测试历史按时间倒序排列。"""
|
||||
await self._setup_history(db)
|
||||
result = await QueryHistoryService.get_history(db=db, user_id=1)
|
||||
times = [item["created_at"] for item in result["items"]]
|
||||
assert times == sorted(times, reverse=True)
|
||||
|
||||
|
||||
# ── 收藏功能 ──
|
||||
|
||||
|
||||
class TestQueryHistoryFavorite:
|
||||
"""查询收藏功能测试。"""
|
||||
|
||||
async def _setup_for_favorite(self, db: AsyncSession):
|
||||
entry = await QueryHistoryService.record_query(
|
||||
db=db,
|
||||
user_id=1, username="admin", instance_id=1,
|
||||
instance_name="测试", db_type="mysql", database_name="test",
|
||||
query_content="SELECT 1", query_type="SELECT",
|
||||
execution_time_ms=10, result_rows=1,
|
||||
)
|
||||
await db.commit()
|
||||
return entry.id
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_toggle_favorite_add(self, db: AsyncSession):
|
||||
"""测试添加收藏。"""
|
||||
entry_id = await self._setup_for_favorite(db)
|
||||
is_fav = await QueryHistoryService.toggle_favorite(db=db, user_id=1, history_id=entry_id)
|
||||
await db.commit()
|
||||
assert is_fav is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_toggle_favorite_remove(self, db: AsyncSession):
|
||||
"""测试取消收藏。"""
|
||||
entry_id = await self._setup_for_favorite(db)
|
||||
await QueryHistoryService.toggle_favorite(db=db, user_id=1, history_id=entry_id)
|
||||
await db.commit()
|
||||
is_fav = await QueryHistoryService.toggle_favorite(db=db, user_id=1, history_id=entry_id)
|
||||
await db.commit()
|
||||
assert is_fav is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_favorites(self, db: AsyncSession):
|
||||
"""测试获取收藏列表。"""
|
||||
entry_id = await self._setup_for_favorite(db)
|
||||
await QueryHistoryService.toggle_favorite(db=db, user_id=1, history_id=entry_id)
|
||||
await db.commit()
|
||||
|
||||
result = await QueryHistoryService.get_favorites(db=db, user_id=1)
|
||||
assert result["total"] == 1
|
||||
assert result["items"][0]["id"] == entry_id
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_favorites_empty(self, db: AsyncSession):
|
||||
"""测试无收藏时返回空列表。"""
|
||||
result = await QueryHistoryService.get_favorites(db=db, user_id=1)
|
||||
assert result["total"] == 0
|
||||
assert result["items"] == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_favorites_per_user(self, db: AsyncSession):
|
||||
"""测试收藏是用户隔离的。"""
|
||||
entry_id = await self._setup_for_favorite(db)
|
||||
await QueryHistoryService.toggle_favorite(db=db, user_id=1, history_id=entry_id)
|
||||
await db.commit()
|
||||
|
||||
result = await QueryHistoryService.get_favorites(db=db, user_id=999)
|
||||
assert result["total"] == 0
|
||||
|
||||
|
||||
# ── 快捷查询 ──
|
||||
|
||||
|
||||
class TestQuickQueries:
|
||||
"""快捷查询功能测试。"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_quick_query(self, db: AsyncSession):
|
||||
"""测试创建快捷查询。"""
|
||||
qq = await QueryHistoryService.create_quick_query(
|
||||
db=db,
|
||||
name="查看活跃用户",
|
||||
description="查看最近活跃的用户列表",
|
||||
query_content="SELECT * FROM users WHERE last_login > DATE_SUB(NOW(), INTERVAL 7 DAY)",
|
||||
db_type="mysql",
|
||||
database_pattern="*",
|
||||
is_shared=False,
|
||||
created_by=1,
|
||||
created_by_name="admin",
|
||||
)
|
||||
await db.commit()
|
||||
assert qq.id > 0
|
||||
assert qq.name == "查看活跃用户"
|
||||
assert qq.is_shared is False
|
||||
assert qq.created_by == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_quick_queries_personal(self, db: AsyncSession):
|
||||
"""测试获取个人快捷查询。"""
|
||||
await QueryHistoryService.create_quick_query(
|
||||
db=db, name="个人查询", description="", query_content="SELECT 1",
|
||||
db_type="mysql", database_pattern="*",
|
||||
is_shared=False, created_by=1, created_by_name="admin",
|
||||
)
|
||||
await QueryHistoryService.create_quick_query(
|
||||
db=db, name="他人查询", description="", query_content="SELECT 2",
|
||||
db_type="mysql", database_pattern="*",
|
||||
is_shared=False, created_by=2, created_by_name="dba",
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
# 用户1只能看到自己的 + 共享的
|
||||
items = await QueryHistoryService.get_quick_queries(db=db, user_id=1)
|
||||
assert len(items) == 1
|
||||
assert items[0]["name"] == "个人查询"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_quick_queries_shared(self, db: AsyncSession):
|
||||
"""测试获取共享快捷查询。"""
|
||||
await QueryHistoryService.create_quick_query(
|
||||
db=db, name="共享查询", description="", query_content="SELECT 1",
|
||||
db_type="mysql", database_pattern="*",
|
||||
is_shared=True, created_by=2, created_by_name="dba",
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
items = await QueryHistoryService.get_quick_queries(db=db, user_id=1)
|
||||
assert len(items) == 1
|
||||
assert items[0]["is_shared"] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_quick_queries_filter_db_type(self, db: AsyncSession):
|
||||
"""测试按数据库类型筛选快捷查询。"""
|
||||
await QueryHistoryService.create_quick_query(
|
||||
db=db, name="MySQL查询", description="", query_content="SELECT 1",
|
||||
db_type="mysql", database_pattern="*",
|
||||
is_shared=True, created_by=1, created_by_name="admin",
|
||||
)
|
||||
await QueryHistoryService.create_quick_query(
|
||||
db=db, name="Redis查询", description="", query_content="GET key",
|
||||
db_type="redis", database_pattern="*",
|
||||
is_shared=True, created_by=1, created_by_name="admin",
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
items = await QueryHistoryService.get_quick_queries(db=db, user_id=1, db_type="redis")
|
||||
assert len(items) == 1
|
||||
assert items[0]["db_type"] == "redis"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_quick_queries_filter_keyword(self, db: AsyncSession):
|
||||
"""测试按关键词筛选快捷查询。"""
|
||||
await QueryHistoryService.create_quick_query(
|
||||
db=db, name="查看活跃用户", description="", query_content="SELECT 1",
|
||||
db_type="mysql", database_pattern="*",
|
||||
is_shared=True, created_by=1, created_by_name="admin",
|
||||
)
|
||||
await QueryHistoryService.create_quick_query(
|
||||
db=db, name="查看慢查询", description="", query_content="SELECT 2",
|
||||
db_type="mysql", database_pattern="*",
|
||||
is_shared=True, created_by=1, created_by_name="admin",
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
items = await QueryHistoryService.get_quick_queries(db=db, user_id=1, keyword="活跃")
|
||||
assert len(items) == 1
|
||||
assert "活跃" in items[0]["name"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_use_quick_query(self, db: AsyncSession):
|
||||
"""测试使用快捷查询。"""
|
||||
qq = await QueryHistoryService.create_quick_query(
|
||||
db=db, name="测试", description="", query_content="SELECT 1",
|
||||
db_type="mysql", database_pattern="*",
|
||||
is_shared=True, created_by=1, created_by_name="admin",
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
content = await QueryHistoryService.use_quick_query(db, qq.id)
|
||||
await db.commit()
|
||||
assert content == "SELECT 1"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_use_quick_query_increments_count(self, db: AsyncSession):
|
||||
"""测试使用快捷查询增加使用次数。"""
|
||||
qq = await QueryHistoryService.create_quick_query(
|
||||
db=db, name="测试", description="", query_content="SELECT 1",
|
||||
db_type="mysql", database_pattern="*",
|
||||
is_shared=True, created_by=1, created_by_name="admin",
|
||||
)
|
||||
await db.commit()
|
||||
assert qq.usage_count == 0
|
||||
|
||||
await QueryHistoryService.use_quick_query(db, qq.id)
|
||||
await db.commit()
|
||||
assert qq.usage_count == 1
|
||||
|
||||
await QueryHistoryService.use_quick_query(db, qq.id)
|
||||
await db.commit()
|
||||
assert qq.usage_count == 2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_use_quick_query_not_found(self, db: AsyncSession):
|
||||
"""测试使用不存在的快捷查询。"""
|
||||
content = await QueryHistoryService.use_quick_query(db, 99999)
|
||||
assert content is None
|
||||
|
||||
|
||||
# ── 预置模板 ──
|
||||
|
||||
|
||||
class TestDefaultQuickQueries:
|
||||
"""预置快捷查询模板测试。"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_default_templates_exist(self, db: AsyncSession):
|
||||
"""测试预置模板存在。"""
|
||||
await QueryHistoryService.seed_default_quick_queries(db)
|
||||
await db.commit()
|
||||
|
||||
items = await QueryHistoryService.get_quick_queries(db=db, user_id=1)
|
||||
# 预置了10个模板
|
||||
assert len(items) == 10
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_default_templates_cover_db_types(self, db: AsyncSession):
|
||||
"""测试预置模板覆盖所有数据库类型。"""
|
||||
await QueryHistoryService.seed_default_quick_queries(db)
|
||||
await db.commit()
|
||||
|
||||
items = await QueryHistoryService.get_quick_queries(db=db, user_id=1)
|
||||
db_types = {item["db_type"] for item in items}
|
||||
assert "mysql" in db_types
|
||||
assert "redis" in db_types
|
||||
assert "postgres" in db_types
|
||||
assert "mongo" in db_types
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_default_templates_all_shared(self, db: AsyncSession):
|
||||
"""测试预置模板都是共享的。"""
|
||||
await QueryHistoryService.seed_default_quick_queries(db)
|
||||
await db.commit()
|
||||
|
||||
items = await QueryHistoryService.get_quick_queries(db=db, user_id=1)
|
||||
for item in items:
|
||||
assert item["is_shared"] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_default_templates_system_created(self, db: AsyncSession):
|
||||
"""测试预置模板由系统创建。"""
|
||||
await QueryHistoryService.seed_default_quick_queries(db)
|
||||
await db.commit()
|
||||
|
||||
items = await QueryHistoryService.get_quick_queries(db=db, user_id=1)
|
||||
for item in items:
|
||||
assert item["created_by_name"] == "系统"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_seed_idempotent(self, db: AsyncSession):
|
||||
"""测试 seed 是幂等的(多次调用不会重复插入)。"""
|
||||
await QueryHistoryService.seed_default_quick_queries(db)
|
||||
await db.commit()
|
||||
await QueryHistoryService.seed_default_quick_queries(db)
|
||||
await db.commit()
|
||||
|
||||
items = await QueryHistoryService.get_quick_queries(db=db, user_id=1)
|
||||
assert len(items) == 10
|
||||
|
||||
|
||||
# ── 查询统计 ──
|
||||
|
||||
|
||||
class TestQueryStatistics:
|
||||
"""查询统计功能测试。"""
|
||||
|
||||
async def _setup_stats(self, db: AsyncSession):
|
||||
for i in range(5):
|
||||
await QueryHistoryService.record_query(
|
||||
db=db, user_id=1, username="admin", instance_id=1,
|
||||
instance_name="测试", db_type="mysql", database_name="test",
|
||||
query_content=f"SELECT * FROM table_{i} WHERE id = 1",
|
||||
query_type="SELECT",
|
||||
execution_time_ms=10 + i * 10,
|
||||
result_rows=i + 1,
|
||||
)
|
||||
for i in range(3):
|
||||
await QueryHistoryService.record_query(
|
||||
db=db, user_id=1, username="admin", instance_id=2,
|
||||
instance_name="测试Redis", db_type="redis", database_name="db0",
|
||||
query_content="GET mykey",
|
||||
query_type="GET",
|
||||
execution_time_ms=5,
|
||||
result_rows=1,
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_statistics_basic(self, db: AsyncSession):
|
||||
"""测试基本统计。"""
|
||||
await self._setup_stats(db)
|
||||
stats = await QueryHistoryService.get_query_statistics(db=db, user_id=1)
|
||||
|
||||
assert stats["total_queries"] == 8
|
||||
assert stats["avg_execution_time_ms"] > 0
|
||||
assert stats["success_rate"] == 100.0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_statistics_db_type_distribution(self, db: AsyncSession):
|
||||
"""测试数据库类型分布统计。"""
|
||||
await self._setup_stats(db)
|
||||
stats = await QueryHistoryService.get_query_statistics(db=db, user_id=1)
|
||||
|
||||
assert "mysql" in stats["db_type_distribution"]
|
||||
assert "redis" in stats["db_type_distribution"]
|
||||
assert stats["db_type_distribution"]["mysql"] == 5
|
||||
assert stats["db_type_distribution"]["redis"] == 3
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_statistics_daily_trend(self, db: AsyncSession):
|
||||
"""测试每日趋势。"""
|
||||
await self._setup_stats(db)
|
||||
stats = await QueryHistoryService.get_query_statistics(db=db, user_id=1)
|
||||
|
||||
assert "daily_trend" in stats
|
||||
assert len(stats["daily_trend"]) == 7
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_statistics_empty(self, db: AsyncSession):
|
||||
"""测试无记录时返回提示。"""
|
||||
stats = await QueryHistoryService.get_query_statistics(db=db, user_id=999)
|
||||
assert "message" in stats
|
||||
assert stats["message"] == "暂无查询记录"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_statistics_top_tables(self, db: AsyncSession):
|
||||
"""测试高频表统计。"""
|
||||
await self._setup_stats(db)
|
||||
stats = await QueryHistoryService.get_query_statistics(db=db, user_id=1)
|
||||
|
||||
assert "top_tables" in stats
|
||||
# 应该能从SQL中提取表名
|
||||
assert len(stats["top_tables"]) > 0
|
||||
|
||||
|
||||
# ── API端点集成测试 ──
|
||||
|
||||
|
||||
class TestQueryHistoryAPI:
|
||||
"""查询历史 API 端点集成测试。"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_my_history_unauthorized(self, client):
|
||||
"""测试未认证访问历史。"""
|
||||
resp = await client.get("/api/v1/query-history/my")
|
||||
assert resp.status_code == 401
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_my_history_empty(self, client, admin_headers, db):
|
||||
"""测试空历史记录。"""
|
||||
resp = await client.get("/api/v1/query-history/my", headers=admin_headers)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["total"] == 0
|
||||
assert data["items"] == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_my_history_with_data(self, client, admin_headers, db, admin_user):
|
||||
"""测试有数据的历史记录。"""
|
||||
await QueryHistoryService.record_query(
|
||||
db=db, user_id=admin_user.id, username="admin", instance_id=1,
|
||||
instance_name="测试", db_type="mysql", database_name="test",
|
||||
query_content="SELECT 1", query_type="SELECT",
|
||||
execution_time_ms=10, result_rows=1,
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
resp = await client.get("/api/v1/query-history/my", headers=admin_headers)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["total"] >= 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_my_history_filter_db_type(self, client, admin_headers, db, admin_user):
|
||||
"""测试按类型筛选。"""
|
||||
await QueryHistoryService.record_query(
|
||||
db=db, user_id=admin_user.id, username="admin", instance_id=1,
|
||||
instance_name="测试", db_type="mysql", database_name="test",
|
||||
query_content="SELECT 1", query_type="SELECT",
|
||||
execution_time_ms=10, result_rows=1,
|
||||
)
|
||||
await QueryHistoryService.record_query(
|
||||
db=db, user_id=admin_user.id, username="admin", instance_id=2,
|
||||
instance_name="测试Redis", db_type="redis", database_name="db0",
|
||||
query_content="GET key", query_type="GET",
|
||||
execution_time_ms=5, result_rows=1,
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
resp = await client.get(
|
||||
"/api/v1/query-history/my?db_type=mysql",
|
||||
headers=admin_headers,
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["total"] == 1
|
||||
assert data["items"][0]["db_type"] == "mysql"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_toggle_favorite_api(self, client, admin_headers, db, admin_user):
|
||||
"""测试收藏API。"""
|
||||
entry = await QueryHistoryService.record_query(
|
||||
db=db, user_id=admin_user.id, username="admin", instance_id=1,
|
||||
instance_name="测试", db_type="mysql", database_name="test",
|
||||
query_content="SELECT 1", query_type="SELECT",
|
||||
execution_time_ms=10, result_rows=1,
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
resp = await client.post(
|
||||
f"/api/v1/query-history/{entry.id}/favorite",
|
||||
headers=admin_headers,
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["is_favorite"] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_favorites_api(self, client, admin_headers, db):
|
||||
"""测试收藏列表API。"""
|
||||
resp = await client.get("/api/v1/query-history/favorites", headers=admin_headers)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert "total" in data
|
||||
assert "items" in data
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_statistics_api(self, client, admin_headers, db):
|
||||
"""测试统计API。"""
|
||||
resp = await client.get("/api/v1/query-history/statistics", headers=admin_headers)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert "message" in data or "total_queries" in data
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_quick_queries_list_api(self, client, admin_headers, db):
|
||||
"""测试快捷查询列表API。"""
|
||||
await QueryHistoryService.seed_default_quick_queries(db)
|
||||
await db.commit()
|
||||
|
||||
resp = await client.get("/api/v1/query-history/quick-queries", headers=admin_headers)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["total"] == 10
|
||||
assert len(data["items"]) == 10
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_quick_queries_create_api(self, client, admin_headers, db):
|
||||
"""测试创建快捷查询API。"""
|
||||
resp = await client.post(
|
||||
"/api/v1/query-history/quick-queries",
|
||||
params={
|
||||
"name": "我的查询",
|
||||
"query_content": "SELECT * FROM users",
|
||||
"db_type": "mysql",
|
||||
"description": "测试快捷查询",
|
||||
"is_shared": False,
|
||||
},
|
||||
headers=admin_headers,
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["name"] == "我的查询"
|
||||
assert "id" in data
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_quick_queries_filter_api(self, client, admin_headers, db):
|
||||
"""测试快捷查询筛选API。"""
|
||||
await QueryHistoryService.seed_default_quick_queries(db)
|
||||
await db.commit()
|
||||
|
||||
resp = await client.get(
|
||||
"/api/v1/query-history/quick-queries?db_type=redis",
|
||||
headers=admin_headers,
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
for item in data["items"]:
|
||||
assert item["db_type"] == "redis"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_guest_can_view_own_history(self, client, guest_headers, db):
|
||||
"""测试访客可以查看自己的历史(但无权执行查询)。"""
|
||||
resp = await client.get("/api/v1/query-history/my", headers=guest_headers)
|
||||
assert resp.status_code == 200
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pagination_parameters(self, client, admin_headers, db, admin_user):
|
||||
"""测试分页参数。"""
|
||||
for i in range(15):
|
||||
await QueryHistoryService.record_query(
|
||||
db=db, user_id=admin_user.id, username="admin", instance_id=1,
|
||||
instance_name="测试", db_type="mysql", database_name="test",
|
||||
query_content=f"SELECT {i}", query_type="SELECT",
|
||||
execution_time_ms=10, result_rows=1,
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
resp = await client.get(
|
||||
"/api/v1/query-history/my?page=1&page_size=5",
|
||||
headers=admin_headers,
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["total"] == 15
|
||||
assert len(data["items"]) == 5
|
||||
assert data["page"] == 1
|
||||
assert data["page_size"] == 5
|
||||
Reference in new issue
Block a user