- 新增 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 依赖
187 lines
6.3 KiB
Python
187 lines
6.3 KiB
Python
"""连接池管理 API 端点测试。"""
|
|
import pytest
|
|
from unittest.mock import patch, MagicMock, AsyncMock
|
|
from httpx import AsyncClient
|
|
|
|
from app.models.user import User
|
|
from app.models.instance import Instance
|
|
from app.core.security import hash_password, create_access_token
|
|
|
|
|
|
def auth_header(token: str) -> dict:
|
|
return {"Authorization": f"Bearer {token}"}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
class TestPoolStats:
|
|
"""GET /api/v1/pools/instances/{instance_id}/stats"""
|
|
|
|
async def test_pool_stats_initialized(
|
|
self, client: AsyncClient, admin_token: str, test_instance
|
|
):
|
|
mock_stats = MagicMock()
|
|
mock_stats.to_dict.return_value = {
|
|
"instance_id": test_instance.id,
|
|
"status": "active",
|
|
"total_connections": 5,
|
|
"active_connections": 2,
|
|
"idle_connections": 3,
|
|
}
|
|
with patch(
|
|
"app.api.v1.endpoints.pool.get_pool_manager"
|
|
) as mock_mgr:
|
|
mock_mgr.return_value.get_pool_stats.return_value = mock_stats
|
|
resp = await client.get(
|
|
f"/api/v1/pools/instances/{test_instance.id}/stats",
|
|
headers=auth_header(admin_token),
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "active"
|
|
assert data["total_connections"] == 5
|
|
|
|
async def test_pool_stats_not_initialized(
|
|
self, client: AsyncClient, admin_token: str, test_instance
|
|
):
|
|
with patch(
|
|
"app.api.v1.endpoints.pool.get_pool_manager"
|
|
) as mock_mgr:
|
|
mock_mgr.return_value.get_pool_stats.return_value = None
|
|
resp = await client.get(
|
|
f"/api/v1/pools/instances/{test_instance.id}/stats",
|
|
headers=auth_header(admin_token),
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "not_initialized"
|
|
|
|
async def test_pool_stats_instance_not_found(
|
|
self, client: AsyncClient, admin_token: str
|
|
):
|
|
resp = await client.get(
|
|
"/api/v1/pools/instances/9999/stats",
|
|
headers=auth_header(admin_token),
|
|
)
|
|
assert resp.status_code == 404
|
|
|
|
async def test_pool_stats_forbidden_developer(
|
|
self, client: AsyncClient, developer_token: str, test_instance
|
|
):
|
|
resp = await client.get(
|
|
f"/api/v1/pools/instances/{test_instance.id}/stats",
|
|
headers=auth_header(developer_token),
|
|
)
|
|
assert resp.status_code == 403
|
|
|
|
async def test_pool_stats_dba_allowed(
|
|
self, client: AsyncClient, dba_token: str, test_instance
|
|
):
|
|
with patch(
|
|
"app.api.v1.endpoints.pool.get_pool_manager"
|
|
) as mock_mgr:
|
|
mock_mgr.return_value.get_pool_stats.return_value = None
|
|
resp = await client.get(
|
|
f"/api/v1/pools/instances/{test_instance.id}/stats",
|
|
headers=auth_header(dba_token),
|
|
)
|
|
assert resp.status_code == 200
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
class TestResetPool:
|
|
"""POST /api/v1/pools/instances/{instance_id}/reset"""
|
|
|
|
async def test_reset_pool_success(
|
|
self, client: AsyncClient, admin_token: str, test_instance
|
|
):
|
|
with patch(
|
|
"app.api.v1.endpoints.pool.get_pool_manager"
|
|
) as mock_mgr:
|
|
mock_mgr.return_value.reset_pool = AsyncMock()
|
|
resp = await client.post(
|
|
f"/api/v1/pools/instances/{test_instance.id}/reset",
|
|
headers=auth_header(admin_token),
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "已重置" in data["message"]
|
|
assert data["instance_id"] == test_instance.id
|
|
|
|
async def test_reset_pool_dba_allowed(
|
|
self, client: AsyncClient, dba_token: str, test_instance
|
|
):
|
|
with patch(
|
|
"app.api.v1.endpoints.pool.get_pool_manager"
|
|
) as mock_mgr:
|
|
mock_mgr.return_value.reset_pool = AsyncMock()
|
|
resp = await client.post(
|
|
f"/api/v1/pools/instances/{test_instance.id}/reset",
|
|
headers=auth_header(dba_token),
|
|
)
|
|
assert resp.status_code == 200
|
|
|
|
async def test_reset_pool_forbidden_developer(
|
|
self, client: AsyncClient, developer_token: str, test_instance
|
|
):
|
|
resp = await client.post(
|
|
f"/api/v1/pools/instances/{test_instance.id}/reset",
|
|
headers=auth_header(developer_token),
|
|
)
|
|
assert resp.status_code == 403
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
class TestPoolsOverview:
|
|
"""GET /api/v1/pools/overview"""
|
|
|
|
async def test_pools_overview_empty(
|
|
self, client: AsyncClient, admin_token: str
|
|
):
|
|
with patch(
|
|
"app.api.v1.endpoints.pool.get_pool_manager"
|
|
) as mock_mgr:
|
|
mock_mgr.return_value.get_all_stats.return_value = {}
|
|
resp = await client.get(
|
|
"/api/v1/pools/overview",
|
|
headers=auth_header(admin_token),
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["total_pools"] == 0
|
|
assert data["pools"] == []
|
|
|
|
async def test_pools_overview_with_pools(
|
|
self, client: AsyncClient, admin_token: str
|
|
):
|
|
mock_stats = MagicMock()
|
|
mock_stats.to_dict.return_value = {
|
|
"instance_id": 1,
|
|
"status": "active",
|
|
"total_connections": 10,
|
|
}
|
|
with patch(
|
|
"app.api.v1.endpoints.pool.get_pool_manager"
|
|
) as mock_mgr:
|
|
mock_mgr.return_value.get_all_stats.return_value = {1: mock_stats}
|
|
resp = await client.get(
|
|
"/api/v1/pools/overview",
|
|
headers=auth_header(admin_token),
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["total_pools"] == 1
|
|
assert len(data["pools"]) == 1
|
|
|
|
async def test_pools_overview_unauthorized(self, client: AsyncClient):
|
|
resp = await client.get("/api/v1/pools/overview")
|
|
assert resp.status_code == 401
|
|
|
|
async def test_pools_overview_forbidden_guest(
|
|
self, client: AsyncClient, guest_token: str
|
|
):
|
|
resp = await client.get(
|
|
"/api/v1/pools/overview",
|
|
headers=auth_header(guest_token),
|
|
)
|
|
assert resp.status_code == 403
|