ForcePilot/backend/test/integration/api/channels/test_outbox_router.py
Kris 5c4ef3a709 test: 格式化所有渠道相关测试用例的函数定义
1.  将多行的测试函数声明合并为单行,统一代码风格
2.  新增临时的微信woc向导验证脚本,用于全流程验证后可删除
3.  统一调整长请求行的换行写法,简化代码结构
2026-07-07 16:27:06 +08:00

385 lines
13 KiB
Python

"""Integration tests for channels outbox_router endpoints."""
from __future__ import annotations
import httpx
import pytest
from .conftest import (
BASE_URL,
DEFAULT_CHANNEL_TYPE,
ISO_TIME_END,
ISO_TIME_START,
NON_EXISTENT_OUTBOX_ID,
)
pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
OUTBOX_URL = f"{BASE_URL}/outbox"
# =============================================================================
# === Auth three-tier for GET /outbox/messages ===
# =============================================================================
async def test_list_outbox_messages_requires_auth(test_client: httpx.AsyncClient):
# Act
response = await test_client.get(f"{OUTBOX_URL}/messages")
# Assert
assert response.status_code == 401
async def test_list_outbox_messages_requires_admin(test_client: httpx.AsyncClient, standard_user):
# Act
response = await test_client.get(f"{OUTBOX_URL}/messages", headers=standard_user["headers"])
# Assert
assert response.status_code == 403
async def test_admin_can_list_outbox_messages(test_client: httpx.AsyncClient, admin_headers):
# Act
response = await test_client.get(f"{OUTBOX_URL}/messages", headers=admin_headers)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
assert isinstance(payload["data"], dict)
assert isinstance(payload["data"]["items"], list)
# =============================================================================
# === GET /outbox/messages pagination validation ===
# =============================================================================
async def test_list_outbox_messages_rejects_limit_zero(test_client: httpx.AsyncClient, admin_headers):
# Act — limit=0 violates ge=1
response = await test_client.get(
f"{OUTBOX_URL}/messages",
params={"limit": 0},
headers=admin_headers,
)
# Assert
assert response.status_code == 422, response.text
async def test_list_outbox_messages_rejects_limit_over_max(test_client: httpx.AsyncClient, admin_headers):
# Act — limit=201 violates le=200
response = await test_client.get(
f"{OUTBOX_URL}/messages",
params={"limit": 201},
headers=admin_headers,
)
# Assert
assert response.status_code == 422, response.text
# =============================================================================
# === GET /outbox/stats ===
# =============================================================================
async def test_admin_can_get_outbox_stats(test_client: httpx.AsyncClient, admin_headers):
# Act
response = await test_client.get(f"{OUTBOX_URL}/stats", headers=admin_headers)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
assert isinstance(payload["data"], dict)
data = payload["data"]
assert "top_errors" in data and isinstance(data["top_errors"], list)
assert "avg_latency_ms" in data
assert "oldest_pending_at" in data
# =============================================================================
# === GET /outbox/trend ===
# =============================================================================
async def test_outbox_trend_requires_start_time(test_client: httpx.AsyncClient, admin_headers):
# Act — start_time required
response = await test_client.get(
f"{OUTBOX_URL}/trend",
params={"end_time": ISO_TIME_END},
headers=admin_headers,
)
# Assert
assert response.status_code == 422, response.text
async def test_outbox_trend_requires_end_time(test_client: httpx.AsyncClient, admin_headers):
# Act — end_time required
response = await test_client.get(
f"{OUTBOX_URL}/trend",
params={"start_time": ISO_TIME_START},
headers=admin_headers,
)
# Assert
assert response.status_code == 422, response.text
async def test_admin_can_get_outbox_trend(test_client: httpx.AsyncClient, admin_headers):
# Arrange
params = {
"start_time": ISO_TIME_START,
"end_time": ISO_TIME_END,
"channel_type": DEFAULT_CHANNEL_TYPE,
"granularity": "hour",
"metric": "queue_depth",
}
# Act
response = await test_client.get(f"{OUTBOX_URL}/trend", params=params, headers=admin_headers)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
assert isinstance(payload["data"], dict)
# =============================================================================
# === GET /outbox/dead-letter ===
# =============================================================================
async def test_admin_can_list_dead_letter(test_client: httpx.AsyncClient, admin_headers):
# Act
response = await test_client.get(f"{OUTBOX_URL}/dead-letter", headers=admin_headers)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
assert isinstance(payload["data"], dict)
# =============================================================================
# === GET /outbox/retry-policy ===
# =============================================================================
async def test_admin_can_get_retry_policy(test_client: httpx.AsyncClient, admin_headers):
# Act
response = await test_client.get(f"{OUTBOX_URL}/retry-policy", headers=admin_headers)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
assert isinstance(payload["data"], dict)
# =============================================================================
# === POST /outbox/dead-letter/batch-retry ===
# =============================================================================
async def test_admin_can_batch_retry_dead_letter(test_client: httpx.AsyncClient, admin_headers):
# Act
response = await test_client.post(
f"{OUTBOX_URL}/dead-letter/batch-retry",
headers=admin_headers,
)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
assert isinstance(payload["data"], dict)
# =============================================================================
# === POST /outbox/dead-letter/batch-delete ===
# =============================================================================
async def test_admin_can_batch_delete_dead_letter(test_client: httpx.AsyncClient, admin_headers):
# Act
response = await test_client.post(
f"{OUTBOX_URL}/dead-letter/batch-delete",
headers=admin_headers,
)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
assert isinstance(payload["data"], dict)
# =============================================================================
# === GET /outbox/dead-letter/export ===
# =============================================================================
async def test_admin_can_export_dead_letter(test_client: httpx.AsyncClient, admin_headers):
# Act
response = await test_client.get(f"{OUTBOX_URL}/dead-letter/export", headers=admin_headers)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
assert isinstance(payload["data"], dict)
# =============================================================================
# === PUT /outbox/retry-policy validation ===
# =============================================================================
async def test_admin_can_update_retry_policy(test_client: httpx.AsyncClient, admin_headers):
# Arrange
body = {
"max_retry": 5,
"ttl_seconds": 3600,
"retry_backoff_schedule": [10, 30, 60],
}
# Act
response = await test_client.put(
f"{OUTBOX_URL}/retry-policy",
json=body,
headers=admin_headers,
)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
assert isinstance(payload["data"], dict)
async def test_update_retry_policy_rejects_max_retry_zero(test_client: httpx.AsyncClient, admin_headers):
# Act — max_retry=0 violates ge=1
body = {"max_retry": 0, "ttl_seconds": 3600, "retry_backoff_schedule": [10, 30, 60]}
response = await test_client.put(
f"{OUTBOX_URL}/retry-policy",
json=body,
headers=admin_headers,
)
# Assert
assert response.status_code == 422, response.text
async def test_update_retry_policy_rejects_max_retry_over_max(test_client: httpx.AsyncClient, admin_headers):
# Act — max_retry=21 violates le=20
body = {"max_retry": 21, "ttl_seconds": 3600, "retry_backoff_schedule": [10, 30, 60]}
response = await test_client.put(
f"{OUTBOX_URL}/retry-policy",
json=body,
headers=admin_headers,
)
# Assert
assert response.status_code == 422, response.text
async def test_update_retry_policy_rejects_ttl_too_small(test_client: httpx.AsyncClient, admin_headers):
# Act — ttl_seconds=59 violates ge=60
body = {"max_retry": 5, "ttl_seconds": 59, "retry_backoff_schedule": [10, 30, 60]}
response = await test_client.put(
f"{OUTBOX_URL}/retry-policy",
json=body,
headers=admin_headers,
)
# Assert
assert response.status_code == 422, response.text
async def test_update_retry_policy_rejects_ttl_too_large(test_client: httpx.AsyncClient, admin_headers):
# Act — ttl_seconds=2592001 violates le=2592000
body = {"max_retry": 5, "ttl_seconds": 2592001, "retry_backoff_schedule": [10, 30, 60]}
response = await test_client.put(
f"{OUTBOX_URL}/retry-policy",
json=body,
headers=admin_headers,
)
# Assert
assert response.status_code == 422, response.text
# =============================================================================
# === GET /outbox/messages/{outbox_id} ===
# =============================================================================
async def test_get_outbox_message_returns_404_for_non_existent(test_client: httpx.AsyncClient, admin_headers):
# Act
response = await test_client.get(
f"{OUTBOX_URL}/messages/{NON_EXISTENT_OUTBOX_ID}",
headers=admin_headers,
)
# Assert
assert response.status_code == 404, response.text
# =============================================================================
# === DELETE /outbox/dead-letter/{outbox_id} ===
# =============================================================================
async def test_delete_dead_letter_returns_404_for_non_existent(test_client: httpx.AsyncClient, admin_headers):
# Act
response = await test_client.delete(
f"{OUTBOX_URL}/dead-letter/{NON_EXISTENT_OUTBOX_ID}",
headers=admin_headers,
)
# Assert
assert response.status_code == 404, response.text
# =============================================================================
# === POST /outbox/messages/{outbox_id}/retry ===
# =============================================================================
async def test_retry_outbox_message_returns_404_for_non_existent(test_client: httpx.AsyncClient, admin_headers):
# Act
response = await test_client.post(
f"{OUTBOX_URL}/messages/{NON_EXISTENT_OUTBOX_ID}/retry",
headers=admin_headers,
)
# Assert
assert response.status_code == 404, response.text
# =============================================================================
# === Static paths not captured by /messages/{outbox_id} ===
# =============================================================================
async def test_stats_path_not_captured_by_outbox_id(test_client: httpx.AsyncClient, admin_headers):
# Act — /stats must route to stats endpoint, not /messages/{outbox_id}
response = await test_client.get(f"{OUTBOX_URL}/stats", headers=admin_headers)
# Assert
assert response.status_code == 200, response.text
async def test_trend_path_not_captured_by_outbox_id(test_client: httpx.AsyncClient, admin_headers):
# Act — /trend must route to trend endpoint
response = await test_client.get(
f"{OUTBOX_URL}/trend",
params={"start_time": ISO_TIME_START, "end_time": ISO_TIME_END},
headers=admin_headers,
)
# Assert
assert response.status_code == 200, response.text
async def test_dead_letter_path_not_captured_by_outbox_id(test_client: httpx.AsyncClient, admin_headers):
# Act — /dead-letter must route to dead-letter endpoint
response = await test_client.get(f"{OUTBOX_URL}/dead-letter", headers=admin_headers)
# Assert
assert response.status_code == 200, response.text
async def test_retry_policy_path_not_captured_by_outbox_id(test_client: httpx.AsyncClient, admin_headers):
# Act — /retry-policy must route to retry-policy endpoint
response = await test_client.get(f"{OUTBOX_URL}/retry-policy", headers=admin_headers)
# Assert
assert response.status_code == 200, response.text