ForcePilot/backend/test/integration/api/channels/test_config_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

253 lines
8.4 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 config_router endpoints.
覆盖配置管理域CFG-02 / CFG-READ-01 / CFG-UPDATE-01 / CFG-ROLL-01 /
CFG-HIST-01 / CFG-EXPORT / CFG-IMPORT / CFG-BATCH-UPDATE的鉴权矩阵、
请求体校验与关键错误路径。所有动态 ``/{key}`` 路径用 ``NON_EXISTENT_CONFIG_KEY``
触发 404静态路径schema / export / import / batch通过提前声明规避动态捕获。
"""
from __future__ import annotations
import httpx
import pytest
from .conftest import BASE_URL, NON_EXISTENT_CONFIG_KEY
pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
CONFIG_URL = f"{BASE_URL}/config"
# =============================================================================
# === Auth three-tier for GET /config/schema ===
# =============================================================================
async def test_get_config_schema_requires_auth(test_client: httpx.AsyncClient):
# Act
response = await test_client.get(f"{CONFIG_URL}/schema")
# Assert
assert response.status_code == 401
async def test_get_config_schema_requires_admin(
test_client: httpx.AsyncClient, standard_user
):
# Act
response = await test_client.get(f"{CONFIG_URL}/schema", headers=standard_user["headers"])
# Assert
assert response.status_code == 403
async def test_admin_can_get_config_schema(test_client: httpx.AsyncClient, admin_headers):
# Act
response = await test_client.get(f"{CONFIG_URL}/schema", headers=admin_headers)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
assert isinstance(payload["data"], (dict, list))
# =============================================================================
# === GET /config/export — superadmin only ===
# =============================================================================
async def test_export_config_requires_auth(test_client: httpx.AsyncClient):
# Act
response = await test_client.get(f"{CONFIG_URL}/export")
# Assert
assert response.status_code == 401
async def test_export_config_requires_superadmin_for_standard_user(
test_client: httpx.AsyncClient, standard_user
):
# Act
response = await test_client.get(f"{CONFIG_URL}/export", headers=standard_user["headers"])
# Assert
assert response.status_code == 403
async def test_export_config_requires_superadmin_for_admin(
test_client: httpx.AsyncClient, admin_headers
):
# Act — admin非 superadmin应被拒绝
response = await test_client.get(f"{CONFIG_URL}/export", headers=admin_headers)
# Assert
assert response.status_code == 403, response.text
# =============================================================================
# === POST /config/import — superadmin only ===
# =============================================================================
async def test_import_config_requires_auth(test_client: httpx.AsyncClient):
# Arrange
body = {"config_data": {"key": "value"}, "dry_run": True}
# Act
response = await test_client.post(f"{CONFIG_URL}/import", json=body)
# Assert
assert response.status_code == 401
async def test_import_config_requires_superadmin_for_admin(
test_client: httpx.AsyncClient, admin_headers
):
# Arrange
body = {"config_data": {"key": "value"}, "dry_run": True}
# Act
response = await test_client.post(f"{CONFIG_URL}/import", json=body, headers=admin_headers)
# Assert
assert response.status_code == 403, response.text
# =============================================================================
# === PUT /config/batch — validation ===
# =============================================================================
async def test_batch_update_config_requires_auth(test_client: httpx.AsyncClient):
# Arrange
body = {"updates": [{"key": "k", "value": "v"}]}
# Act
response = await test_client.put(f"{CONFIG_URL}/batch", json=body)
# Assert
assert response.status_code == 401
async def test_batch_update_config_requires_admin(
test_client: httpx.AsyncClient, standard_user
):
# Arrange
body = {"updates": [{"key": "k", "value": "v"}]}
# Act
response = await test_client.put(
f"{CONFIG_URL}/batch", json=body, headers=standard_user["headers"]
)
# Assert
assert response.status_code == 403
async def test_batch_update_config_rejects_empty_updates(
test_client: httpx.AsyncClient, admin_headers
):
# Act — updates 为空违反 min_length=1
response = await test_client.put(
f"{CONFIG_URL}/batch", json={"updates": []}, headers=admin_headers
)
# Assert
assert response.status_code == 422, response.text
async def test_batch_update_config_rejects_too_many_updates(
test_client: httpx.AsyncClient, admin_headers
):
# Arrange — 51 条超过 max_length=50
body = {"updates": [{"key": f"k{i}", "value": "v"} for i in range(51)]}
# Act
response = await test_client.put(f"{CONFIG_URL}/batch", json=body, headers=admin_headers)
# Assert
assert response.status_code == 422, response.text
# =============================================================================
# === GET /config/{key} — non-existent key ===
# =============================================================================
async def test_get_config_returns_404_for_non_existent_key(
test_client: httpx.AsyncClient, admin_headers
):
# Act
response = await test_client.get(
f"{CONFIG_URL}/{NON_EXISTENT_CONFIG_KEY}", headers=admin_headers
)
# Assert
assert response.status_code == 404, response.text
# =============================================================================
# === PUT /config/{key} — non-existent key ===
# =============================================================================
async def test_update_config_returns_404_for_non_existent_key(
test_client: httpx.AsyncClient, admin_headers
):
# Arrange
body = {"value": "new_value"}
# Act
response = await test_client.put(
f"{CONFIG_URL}/{NON_EXISTENT_CONFIG_KEY}", json=body, headers=admin_headers
)
# Assert
assert response.status_code == 404, response.text
# =============================================================================
# === POST /config/{key}/rollback — non-existent key + validation ===
# =============================================================================
async def test_rollback_config_returns_404_for_non_existent_key(
test_client: httpx.AsyncClient, admin_headers
):
# Arrange
body = {"target_version": 1}
# Act
response = await test_client.post(
f"{CONFIG_URL}/{NON_EXISTENT_CONFIG_KEY}/rollback", json=body, headers=admin_headers
)
# Assert
assert response.status_code == 404, response.text
async def test_rollback_config_rejects_zero_target_version(
test_client: httpx.AsyncClient, admin_headers
):
# Act — target_version=0 违反 ge=1
response = await test_client.post(
f"{CONFIG_URL}/{NON_EXISTENT_CONFIG_KEY}/rollback",
json={"target_version": 0},
headers=admin_headers,
)
# Assert
assert response.status_code == 422, response.text
# =============================================================================
# === GET /config/{key}/history — non-existent key ===
# =============================================================================
async def test_get_config_history_returns_404_for_non_existent_key(
test_client: httpx.AsyncClient, admin_headers
):
# Act
response = await test_client.get(
f"{CONFIG_URL}/{NON_EXISTENT_CONFIG_KEY}/history", headers=admin_headers
)
# Assert
assert response.status_code == 404, response.text
# =============================================================================
# === Static path vs dynamic — /config/schema not captured as key ===
# =============================================================================
async def test_schema_static_path_not_captured_as_key(
test_client: httpx.AsyncClient, admin_headers
):
# Act — 静态路径 /config/schema 应被 get_config_schema 捕获,而非 get_config
response = await test_client.get(f"{CONFIG_URL}/schema", headers=admin_headers)
# Assert — schema 端点返回 200而非 404若被当作 key="schema" 则 404
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True