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

374 lines
12 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 session_router endpoints."""
from __future__ import annotations
import httpx
import pytest
from .conftest import (
BASE_URL,
NON_EXISTENT_SESSION_ID,
)
pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
SESSIONS_URL = f"{BASE_URL}/sessions"
# =============================================================================
# === Auth three-tier for list sessions ===
# =============================================================================
async def test_list_sessions_requires_auth(test_client: httpx.AsyncClient):
# Act
response = await test_client.get(SESSIONS_URL)
# Assert
assert response.status_code == 401, response.text
async def test_list_sessions_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
# Act
response = await test_client.get(SESSIONS_URL, headers=standard_user["headers"])
# Assert
assert response.status_code == 403, response.text
async def test_admin_can_list_sessions(test_client: httpx.AsyncClient, admin_headers):
# Act
response = await test_client.get(SESSIONS_URL, 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 "items" in payload["data"]
# =============================================================================
# === Pagination validation for list sessions ===
# =============================================================================
async def test_list_sessions_rejects_limit_zero(test_client: httpx.AsyncClient, admin_headers):
# Act — limit=0 violates ge=1
response = await test_client.get(
SESSIONS_URL,
params={"limit": 0},
headers=admin_headers,
)
# Assert
assert response.status_code == 422, response.text
async def test_list_sessions_rejects_limit_over_max(test_client: httpx.AsyncClient, admin_headers):
# Act — limit=1001 violates le=1000
response = await test_client.get(
SESSIONS_URL,
params={"limit": 1001},
headers=admin_headers,
)
# Assert
assert response.status_code == 422, response.text
# =============================================================================
# === batch-close ===
# =============================================================================
async def test_batch_close_sessions_requires_auth(test_client: httpx.AsyncClient):
# Arrange
body = {"session_ids": [NON_EXISTENT_SESSION_ID]}
# Act
response = await test_client.post(f"{SESSIONS_URL}/batch-close", json=body)
# Assert
assert response.status_code == 401, response.text
async def test_batch_close_sessions_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
# Arrange
body = {"session_ids": [NON_EXISTENT_SESSION_ID]}
# Act
response = await test_client.post(
f"{SESSIONS_URL}/batch-close",
json=body,
headers=standard_user["headers"],
)
# Assert
assert response.status_code == 403, response.text
async def test_batch_close_sessions_rejects_empty_body(test_client: httpx.AsyncClient, admin_headers):
# Arrange — session_ids 与 filter 同时为空dispatch 校验返回 400
body = {"session_ids": None, "filter": None}
# Act
response = await test_client.post(
f"{SESSIONS_URL}/batch-close",
json=body,
headers=admin_headers,
)
# Assert
assert response.status_code in (400, 422), response.text
async def test_batch_close_sessions_with_non_existent_ids_returns_success(
test_client: httpx.AsyncClient, admin_headers
):
# Arrange — 不存在的 session_ids部分成功语义下应返回 200
body = {"session_ids": [NON_EXISTENT_SESSION_ID], "max_count": 100, "reason": "pytest"}
# Act
response = await test_client.post(
f"{SESSIONS_URL}/batch-close",
json=body,
headers=admin_headers,
)
# Assert
assert response.status_code == 200, response.text
payload = response.json()
assert payload["success"] is True
# =============================================================================
# === get session detail ===
# =============================================================================
async def test_get_session_requires_auth(test_client: httpx.AsyncClient):
# Act
response = await test_client.get(f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}")
# Assert
assert response.status_code == 401, response.text
async def test_get_session_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
# Act
response = await test_client.get(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}",
headers=standard_user["headers"],
)
# Assert
assert response.status_code == 403, response.text
async def test_get_session_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
# Act
response = await test_client.get(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}",
headers=admin_headers,
)
# Assert
assert response.status_code == 404, response.text
# =============================================================================
# === merge session ===
# =============================================================================
async def test_merge_session_requires_auth(test_client: httpx.AsyncClient):
# Arrange
body = {"source_conversation_id": "conv_xxx", "reason": "pytest"}
# Act
response = await test_client.post(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/merge",
json=body,
)
# Assert
assert response.status_code == 401, response.text
async def test_merge_session_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
# Arrange
body = {"source_conversation_id": "conv_xxx", "reason": "pytest"}
# Act
response = await test_client.post(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/merge",
json=body,
headers=standard_user["headers"],
)
# Assert
assert response.status_code == 403, response.text
async def test_merge_session_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
# Arrange
body = {"source_conversation_id": "conv_xxx", "reason": "pytest"}
# Act
response = await test_client.post(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/merge",
json=body,
headers=admin_headers,
)
# Assert
assert response.status_code == 404, response.text
# =============================================================================
# === transfer session owner ===
# =============================================================================
async def test_transfer_session_requires_auth(test_client: httpx.AsyncClient):
# Arrange
body = {"new_owner_id": "owner_xxx"}
# Act
response = await test_client.post(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/transfer",
json=body,
)
# Assert
assert response.status_code == 401, response.text
async def test_transfer_session_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
# Arrange
body = {"new_owner_id": "owner_xxx"}
# Act
response = await test_client.post(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/transfer",
json=body,
headers=standard_user["headers"],
)
# Assert
assert response.status_code == 403, response.text
async def test_transfer_session_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
# Arrange
body = {"new_owner_id": "owner_xxx"}
# Act
response = await test_client.post(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/transfer",
json=body,
headers=admin_headers,
)
# Assert
assert response.status_code == 404, response.text
# =============================================================================
# === close session ===
# =============================================================================
async def test_close_session_requires_auth(test_client: httpx.AsyncClient):
# Arrange
body = {"reason": "pytest"}
# Act
response = await test_client.post(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/close",
json=body,
)
# Assert
assert response.status_code == 401, response.text
async def test_close_session_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
# Arrange
body = {"reason": "pytest"}
# Act
response = await test_client.post(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/close",
json=body,
headers=standard_user["headers"],
)
# Assert
assert response.status_code == 403, response.text
async def test_close_session_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
# Arrange
body = {"reason": "pytest"}
# Act
response = await test_client.post(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/close",
json=body,
headers=admin_headers,
)
# Assert
assert response.status_code == 404, response.text
# =============================================================================
# === list session messages ===
# =============================================================================
async def test_list_session_messages_requires_auth(test_client: httpx.AsyncClient):
# Act
response = await test_client.get(f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/messages")
# Assert
assert response.status_code == 401, response.text
async def test_list_session_messages_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
# Act
response = await test_client.get(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/messages",
headers=standard_user["headers"],
)
# Assert
assert response.status_code == 403, response.text
async def test_list_session_messages_non_existent_returns_404(
test_client: httpx.AsyncClient, admin_headers
):
# Act
response = await test_client.get(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/messages",
headers=admin_headers,
)
# Assert
assert response.status_code == 404, response.text
async def test_list_session_messages_rejects_invalid_limit(
test_client: httpx.AsyncClient, admin_headers
):
# Act — limit=0 violates ge=1
response = await test_client.get(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/messages",
params={"limit": 0},
headers=admin_headers,
)
# Assert — session 不存在或参数非法,至少不应是 200
assert response.status_code in (404, 422), response.text
# =============================================================================
# === session stats ===
# =============================================================================
async def test_get_session_stats_requires_auth(test_client: httpx.AsyncClient):
# Act
response = await test_client.get(f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/stats")
# Assert
assert response.status_code == 401, response.text
async def test_get_session_stats_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
# Act
response = await test_client.get(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/stats",
headers=standard_user["headers"],
)
# Assert
assert response.status_code == 403, response.text
async def test_get_session_stats_non_existent_returns_404(
test_client: httpx.AsyncClient, admin_headers
):
# Act
response = await test_client.get(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/stats",
headers=admin_headers,
)
# Assert
assert response.status_code == 404, response.text