1. 新增channels模块集成测试目录与基础fixture 2. 为capability、doctor、reports、dashboard、directory、webhook、wizard、health、analytics、allowlist、config等路由编写完整的鉴权、参数校验与异常场景测试 3. 修复基础集成测试setup,新增external/scheduler/channel数据库schema初始化步骤
270 lines
8.7 KiB
Python
270 lines
8.7 KiB
Python
"""Integration tests for channels plugin_router endpoints.
|
||
|
||
覆盖插件管理域(PLG-QUERY-01/02 + PLG-LIFE-01~07 + PLG-INSTALL +
|
||
PLG-UNINSTALL-FILE + PLG-CONFIG + PLG-BATCH-LIFE)的鉴权矩阵与
|
||
非存在插件 404 错误路径。install / uninstall 需超级管理员权限,
|
||
其余端点要求管理员。生命周期操作以 ``NON_EXISTENT_PLUGIN_ID`` 触发
|
||
``PLUGIN_NOT_FOUND`` → 404。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import httpx
|
||
import pytest
|
||
|
||
from .conftest import BASE_URL, NON_EXISTENT_PLUGIN_ID
|
||
|
||
pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
|
||
|
||
PLUGINS_URL = f"{BASE_URL}/plugins"
|
||
|
||
|
||
# =============================================================================
|
||
# === Auth three-tier for GET /plugins ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_list_plugins_requires_auth(test_client: httpx.AsyncClient):
|
||
# Act
|
||
response = await test_client.get(PLUGINS_URL)
|
||
# Assert
|
||
assert response.status_code == 401
|
||
|
||
|
||
async def test_list_plugins_requires_admin(
|
||
test_client: httpx.AsyncClient, standard_user
|
||
):
|
||
# Act
|
||
response = await test_client.get(PLUGINS_URL, headers=standard_user["headers"])
|
||
# Assert
|
||
assert response.status_code == 403
|
||
|
||
|
||
async def test_admin_can_list_plugins(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act
|
||
response = await test_client.get(PLUGINS_URL, headers=admin_headers)
|
||
# Assert
|
||
assert response.status_code == 200, response.text
|
||
|
||
payload = response.json()
|
||
assert payload["success"] is True
|
||
assert isinstance(payload["data"], list)
|
||
|
||
|
||
# =============================================================================
|
||
# === POST /plugins/install — superadmin only ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_install_plugin_requires_auth(test_client: httpx.AsyncClient):
|
||
# Arrange
|
||
body = {"source_type": "path", "source": "/tmp/nonexistent_plugin"}
|
||
# Act
|
||
response = await test_client.post(f"{PLUGINS_URL}/install", json=body)
|
||
# Assert
|
||
assert response.status_code == 401
|
||
|
||
|
||
async def test_install_plugin_requires_superadmin_for_admin(
|
||
test_client: httpx.AsyncClient, admin_headers
|
||
):
|
||
# Arrange
|
||
body = {"source_type": "path", "source": "/tmp/nonexistent_plugin"}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PLUGINS_URL}/install", json=body, headers=admin_headers
|
||
)
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === DELETE /plugins/{plugin_id} — superadmin only ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_uninstall_plugin_requires_superadmin_for_admin(
|
||
test_client: httpx.AsyncClient, admin_headers
|
||
):
|
||
# Act — admin(非 superadmin)应被拒绝
|
||
response = await test_client.delete(
|
||
f"{PLUGINS_URL}/{NON_EXISTENT_PLUGIN_ID}", headers=admin_headers
|
||
)
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === GET /plugins/{plugin_id} — non-existent (404) ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_get_plugin_returns_404_for_non_existent(
|
||
test_client: httpx.AsyncClient, admin_headers
|
||
):
|
||
# Act
|
||
response = await test_client.get(
|
||
f"{PLUGINS_URL}/{NON_EXISTENT_PLUGIN_ID}", headers=admin_headers
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === GET/PUT /plugins/{plugin_id}/config — non-existent (404) ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_get_plugin_config_returns_404_for_non_existent(
|
||
test_client: httpx.AsyncClient, admin_headers
|
||
):
|
||
# Act
|
||
response = await test_client.get(
|
||
f"{PLUGINS_URL}/{NON_EXISTENT_PLUGIN_ID}/config", headers=admin_headers
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_update_plugin_config_returns_404_for_non_existent(
|
||
test_client: httpx.AsyncClient, admin_headers
|
||
):
|
||
# Arrange — 合法请求体,确保非 422 而走到 use case 触发 404
|
||
body = {"config": {"key": "value"}, "apply_mode": "hot"}
|
||
# Act
|
||
response = await test_client.put(
|
||
f"{PLUGINS_URL}/{NON_EXISTENT_PLUGIN_ID}/config", json=body, headers=admin_headers
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === Lifecycle endpoints — non-existent plugin (404) ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_load_plugin_returns_404_for_non_existent(
|
||
test_client: httpx.AsyncClient, admin_headers
|
||
):
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PLUGINS_URL}/{NON_EXISTENT_PLUGIN_ID}/load", headers=admin_headers
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_start_plugin_returns_404_for_non_existent(
|
||
test_client: httpx.AsyncClient, admin_headers
|
||
):
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PLUGINS_URL}/{NON_EXISTENT_PLUGIN_ID}/start", headers=admin_headers
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_pause_plugin_returns_404_for_non_existent(
|
||
test_client: httpx.AsyncClient, admin_headers
|
||
):
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PLUGINS_URL}/{NON_EXISTENT_PLUGIN_ID}/pause", headers=admin_headers
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_resume_plugin_returns_404_for_non_existent(
|
||
test_client: httpx.AsyncClient, admin_headers
|
||
):
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PLUGINS_URL}/{NON_EXISTENT_PLUGIN_ID}/resume", headers=admin_headers
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_stop_plugin_returns_404_for_non_existent(
|
||
test_client: httpx.AsyncClient, admin_headers
|
||
):
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PLUGINS_URL}/{NON_EXISTENT_PLUGIN_ID}/stop", headers=admin_headers
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_unload_plugin_returns_404_for_non_existent(
|
||
test_client: httpx.AsyncClient, admin_headers
|
||
):
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PLUGINS_URL}/{NON_EXISTENT_PLUGIN_ID}/unload", headers=admin_headers
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_reload_plugin_returns_404_for_non_existent(
|
||
test_client: httpx.AsyncClient, admin_headers
|
||
):
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PLUGINS_URL}/{NON_EXISTENT_PLUGIN_ID}/reload", headers=admin_headers
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === POST /plugins/batch/start — empty plugin_ids ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_batch_start_plugins_with_empty_plugin_ids(
|
||
test_client: httpx.AsyncClient, admin_headers
|
||
):
|
||
# Arrange — 空 plugin_ids(dispatch handler 校验二选一,可能 200 或 400)
|
||
body = {"plugin_ids": []}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PLUGINS_URL}/batch/start", json=body, headers=admin_headers
|
||
)
|
||
# Assert
|
||
assert response.status_code in (200, 400), response.text
|
||
|
||
|
||
async def test_batch_start_plugins_with_empty_body(
|
||
test_client: httpx.AsyncClient, admin_headers
|
||
):
|
||
# Act — 空请求体,字段均取默认值,可能 200 或 400
|
||
response = await test_client.post(
|
||
f"{PLUGINS_URL}/batch/start", json={}, headers=admin_headers
|
||
)
|
||
# Assert
|
||
assert response.status_code in (200, 400), response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === Static path /install not captured as plugin_id ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_install_static_path_not_captured_as_plugin_id(
|
||
test_client: httpx.AsyncClient, admin_headers
|
||
):
|
||
# Arrange
|
||
body = {"source_type": "path", "source": "/tmp/nonexistent_plugin"}
|
||
# Act — POST /plugins/install 应路由到 install_plugin(superadmin → 403),
|
||
# 而非被 /{plugin_id} 捕获为 plugin_id="install"(否则 404)
|
||
response = await test_client.post(
|
||
f"{PLUGINS_URL}/install", json=body, headers=admin_headers
|
||
)
|
||
# Assert — 403 表示命中 install_plugin 的 superadmin 校验,确认路由正确
|
||
assert response.status_code == 403, response.text
|