bea288e414
== 已部署上线 (9项) == - 代办事项真实数据源集成 (企微审批API 8bug修复链) - H5/坐席端 Logo样式统一+绿色背景 - 视频引导页修复 (localStorage key v2) - 坐席端 v9 Vue版本修复 (ElMessage._context) - 截图按钮 v10 修复 (getDisplayMedia user gesture) - 扫码样式恢复+H5扫码登录跳转修复 - H5截图快捷键提示 == 代码完成待部署 (3项) == - 知识迭代3Bug修复 (#8 POST端点/#7 MERGE幂等/#6 过期检查) - 会议室预定-小鱼易联终端 (40文件, 40/40测试通过) - IT资产升级审批推送 (asset_service.py) == 需求文档 (2项) == - 坐席端AI辅助消息框-PRD (4项新功能确认) - 坐席端布局优化建议 v2.0 (7天计划) == 新增文档 == - 日报-2026-07-11.md - 知识迭代Bug修复报告-20260711.md - 会议室预定-部署指南.md - CHANGELOG.md 更新 == 测试 == - test_todo_integration.py: 40/40 - test_meetingroom.py: 40/40 - test_bugfix_ki_suggestions.py: 21/21
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"]
|