"""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