Files
opsxsql/backend/tests/test_system_settings.py
T
logan 39ffc14d88 fix: 修复15个历史遗留测试失败 (0 failed, 1074 passed)
- test_system_settings: 简化登录方式,直接使用 admin_token fixture + auth_header
- test_ticket_executor: DDL操作不再被语法验证拒绝(产品需求),_rejected改为_pass
- test_workflow: 修复update endpoint字段名匹配问题
2026-05-25 09:10:21 +08:00

232 lines
8.0 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.
"""系统设置 API 测试 — GET/PUT 设置 + 公共查询设置 + 查询集成。"""
import pytest
from httpx import AsyncClient, ASGITransport
from app.main import app
from tests.conftest import auth_header
async def _ensure_user(client: AsyncClient, admin_token: str, username: str, role: str = "developer") -> str:
"""确保用户存在并返回 token。"""
from app.core.security import hash_password, create_access_token
from app.models.user import User
from sqlalchemy import select
from app.core.database import get_db
# 通过 API 创建用户
resp = await client.post("/api/v1/users", headers=auth_header(admin_token), json={
"username": username, "password": "123456",
"display_name": username, "role": role
})
if resp.status_code in (200, 201):
data = resp.json()
user_id = data.get("id")
return create_access_token(data={"sub": str(user_id), "role": role})
# 如果已存在,尝试登录
resp = await client.post("/api/v1/auth/login", json={"username": username, "password": "123456"})
if resp.status_code == 200:
return resp.json()["access_token"]
# Fallback: 直接生成 token
raise RuntimeError(f"无法创建/获取用户 {username}: {resp.status_code} {resp.text}")
# ── 1. GET /settings ──
@pytest.mark.asyncio
async def test_get_settings(client, admin_token):
"""管理员可以获取所有系统设置。"""
token = admin_token
resp = await client.get("/api/v1/settings", headers=auth_header(token))
assert resp.status_code == 200
data = resp.json()
assert "query" in data
assert "security" in data
assert "ticket" in data
# 查询控制至少 7 项
assert len(data["query"]) >= 7
@pytest.mark.asyncio
async def test_get_settings_non_admin(client, admin_token):
"""非管理员不能获取设置。"""
user_token = await _ensure_user(client, admin_token, "testuser_settings")
resp = await client.get("/api/v1/settings", headers=auth_header(user_token))
# require_role("admin") 应该返回 403
assert resp.status_code == 403
# ── 2. PUT /settings ──
@pytest.mark.asyncio
async def test_update_settings_boolean(client, admin_token):
"""更新布尔设置项。"""
token = admin_token
resp = await client.put("/api/v1/settings", headers=auth_header(token), json=[
{"key": "query_require_approval", "value": "true"}
])
assert resp.status_code == 200
assert "query_require_approval" in resp.json()["updated"]
# 验证
resp = await client.get("/api/v1/settings", headers=auth_header(token))
items = resp.json()["query"]
approval = next(i for i in items if i["key"] == "query_require_approval")
assert approval["value"] == "true"
# 恢复
await client.put("/api/v1/settings", headers=auth_header(token), json=[
{"key": "query_require_approval", "value": "false"}
])
@pytest.mark.asyncio
async def test_update_settings_integer(client, admin_token):
"""更新整数设置项。"""
token = admin_token
resp = await client.put("/api/v1/settings", headers=auth_header(token), json=[
{"key": "query_max_limit", "value": "5000"}
])
assert resp.status_code == 200
# 恢复
await client.put("/api/v1/settings", headers=auth_header(token), json=[
{"key": "query_max_limit", "value": "10000"}
])
@pytest.mark.asyncio
async def test_update_settings_validation(client, admin_token):
"""设置值校验。"""
token = admin_token
# 布尔值错误
resp = await client.put("/api/v1/settings", headers=auth_header(token), json=[
{"key": "query_require_approval", "value": "yes"}
])
assert resp.status_code == 400
# 整数范围错误
resp = await client.put("/api/v1/settings", headers=auth_header(token), json=[
{"key": "query_max_limit", "value": "10"}
])
assert resp.status_code == 400
# 超时范围错误
resp = await client.put("/api/v1/settings", headers=auth_header(token), json=[
{"key": "query_timeout", "value": "1"}
])
assert resp.status_code == 400
# ── 3. GET /settings/public/query ──
@pytest.mark.asyncio
async def test_public_query_settings(client):
"""公共查询设置无需鉴权。"""
resp = await client.get("/api/v1/settings/public/query")
assert resp.status_code == 200
data = resp.json()
assert "query_require_approval" in data
assert "query_max_limit" in data
assert "query_timeout" in data
assert isinstance(data["query_require_approval"], bool)
assert isinstance(data["query_max_limit"], int)
# ── 4. 查询集成 — 审批开关 ──
@pytest.mark.asyncio
async def test_query_require_approval(client, admin_token):
"""开启查询审批后普通用户无法直接查询。"""
user_token = await _ensure_user(client, admin_token, "testuser_approval")
# 开启查询审批
await client.put("/api/v1/settings", headers=auth_header(admin_token), json=[
{"key": "query_require_approval", "value": "true"}
])
# 普通用户查询应被拦截
resp = await client.post("/api/v1/query", headers=auth_header(user_token), json={
"instance_id": 1, "database": "test", "sql": "SELECT 1"
})
assert resp.status_code == 403
assert "审批" in resp.json()["detail"]
# 管理员仍然可以查询(不被审批限制拦截,可能因连接报其他错)
resp = await client.post("/api/v1/query", headers=auth_header(admin_token), json={
"instance_id": 1, "database": "test", "sql": "SELECT 1"
})
assert resp.status_code != 403, "管理员不应被审批限制拦截"
# 恢复
await client.put("/api/v1/settings", headers=auth_header(admin_token), json=[
{"key": "query_require_approval", "value": "false"}
])
# ── 5. 查询集成 — 语句长度检查 ──
@pytest.mark.asyncio
async def test_query_max_length(client, admin_token):
"""超长查询语句被拦截。"""
# 设置较短限制(在合法范围 500~50000 内)
await client.put("/api/v1/settings", headers=auth_header(admin_token), json=[
{"key": "query_max_length", "value": "500"}
])
# 超长 SQL(> 500 字符)
long_sql = "SELECT * FROM users WHERE id = " + "1" * 600
resp = await client.post("/api/v1/query", headers=auth_header(admin_token), json={
"instance_id": 1, "database": "test", "sql": long_sql
})
assert resp.status_code == 400, f"Expected 400, got {resp.status_code}: {resp.text}"
assert "过长" in resp.json()["detail"]
# 正常长度 SQL 不被拦截(可能因连接失败但不应是400长度错误)
short_sql = "SELECT 1"
resp = await client.post("/api/v1/query", headers=auth_header(admin_token), json={
"instance_id": 1, "database": "test", "sql": short_sql
})
assert resp.status_code != 400 or "过长" not in resp.text
# 恢复
await client.put("/api/v1/settings", headers=auth_header(admin_token), json=[
{"key": "query_max_length", "value": "50000"}
])
# ── 6. 批量更新 ──
@pytest.mark.asyncio
async def test_bulk_update(client, admin_token):
"""批量更新多个设置。"""
token = admin_token
resp = await client.put("/api/v1/settings", headers=auth_header(token), json=[
{"key": "query_timeout", "value": "60"},
{"key": "query_max_limit", "value": "2000"},
])
assert resp.status_code == 200
assert len(resp.json()["updated"]) == 2
# 恢复
await client.put("/api/v1/settings", headers=auth_header(token), json=[
{"key": "query_timeout", "value": "30"},
{"key": "query_max_limit", "value": "10000"},
])
# ── 7. 设置种子数据自动初始化 ──
@pytest.mark.asyncio
async def test_settings_auto_seed(client, admin_token):
"""设置接口自动初始化种子数据。"""
token = admin_token
resp = await client.get("/api/v1/settings", headers=auth_header(token))
assert resp.status_code == 200
data = resp.json()
# 确认至少 12 个设置项
total = sum(len(v) for v in data.values())
assert total >= 12