ForcePilot/backend/test/integration/api/channels/test_session_router.py

418 lines
13 KiB
Python
Raw Normal View History

"""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
# =============================================================================
# === bind-user / unbind-user / identity (P3) ===
# =============================================================================
async def test_bind_session_user_non_existent_returns_404(
test_client: httpx.AsyncClient, admin_headers
):
# Arrange
body = {"user_uid": "non_existent_user_uid"}
# Act
response = await test_client.post(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/bind-user",
json=body,
headers=admin_headers,
)
# Assert
assert response.status_code == 404, response.text
async def test_unbind_session_user_non_existent_returns_404(
test_client: httpx.AsyncClient, admin_headers
):
# Act
response = await test_client.delete(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/bind-user",
headers=admin_headers,
)
# Assert
assert response.status_code == 404, response.text
async def test_get_session_identity_non_existent_returns_404(
test_client: httpx.AsyncClient, admin_headers
):
# Act
response = await test_client.get(
f"{SESSIONS_URL}/{NON_EXISTENT_SESSION_ID}/identity",
headers=admin_headers,
)
# Assert
assert response.status_code == 404, response.text