"""连接池管理端点完整测试 — pool.py 覆盖端点: GET /api/v1/pools/instances/{instance_id}/stats 连接池状态 POST /api/v1/pools/instances/{instance_id}/reset 重置连接池 GET /api/v1/pools/overview 所有连接池概览 """ import pytest from unittest.mock import patch, AsyncMock, MagicMock from httpx import AsyncClient from tests.conftest import auth_header # ═══════════════════════════════════════════════════════════════ # GET /pools/instances/{instance_id}/stats — 连接池状态 # ═══════════════════════════════════════════════════════════════ class TestPoolStats: async def test_stats_not_initialized( self, client: AsyncClient, admin_token, test_instance ): """连接池未初始化时返回 not_initialized。""" with patch("app.api.v1.endpoints.pool.get_pool_manager") as mock_pm: manager = MagicMock() manager.get_pool_stats.return_value = None mock_pm.return_value = manager resp = await client.get( f"/api/v1/pools/instances/{test_instance.id}/stats", headers=auth_header(admin_token), ) assert resp.status_code == 200 body = resp.json() assert body["status"] == "not_initialized" assert body["instance_id"] == test_instance.id async def test_stats_with_active_pool( self, client: AsyncClient, admin_token, test_instance ): """已初始化的连接池返回统计数据。""" with patch("app.api.v1.endpoints.pool.get_pool_manager") as mock_pm: stats = MagicMock() stats.to_dict.return_value = { "instance_id": test_instance.id, "active": 3, "idle": 2, "max_size": 10, } manager = MagicMock() manager.get_pool_stats.return_value = stats mock_pm.return_value = manager resp = await client.get( f"/api/v1/pools/instances/{test_instance.id}/stats", headers=auth_header(admin_token), ) assert resp.status_code == 200 body = resp.json() assert body["active"] == 3 assert body["idle"] == 2 async def test_stats_instance_not_found( self, client: AsyncClient, admin_token ): """数据源不存在返回 404。""" resp = await client.get( "/api/v1/pools/instances/9999/stats", headers=auth_header(admin_token), ) assert resp.status_code == 404 async def test_stats_dba_allowed( self, client: AsyncClient, dba_token, test_instance ): """DBA 角色可以查看连接池状态。""" with patch("app.api.v1.endpoints.pool.get_pool_manager") as mock_pm: stats = MagicMock() stats.to_dict.return_value = {"active": 0, "idle": 0} manager = MagicMock() manager.get_pool_stats.return_value = stats mock_pm.return_value = manager resp = await client.get( f"/api/v1/pools/instances/{test_instance.id}/stats", headers=auth_header(dba_token), ) assert resp.status_code == 200 async def test_stats_developer_forbidden( self, client: AsyncClient, developer_token, test_instance ): """developer 角色无权查看连接池状态。""" 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_stats_guest_forbidden( self, client: AsyncClient, guest_token, test_instance ): """guest 角色无权查看连接池状态。""" resp = await client.get( f"/api/v1/pools/instances/{test_instance.id}/stats", headers=auth_header(guest_token), ) assert resp.status_code == 403 async def test_stats_unauthenticated( self, client: AsyncClient, test_instance ): """未认证请求返回 401。""" resp = await client.get( f"/api/v1/pools/instances/{test_instance.id}/stats", ) assert resp.status_code == 401 async def test_stats_returns_not_initialized_message( self, client: AsyncClient, admin_token, test_instance ): """未初始化时返回友好提示信息。""" with patch("app.api.v1.endpoints.pool.get_pool_manager") as mock_pm: manager = MagicMock() manager.get_pool_stats.return_value = None mock_pm.return_value = manager resp = await client.get( f"/api/v1/pools/instances/{test_instance.id}/stats", headers=auth_header(admin_token), ) body = resp.json() assert "message" in body assert "首次查询" in body["message"] or "初始化" in body["message"] # ═══════════════════════════════════════════════════════════════ # POST /pools/instances/{instance_id}/reset — 重置连接池 # ═══════════════════════════════════════════════════════════════ class TestPoolReset: async def test_reset_success( self, client: AsyncClient, admin_token, test_instance ): """管理员重置连接池成功。""" with patch("app.api.v1.endpoints.pool.get_pool_manager") as mock_pm: manager = MagicMock() manager.reset_pool = AsyncMock() mock_pm.return_value = manager resp = await client.post( f"/api/v1/pools/instances/{test_instance.id}/reset", headers=auth_header(admin_token), ) assert resp.status_code == 200 body = resp.json() assert "已重置" in body["message"] assert body["instance_id"] == test_instance.id async def test_reset_dba_allowed( self, client: AsyncClient, dba_token, test_instance ): """DBA 角色可以重置连接池。""" with patch("app.api.v1.endpoints.pool.get_pool_manager") as mock_pm: manager = MagicMock() manager.reset_pool = AsyncMock() mock_pm.return_value = manager 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_developer_forbidden( self, client: AsyncClient, developer_token, test_instance ): """developer 角色无权重置连接池。""" resp = await client.post( f"/api/v1/pools/instances/{test_instance.id}/reset", headers=auth_header(developer_token), ) assert resp.status_code == 403 async def test_reset_guest_forbidden( self, client: AsyncClient, guest_token, test_instance ): """guest 角色无权重置连接池。""" resp = await client.post( f"/api/v1/pools/instances/{test_instance.id}/reset", headers=auth_header(guest_token), ) assert resp.status_code == 403 async def test_reset_unauthenticated( self, client: AsyncClient, test_instance ): """未认证请求返回 401。""" resp = await client.post( f"/api/v1/pools/instances/{test_instance.id}/reset", ) assert resp.status_code == 401 async def test_reset_calls_manager_reset_pool( self, client: AsyncClient, admin_token, test_instance ): """验证 reset_pool 被正确调用。""" with patch("app.api.v1.endpoints.pool.get_pool_manager") as mock_pm: manager = MagicMock() manager.reset_pool = AsyncMock() mock_pm.return_value = manager await client.post( f"/api/v1/pools/instances/{test_instance.id}/reset", headers=auth_header(admin_token), ) manager.reset_pool.assert_awaited_once_with(test_instance.id) # ═══════════════════════════════════════════════════════════════ # GET /pools/overview — 所有连接池概览 # ═══════════════════════════════════════════════════════════════ class TestPoolsOverview: async def test_overview_empty(self, client: AsyncClient, admin_token): """无活跃连接池时返回空概览。""" with patch("app.api.v1.endpoints.pool.get_pool_manager") as mock_pm: manager = MagicMock() manager.get_all_stats.return_value = {} mock_pm.return_value = manager resp = await client.get( "/api/v1/pools/overview", headers=auth_header(admin_token), ) assert resp.status_code == 200 body = resp.json() assert body["total_pools"] == 0 assert body["pools"] == [] async def test_overview_with_pools(self, client: AsyncClient, admin_token): """有连接池时返回正确概览。""" with patch("app.api.v1.endpoints.pool.get_pool_manager") as mock_pm: stats1 = MagicMock() stats1.to_dict.return_value = {"instance_id": 1, "active": 2} stats2 = MagicMock() stats2.to_dict.return_value = {"instance_id": 2, "active": 1} manager = MagicMock() manager.get_all_stats.return_value = {1: stats1, 2: stats2} mock_pm.return_value = manager resp = await client.get( "/api/v1/pools/overview", headers=auth_header(admin_token), ) assert resp.status_code == 200 body = resp.json() assert body["total_pools"] == 2 assert len(body["pools"]) == 2 async def test_overview_dba_allowed(self, client: AsyncClient, dba_token): """DBA 角色可以查看连接池概览。""" with patch("app.api.v1.endpoints.pool.get_pool_manager") as mock_pm: manager = MagicMock() manager.get_all_stats.return_value = {} mock_pm.return_value = manager resp = await client.get( "/api/v1/pools/overview", headers=auth_header(dba_token), ) assert resp.status_code == 200 async def test_overview_developer_forbidden( self, client: AsyncClient, developer_token ): """developer 角色无权查看连接池概览。""" resp = await client.get( "/api/v1/pools/overview", headers=auth_header(developer_token), ) assert resp.status_code == 403 async def test_overview_guest_forbidden( self, client: AsyncClient, guest_token ): """guest 角色无权查看连接池概览。""" resp = await client.get( "/api/v1/pools/overview", headers=auth_header(guest_token), ) assert resp.status_code == 403 async def test_overview_unauthenticated(self, client: AsyncClient): """未认证请求返回 401。""" resp = await client.get("/api/v1/pools/overview") assert resp.status_code == 401 async def test_overview_single_pool_data( self, client: AsyncClient, admin_token ): """单个连接池数据验证。""" with patch("app.api.v1.endpoints.pool.get_pool_manager") as mock_pm: stats = MagicMock() stats.to_dict.return_value = { "instance_id": 42, "active": 5, "idle": 3, "max_size": 10, } manager = MagicMock() manager.get_all_stats.return_value = {42: stats} mock_pm.return_value = manager resp = await client.get( "/api/v1/pools/overview", headers=auth_header(admin_token), ) body = resp.json() assert body["total_pools"] == 1 assert body["pools"][0]["instance_id"] == 42 assert body["pools"][0]["active"] == 5