322 lines
10 KiB
Python
322 lines
10 KiB
Python
"""Integration tests for channels pairing_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_ACCOUNT_ID,
|
||
NON_EXISTENT_PAIRING_ID,
|
||
NON_EXISTENT_PEER_ID,
|
||
)
|
||
|
||
pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
|
||
|
||
PAIRINGS_URL = f"{BASE_URL}/pairings"
|
||
|
||
|
||
def _make_create_pairing_body() -> dict:
|
||
"""Build a minimal CreatePairingRequest body."""
|
||
return {
|
||
"channel_type": DEFAULT_CHANNEL_TYPE,
|
||
"account_id": NON_EXISTENT_ACCOUNT_ID,
|
||
"peer_id": NON_EXISTENT_PEER_ID,
|
||
"peer_name": "pytest_peer",
|
||
"expires_in_seconds": 604800,
|
||
}
|
||
|
||
|
||
# =============================================================================
|
||
# === Auth three-tier for list pairings ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_list_pairings_requires_auth(test_client: httpx.AsyncClient):
|
||
# Act
|
||
response = await test_client.get(PAIRINGS_URL)
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_list_pairings_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
|
||
# Act
|
||
response = await test_client.get(PAIRINGS_URL, headers=standard_user["headers"])
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
async def test_admin_can_list_pairings(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act
|
||
response = await test_client.get(PAIRINGS_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)
|
||
# handler _pairingList 返回 {"pairings": [...]}(与前端 usePairingApproval 契约一致)
|
||
assert "pairings" in payload["data"]
|
||
|
||
|
||
# =============================================================================
|
||
# === Pagination + filter validation for list pairings ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_list_pairings_rejects_limit_zero(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act — limit=0 violates ge=1
|
||
response = await test_client.get(
|
||
PAIRINGS_URL,
|
||
params={"limit": 0},
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 422, response.text
|
||
|
||
|
||
async def test_list_pairings_rejects_limit_over_max(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act — limit=1001 violates le=1000
|
||
response = await test_client.get(
|
||
PAIRINGS_URL,
|
||
params={"limit": 1001},
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 422, response.text
|
||
|
||
|
||
async def test_list_pairings_rejects_invalid_status(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act — status 不在枚举范围内
|
||
response = await test_client.get(
|
||
PAIRINGS_URL,
|
||
params={"status": "INVALID_STATUS"},
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 422, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === create pairing ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_create_pairing_requires_auth(test_client: httpx.AsyncClient):
|
||
# Arrange
|
||
body = _make_create_pairing_body()
|
||
# Act
|
||
response = await test_client.post(PAIRINGS_URL, json=body)
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_create_pairing_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
|
||
# Arrange
|
||
body = _make_create_pairing_body()
|
||
# Act
|
||
response = await test_client.post(
|
||
PAIRINGS_URL,
|
||
json=body,
|
||
headers=standard_user["headers"],
|
||
)
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
async def test_create_pairing_non_existent_account_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange
|
||
body = _make_create_pairing_body()
|
||
# Act
|
||
response = await test_client.post(PAIRINGS_URL, json=body, headers=admin_headers)
|
||
# Assert — account_id 不存在:NOT_FOUND (404) 或适配器能力未实现 (501)
|
||
assert response.status_code in (404, 501), response.text
|
||
|
||
|
||
async def test_create_pairing_rejects_expires_zero(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange — expires_in_seconds=0 低于下限 1
|
||
body = _make_create_pairing_body()
|
||
body["expires_in_seconds"] = 0
|
||
# Act
|
||
response = await test_client.post(PAIRINGS_URL, json=body, headers=admin_headers)
|
||
# Assert
|
||
assert response.status_code == 422, response.text
|
||
|
||
|
||
async def test_create_pairing_rejects_expires_over_max(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange — expires_in_seconds > 2592000 超过上限
|
||
body = _make_create_pairing_body()
|
||
body["expires_in_seconds"] = 2592001
|
||
# Act
|
||
response = await test_client.post(PAIRINGS_URL, json=body, headers=admin_headers)
|
||
# Assert
|
||
assert response.status_code == 422, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === batch approve / reject ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_batch_approve_pairings_rejects_empty_ids(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange — pairing_ids 为空违反 min_items=1
|
||
body = {"pairing_ids": []}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PAIRINGS_URL}/batch-approve",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 422, response.text
|
||
|
||
|
||
async def test_batch_approve_pairings_rejects_too_many_ids(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange — pairing_ids 超过 max_items=500
|
||
body = {"pairing_ids": [f"id_{i}" for i in range(501)]}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PAIRINGS_URL}/batch-approve",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 422, response.text
|
||
|
||
|
||
async def test_batch_reject_pairings_rejects_empty_ids(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange
|
||
body = {"pairing_ids": []}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PAIRINGS_URL}/batch-reject",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 422, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === clean expired ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_clean_expired_pairings_rejects_max_count_zero(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange — max_count=0 低于下限 1
|
||
body = {"max_count": 0}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PAIRINGS_URL}/clean-expired",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 422, response.text
|
||
|
||
|
||
async def test_clean_expired_pairings_rejects_max_count_over_max(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange — max_count=1001 超过上限 1000
|
||
body = {"max_count": 1001}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PAIRINGS_URL}/clean-expired",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 422, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === pairings stats ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_get_pairings_stats_returns_200(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act
|
||
response = await test_client.get(
|
||
f"{PAIRINGS_URL}/stats",
|
||
params={"start_time": ISO_TIME_START, "end_time": ISO_TIME_END},
|
||
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 pairing detail ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_get_pairing_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act
|
||
response = await test_client.get(
|
||
f"{PAIRINGS_URL}/{NON_EXISTENT_PAIRING_ID}",
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === approve pairing ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_approve_pairing_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange
|
||
body = {"approver_id": "admin_xxx", "reason": "pytest"}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PAIRINGS_URL}/{NON_EXISTENT_PAIRING_ID}/approve",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === reject pairing ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_reject_pairing_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange
|
||
body = {"approver_id": "admin_xxx", "reason": "pytest"}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PAIRINGS_URL}/{NON_EXISTENT_PAIRING_ID}/reject",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === revoke pairing ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_revoke_pairing_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange
|
||
body = {"reason": "pytest"}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{PAIRINGS_URL}/{NON_EXISTENT_PAIRING_ID}/revoke",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|