ForcePilot/backend/test/integration/api/channels/test_plugin_router.py
Kris 04a2472014 test(channel): add full integration test suite for channels module
1. 新增channels模块集成测试目录与基础fixture
2. 为capability、doctor、reports、dashboard、directory、webhook、wizard、health、analytics、allowlist、config等路由编写完整的鉴权、参数校验与异常场景测试
3. 修复基础集成测试setup,新增external/scheduler/channel数据库schema初始化步骤
2026-07-02 03:25:27 +08:00

270 lines
8.7 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.

"""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_idsdispatch 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_pluginsuperadmin → 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