sync: 同步 sql-platform-pro 最新代码 — 权限体系安全修复 v0.8.1
- P0: 实例访问控制全线接入权限组 (829测试) - P1: permissions字段激活 + 操作级权限检查 (843测试) - 前端: 删除数据权限页面, 权限组合并为唯一入口 - 新增: CodeMirror编辑器 + 查询模板 + 慢查询分析 - 新增: MongoDB管理页面 + Dashboard增强 - 修复: Pydantic V2迁移 + 前端TS错误修复
This commit is contained in:
1 parent
6fcd9465cf
commit
4c9427e546
105 files changed
+23680
-471
No files matched your search
@@ -0,0 +1,182 @@
|
||||
"""回滚 SQL API 测试 — 覆盖 generate-rollback 和 preview 端点。"""
|
||||
import pytest
|
||||
from unittest.mock import patch, AsyncMock, MagicMock
|
||||
from httpx import AsyncClient
|
||||
|
||||
from tests.conftest import auth_header
|
||||
|
||||
|
||||
async def _create_ticket(client: AsyncClient, admin_token: str, instance_id: int) -> dict:
|
||||
"""Helper: create a ticket and return the response body."""
|
||||
payload = {
|
||||
"title": "测试工单-回滚验证",
|
||||
"instance_id": instance_id,
|
||||
"target_database": "test_db",
|
||||
"sql_content": "UPDATE users SET name='test' WHERE id=1",
|
||||
"reason": "执行回滚测试用例",
|
||||
"priority": "medium",
|
||||
"db_type": "mysql",
|
||||
}
|
||||
resp = await client.post(
|
||||
"/api/v1/tickets", json=payload, headers=auth_header(admin_token)
|
||||
)
|
||||
return resp
|
||||
|
||||
|
||||
# ────────────────────────── Generate Rollback Tests ──────────────────────────
|
||||
|
||||
|
||||
class TestGenerateRollback:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_success(self, client, admin_token, test_instance):
|
||||
"""生成回滚 SQL — mock RollbackGenerator 以避免真实连接。"""
|
||||
ticket_resp = await _create_ticket(client, admin_token, test_instance.id)
|
||||
assert ticket_resp.status_code == 201
|
||||
ticket_id = ticket_resp.json()["id"]
|
||||
|
||||
with patch("app.api.v1.endpoints.rollback.RollbackGenerator") as MockGen:
|
||||
instance_mock = MagicMock()
|
||||
instance_mock.generate = AsyncMock(return_value="SELECT * FROM users WHERE id=1")
|
||||
MockGen.return_value = instance_mock
|
||||
|
||||
resp = await client.post(
|
||||
f"/api/v1/tickets/{ticket_id}/generate-rollback",
|
||||
json={"auto_save": False},
|
||||
headers=auth_header(admin_token),
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
body = resp.json()
|
||||
assert body["ticket_id"] == ticket_id
|
||||
assert "UPDATE" in body["original_sql"]
|
||||
assert body["rollback_sql"] == "SELECT * FROM users WHERE id=1"
|
||||
assert body["saved"] is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_with_auto_save(self, client, admin_token, test_instance):
|
||||
"""auto_save=True 应保存回滚 SQL 到工单。"""
|
||||
ticket_resp = await _create_ticket(client, admin_token, test_instance.id)
|
||||
ticket_id = ticket_resp.json()["id"]
|
||||
|
||||
with patch("app.api.v1.endpoints.rollback.RollbackGenerator") as MockGen:
|
||||
instance_mock = MagicMock()
|
||||
instance_mock.generate = AsyncMock(return_value="DELETE FROM users WHERE id=1 AND name='test'")
|
||||
MockGen.return_value = instance_mock
|
||||
|
||||
resp = await client.post(
|
||||
f"/api/v1/tickets/{ticket_id}/generate-rollback",
|
||||
json={"auto_save": True},
|
||||
headers=auth_header(admin_token),
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["saved"] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_ticket_not_found(self, client, admin_token):
|
||||
resp = await client.post(
|
||||
"/api/v1/tickets/9999/generate-rollback",
|
||||
json={"auto_save": False},
|
||||
headers=auth_header(admin_token),
|
||||
)
|
||||
assert resp.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_developer_forbidden(self, client, developer_token, test_instance):
|
||||
resp = await client.post(
|
||||
"/api/v1/tickets/1/generate-rollback",
|
||||
json={"auto_save": False},
|
||||
headers=auth_header(developer_token),
|
||||
)
|
||||
assert resp.status_code == 403
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_guest_forbidden(self, client, guest_token):
|
||||
resp = await client.post(
|
||||
"/api/v1/tickets/1/generate-rollback",
|
||||
json={"auto_save": False},
|
||||
headers=auth_header(guest_token),
|
||||
)
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
# ────────────────────────── Preview Rollback Tests ──────────────────────────
|
||||
|
||||
|
||||
class TestPreviewRollback:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_preview_success(self, client, admin_token, test_instance):
|
||||
with patch("app.api.v1.endpoints.rollback.RollbackGenerator") as MockGen:
|
||||
instance_mock = MagicMock()
|
||||
instance_mock.preview = AsyncMock(return_value="SELECT * FROM users WHERE id=1")
|
||||
MockGen.return_value = instance_mock
|
||||
|
||||
resp = await client.post(
|
||||
"/api/v1/rollback/preview",
|
||||
json={
|
||||
"instance_id": test_instance.id,
|
||||
"database": "test_db",
|
||||
"sql": "UPDATE users SET name='x' WHERE id=1",
|
||||
},
|
||||
headers=auth_header(admin_token),
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
body = resp.json()
|
||||
assert "UPDATE" in body["original_sql"]
|
||||
assert body["rollback_sql"] == "SELECT * FROM users WHERE id=1"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_preview_instance_not_found(self, client, admin_token):
|
||||
with patch("app.api.v1.endpoints.rollback.RollbackGenerator") as MockGen:
|
||||
MockGen.return_value = MagicMock()
|
||||
resp = await client.post(
|
||||
"/api/v1/rollback/preview",
|
||||
json={
|
||||
"instance_id": 9999,
|
||||
"database": "test_db",
|
||||
"sql": "DELETE FROM t WHERE id=1",
|
||||
},
|
||||
headers=auth_header(admin_token),
|
||||
)
|
||||
assert resp.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_preview_any_authenticated_user(self, client, developer_token, test_instance):
|
||||
"""preview 允许所有认证用户 (get_current_user)。"""
|
||||
with patch("app.api.v1.endpoints.rollback.RollbackGenerator") as MockGen:
|
||||
instance_mock = MagicMock()
|
||||
instance_mock.preview = AsyncMock(return_value="-- 回滚SQL预览")
|
||||
MockGen.return_value = instance_mock
|
||||
|
||||
resp = await client.post(
|
||||
"/api/v1/rollback/preview",
|
||||
json={
|
||||
"instance_id": test_instance.id,
|
||||
"database": "test_db",
|
||||
"sql": "UPDATE t SET a=1 WHERE id=1",
|
||||
},
|
||||
headers=auth_header(developer_token),
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_preview_no_auth_401(self, client):
|
||||
resp = await client.post(
|
||||
"/api/v1/rollback/preview",
|
||||
json={
|
||||
"instance_id": 1,
|
||||
"database": "test_db",
|
||||
"sql": "UPDATE t SET a=1",
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 401
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_preview_validation_error(self, client, admin_token):
|
||||
"""缺少必填字段 → 422。"""
|
||||
resp = await client.post(
|
||||
"/api/v1/rollback/preview",
|
||||
json={"instance_id": 1},
|
||||
headers=auth_header(admin_token),
|
||||
)
|
||||
assert resp.status_code == 422
|
||||
Reference in new issue
Block a user