"""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