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

281 lines
8.5 KiB
Python

"""Integration tests for channels audit_router endpoints."""
from __future__ import annotations
import httpx
import pytest
from .conftest import BASE_URL, NON_EXISTENT_LOG_ID, NON_EXISTENT_TASK_ID
pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
AUDIT_URL = f"{BASE_URL}/audit"
# =============================================================================
# === Auth three-tier for GET /audit/logs ===
# =============================================================================
async def test_query_audit_logs_requires_auth(test_client: httpx.AsyncClient):
# Act
response = await test_client.get(f"{AUDIT_URL}/logs")
# Assert
assert response.status_code == 401, response.text
async def test_query_audit_logs_requires_admin(
test_client: httpx.AsyncClient, standard_user
):
# Act
response = await test_client.get(
f"{AUDIT_URL}/logs", headers=standard_user["headers"]
)
# Assert
assert response.status_code == 403, response.text
async def test_admin_can_query_audit_logs(
test_client: httpx.AsyncClient, admin_headers
):
# Act
response = await test_client.get(f"{AUDIT_URL}/logs", headers=admin_headers)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
assert "data" in payload
# =============================================================================
# === Pagination validation for GET /audit/logs ===
# =============================================================================
async def test_query_audit_logs_rejects_limit_zero(
test_client: httpx.AsyncClient, admin_headers
):
# Act — limit=0 violates ge=1
response = await test_client.get(
f"{AUDIT_URL}/logs", params={"limit": 0}, headers=admin_headers
)
# Assert
assert response.status_code == 422, response.text
async def test_query_audit_logs_rejects_limit_over_max(
test_client: httpx.AsyncClient, admin_headers
):
# Act — limit=501 violates le=500
response = await test_client.get(
f"{AUDIT_URL}/logs", params={"limit": 501}, headers=admin_headers
)
# Assert
assert response.status_code == 422, response.text
async def test_query_audit_logs_rejects_negative_offset(
test_client: httpx.AsyncClient, admin_headers
):
# Act — offset=-1 violates ge=0
response = await test_client.get(
f"{AUDIT_URL}/logs", params={"offset": -1}, headers=admin_headers
)
# Assert
assert response.status_code == 422, response.text
# =============================================================================
# === GET /audit/logs/stats and /audit/operations ===
# =============================================================================
async def test_admin_can_get_audit_log_stats(
test_client: httpx.AsyncClient, admin_headers
):
# Act
response = await test_client.get(f"{AUDIT_URL}/logs/stats", headers=admin_headers)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
assert "data" in payload
async def test_admin_can_list_audit_operations(
test_client: httpx.AsyncClient, admin_headers
):
# Act
response = await test_client.get(f"{AUDIT_URL}/operations", headers=admin_headers)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
assert "data" in payload
# =============================================================================
# === POST /audit/export (create export task) ===
# =============================================================================
async def test_admin_can_create_audit_export_with_empty_body(
test_client: httpx.AsyncClient, admin_headers
):
# Arrange — all fields optional, empty dict is valid
body: dict = {}
# Act
response = await test_client.post(
f"{AUDIT_URL}/export", json=body, headers=admin_headers
)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
assert "data" in payload
# =============================================================================
# === GET /audit/export (list export tasks) ===
# =============================================================================
async def test_admin_can_list_export_tasks(
test_client: httpx.AsyncClient, admin_headers
):
# Act
response = await test_client.get(f"{AUDIT_URL}/export", headers=admin_headers)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
assert "data" in payload
# =============================================================================
# === GET /audit/retention-policy ===
# =============================================================================
async def test_admin_can_get_retention_policy(
test_client: httpx.AsyncClient, admin_headers
):
# Act
response = await test_client.get(
f"{AUDIT_URL}/retention-policy", headers=admin_headers
)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
assert "data" in payload
# =============================================================================
# === PUT /audit/retention-policy (superadmin only) ===
# =============================================================================
async def test_update_retention_policy_requires_auth(test_client: httpx.AsyncClient):
# Arrange
body = {"default_retention_days": 90}
# Act
response = await test_client.put(f"{AUDIT_URL}/retention-policy", json=body)
# Assert
assert response.status_code == 401, response.text
async def test_update_retention_policy_rejects_standard_user(
test_client: httpx.AsyncClient, standard_user
):
# Arrange
body = {"default_retention_days": 90}
# Act
response = await test_client.put(
f"{AUDIT_URL}/retention-policy",
json=body,
headers=standard_user["headers"],
)
# Assert
assert response.status_code == 403, response.text
async def test_update_retention_policy_requires_superadmin(
test_client: httpx.AsyncClient, admin_headers
):
# Arrange — admin role (not superadmin) should be rejected
body = {"default_retention_days": 90}
# Act
response = await test_client.put(
f"{AUDIT_URL}/retention-policy", json=body, headers=admin_headers
)
# Assert
assert response.status_code == 403, response.text
async def test_update_retention_policy_rejects_archive_before_ge_retention(
test_client: httpx.AsyncClient, admin_headers
):
# Arrange — auto_archive_before_days >= default_retention_days violates
# dispatch-stage rule (RULE_VIOLATION -> 422)
body = {
"default_retention_days": 10,
"auto_archive_before_days": 10,
}
# Act
response = await test_client.put(
f"{AUDIT_URL}/retention-policy", json=body, headers=admin_headers
)
# Assert
assert response.status_code == 422, response.text
# =============================================================================
# === 404 paths for non-existent resources ===
# =============================================================================
async def test_get_audit_log_returns_404_for_nonexistent(
test_client: httpx.AsyncClient, admin_headers
):
# Act
response = await test_client.get(
f"{AUDIT_URL}/logs/{NON_EXISTENT_LOG_ID}", headers=admin_headers
)
# Assert
assert response.status_code == 404, response.text
async def test_get_audit_export_task_returns_404_for_nonexistent(
test_client: httpx.AsyncClient, admin_headers
):
# Act
response = await test_client.get(
f"{AUDIT_URL}/export/{NON_EXISTENT_TASK_ID}", headers=admin_headers
)
# Assert
assert response.status_code == 404, response.text
async def test_cancel_audit_export_task_returns_404_for_nonexistent(
test_client: httpx.AsyncClient, admin_headers
):
# Act
response = await test_client.delete(
f"{AUDIT_URL}/export/{NON_EXISTENT_TASK_ID}", headers=admin_headers
)
# Assert
assert response.status_code == 404, response.text
async def test_download_audit_export_returns_404_for_nonexistent(
test_client: httpx.AsyncClient, admin_headers
):
# Act
response = await test_client.get(
f"{AUDIT_URL}/export/{NON_EXISTENT_TASK_ID}/download",
headers=admin_headers,
)
# Assert
assert response.status_code == 404, response.text