233 lines
8.2 KiB
Python
233 lines
8.2 KiB
Python
|
|
# =============================================================================
|
||
|
|
# 企微IT智能服务台 — 统一认证 API 测试
|
||
|
|
# =============================================================================
|
||
|
|
# 测试覆盖:
|
||
|
|
# 1. GET /auth/qrcode — 获取扫码登录二维码
|
||
|
|
# 2. GET /auth/scan/status — 轮询扫码状态
|
||
|
|
# 3. POST /auth/verify — 验证 Token
|
||
|
|
# 4. POST /auth/logout — 登出
|
||
|
|
# 5. GET /auth/me — 获取当前用户信息
|
||
|
|
# 6. POST /auth/switch-role — 切换角色
|
||
|
|
#
|
||
|
|
# 说明:这些端点复用现有的 auth_qrcode 服务,因此重点测试统一认证 API 层的封装
|
||
|
|
# =============================================================================
|
||
|
|
|
||
|
|
import json
|
||
|
|
import secrets
|
||
|
|
from datetime import datetime
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from tests.conftest import MockRedis
|
||
|
|
|
||
|
|
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
# 工具: 创建测试 Token 并写入 Redis
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
async def _create_test_token(
|
||
|
|
mock_redis: MockRedis,
|
||
|
|
employee_id: str = "test-user-001",
|
||
|
|
name: str = "测试用户",
|
||
|
|
roles: list = None,
|
||
|
|
login_source: str = "h5",
|
||
|
|
) -> str:
|
||
|
|
"""在 mock_redis 里手动写一个用户 token,返回 token 字符串。
|
||
|
|
|
||
|
|
与 TokenService.create_token 一致: 写 user:token:{token}
|
||
|
|
"""
|
||
|
|
if roles is None:
|
||
|
|
roles = ["user"]
|
||
|
|
|
||
|
|
token = secrets.token_urlsafe(32)
|
||
|
|
token_data = {
|
||
|
|
"employee_id": employee_id,
|
||
|
|
"name": name,
|
||
|
|
"department": "测试部",
|
||
|
|
"avatar": "",
|
||
|
|
"roles": roles,
|
||
|
|
"current_role": roles[0] if roles else "user",
|
||
|
|
"login_source": login_source,
|
||
|
|
"created_at": datetime.now().isoformat(),
|
||
|
|
"last_active": datetime.now().isoformat(),
|
||
|
|
}
|
||
|
|
await mock_redis.setex(
|
||
|
|
f"user:token:{token}",
|
||
|
|
8 * 60 * 60,
|
||
|
|
json.dumps(token_data, ensure_ascii=False),
|
||
|
|
)
|
||
|
|
return token
|
||
|
|
|
||
|
|
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
# 1. GET /auth/qrcode — 获取扫码登录二维码
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
class TestQrcodeEndpoint:
|
||
|
|
"""测试统一认证的二维码获取端点。"""
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_get_qrcode_returns_ticket_and_url(self, client, mock_redis):
|
||
|
|
"""验证获取二维码返回 ticket + qrcode_url + qrcode_png_base64 + expires_in。"""
|
||
|
|
response = await client.get("/auth/qrcode")
|
||
|
|
|
||
|
|
assert response.status_code == 200
|
||
|
|
body = response.json()
|
||
|
|
assert body["code"] == 0
|
||
|
|
assert "data" in body
|
||
|
|
assert body["data"] is not None
|
||
|
|
|
||
|
|
data = body["data"]
|
||
|
|
assert "ticket" in data
|
||
|
|
assert len(data["ticket"]) >= 16
|
||
|
|
assert "qrcode_url" in data
|
||
|
|
assert "qrcode_png_base64" in data
|
||
|
|
assert "expires_in" in data
|
||
|
|
assert "expires_at" in data
|
||
|
|
# 有效期应该是 120 秒 (从 QrcodeService 来的)
|
||
|
|
assert data["expires_in"] == 120
|
||
|
|
|
||
|
|
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
# 2. POST /auth/verify — 验证 Token
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
class TestVerifyTokenEndpoint:
|
||
|
|
"""测试统一认证的 Token 验证端点。"""
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_verify_invalid_token_returns_false(self, client, mock_redis):
|
||
|
|
"""验证无效 Token 返回 valid=False。"""
|
||
|
|
response = await client.post(
|
||
|
|
"/auth/verify",
|
||
|
|
json={"token": "invalid-token-xyz"},
|
||
|
|
)
|
||
|
|
|
||
|
|
assert response.status_code == 200
|
||
|
|
body = response.json()
|
||
|
|
assert body["code"] == 0
|
||
|
|
data = body["data"]
|
||
|
|
assert data["valid"] is False
|
||
|
|
assert data["employee_id"] is None
|
||
|
|
assert data["name"] is None
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_verify_blacklisted_token_returns_false(self, client, mock_redis):
|
||
|
|
"""验证已加入黑名单的 Token 返回 valid=False。"""
|
||
|
|
# 创建 token
|
||
|
|
token = await _create_test_token(
|
||
|
|
mock_redis,
|
||
|
|
employee_id="test-user-001",
|
||
|
|
name="测试用户",
|
||
|
|
)
|
||
|
|
|
||
|
|
# 将 token 加入黑名单
|
||
|
|
import hashlib
|
||
|
|
token_hash = hashlib.sha256(token.encode()).hexdigest()
|
||
|
|
await mock_redis.setex(f"token:blacklist:{token_hash}", 8 * 60 * 60, "1")
|
||
|
|
|
||
|
|
# 验证黑名单中的 token
|
||
|
|
response = await client.post(
|
||
|
|
"/auth/verify",
|
||
|
|
json={"token": token},
|
||
|
|
)
|
||
|
|
|
||
|
|
assert response.status_code == 200
|
||
|
|
body = response.json()
|
||
|
|
assert body["code"] == 0
|
||
|
|
data = body["data"]
|
||
|
|
assert data["valid"] is False
|
||
|
|
|
||
|
|
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
# 3. POST /auth/logout — 登出
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
class TestLogoutEndpoint:
|
||
|
|
"""测试统一认证的登出端点。"""
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_logout_without_auth_returns_error(self, client, mock_redis):
|
||
|
|
"""验证未登录登出返回错误。"""
|
||
|
|
response = await client.post("/auth/logout")
|
||
|
|
|
||
|
|
# 未鉴权应该返回 401 或 403
|
||
|
|
assert response.status_code in (401, 403)
|
||
|
|
|
||
|
|
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
# 4. GET /auth/me — 获取当前用户信息
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
class TestCurrentUserEndpoint:
|
||
|
|
"""测试统一认证的获取当前用户信息端点。"""
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_get_current_user_returns_info(self, client, mock_redis):
|
||
|
|
"""验证获取当前用户信息成功。"""
|
||
|
|
# 创建有效 token
|
||
|
|
token = await _create_test_token(
|
||
|
|
mock_redis,
|
||
|
|
employee_id="test-user-001",
|
||
|
|
name="测试用户",
|
||
|
|
roles=["agent"],
|
||
|
|
login_source="agent",
|
||
|
|
)
|
||
|
|
|
||
|
|
# 获取当前用户
|
||
|
|
response = await client.get(
|
||
|
|
"/auth/me",
|
||
|
|
headers={"Authorization": f"Bearer {token}"},
|
||
|
|
)
|
||
|
|
|
||
|
|
assert response.status_code == 200
|
||
|
|
body = response.json()
|
||
|
|
assert body["code"] == 0
|
||
|
|
data = body["data"]
|
||
|
|
assert data["employee_id"] == "test-user-001"
|
||
|
|
assert data["name"] == "测试用户"
|
||
|
|
assert data["roles"] == ["agent"]
|
||
|
|
assert data["current_role"] == "agent"
|
||
|
|
assert data["login_source"] == "agent"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_get_current_user_without_auth_returns_error(self, client, mock_redis):
|
||
|
|
"""验证未登录获取用户信息返回错误。"""
|
||
|
|
response = await client.get("/auth/me")
|
||
|
|
|
||
|
|
# 未鉴权应该返回 401 或 403
|
||
|
|
assert response.status_code in (401, 403)
|
||
|
|
|
||
|
|
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
# 5. POST /auth/switch-role — 切换角色
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
class TestSwitchRoleEndpoint:
|
||
|
|
"""测试统一认证的角色切换端点。"""
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_switch_role_without_auth_returns_error(self, client, mock_redis):
|
||
|
|
"""验证未登录切换角色返回错误。"""
|
||
|
|
response = await client.post(
|
||
|
|
"/auth/switch-role",
|
||
|
|
json={"role": "admin"},
|
||
|
|
)
|
||
|
|
|
||
|
|
# 未鉴权应该返回 401 或 403
|
||
|
|
assert response.status_code in (401, 403)
|
||
|
|
|
||
|
|
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
# 6. 废弃 API 测试
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
class TestDeprecatedAPIs:
|
||
|
|
"""测试废弃的登录 API 是否正确禁用或返回提示。"""
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_agents_login_still_works(self, client, mock_redis, db_session):
|
||
|
|
"""验证坐席登录端点仍然可用。"""
|
||
|
|
# agents/login 是坐席登录,仍应可用
|
||
|
|
response = await client.post(
|
||
|
|
"/agents/login",
|
||
|
|
json={"user_id": "test-agent-001", "name": "测试坐席"},
|
||
|
|
)
|
||
|
|
# 应该返回 200
|
||
|
|
assert response.status_code == 200
|
||
|
|
body = response.json()
|
||
|
|
assert body["code"] == 0
|
||
|
|
assert "token" in body["data"]
|