Files
wecom_it_smart_desk/backend/tests/test_rbac_verification.py
T

114 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""功能④ RBAC 细粒度角色权限 — 验真测试。
验真目标:
1. 角色/权限模型 + 种子数据是否真实存在(rbac_service / models / scripts
2. require_role / require_permission 在 API 层是否真正生效
—— 期望:admin 可访问(200),非 admin 被拒(403)
—— 实际:所有 /admin/users 端点返回 422(装饰器被误用为 Depends
说明:本测试中的"期望行为"断言是正确的(符合 PRD/设计),
若断言失败,说明是"源码缺陷"而非"测试写错"
"""
from sqlalchemy import select
from app.models.agent import Agent
from app.models.role import Role
from app.models.user_role import UserRole
from app.services.rbac_service import ROLE_PERMISSIONS, check_permission
from tests.conftest import create_test_agent
# ---------------------------------------------------------------------------
# 1. 角色/权限模型(单元测试):证明模型是真实的
# ---------------------------------------------------------------------------
def test_rbac_role_permissions_model_is_real():
"""ROLE_PERMISSIONS 包含 5 个角色,admin 使用通配符 *:*:all。"""
expected_roles = {"user", "agent", "team_lead", "auditor", "admin"}
assert expected_roles.issubset(set(ROLE_PERMISSIONS.keys())), (
f"缺少角色: {expected_roles - set(ROLE_PERMISSIONS.keys())}"
)
# admin 使用通配符表示全权限
assert ("*", "*", "all") in ROLE_PERMISSIONS["admin"], "admin 缺少 *:*:all 通配符"
def test_check_permission_returns_true_for_granted():
"""已授权角色应返回 True。"""
ok = check_permission(
user_roles=["admin"],
user_permissions={"admin": ["conversation:read:all"]},
required_resource="conversation",
required_action="read",
required_scope="all",
)
assert ok is True
def test_check_permission_returns_false_for_denied():
"""未授权角色应返回 False。"""
ok = check_permission(
user_roles=["agent"],
user_permissions={"agent": ["conversation:read:own"]},
required_resource="conversation",
required_action="read",
required_scope="all",
)
assert ok is False
# ---------------------------------------------------------------------------
# 2. API 层 RBAC 生效性(集成测试):期望 admin=200 / 非 admin=403
# 若返回 422,则证明装饰器被误用为 Depends(源码缺陷)
# ---------------------------------------------------------------------------
async def _seed_role(db_session, role_name: str) -> str:
role = (await db_session.execute(select(Role).where(Role.name == role_name))).scalars().first()
if not role:
role = Role(id=f"rbac-{role_name}", name=role_name, display_name=role_name, permissions=[])
db_session.add(role)
await db_session.flush()
return role.id
async def _login(client, user_id, name):
r = await client.post("/agents/login", json={"user_id": user_id, "name": name})
assert r.status_code == 200, f"登录失败: {r.text}"
return r.json()["data"]["token"]
async def test_admin_user_list_allows_admin(client, db_session):
"""期望:拥有 admin 角色的坐席访问 GET /admin/users 应返回 200。
实际结果若为 422,证明 require_role 装饰器被误用为 Depends(require_role(...))。
"""
role_id = await _seed_role(db_session, "admin")
admin = create_test_agent(user_id="rbac_admin", name="RBAC管理员")
admin.role = "admin"
db_session.add(admin)
await db_session.flush()
db_session.add(UserRole(id="rbac-ur-1", employee_id="rbac_admin", role_id=role_id, source="manual"))
await db_session.flush()
token = await _login(client, "rbac_admin", "RBAC管理员")
r = await client.get("/admin/users", headers={"Authorization": f"Bearer {token}"})
# 期望 200;若源码正确,应得到 200。若为 422 则装饰器误用。
assert r.status_code == 200, f"期望200,实际 {r.status_code}: {r.text[:300]}"
async def test_admin_user_list_denies_non_admin(client, db_session):
"""期望:无 admin 角色(仅 agent)访问 GET /admin/users 应返回 403。
实际结果若为 422,证明装饰器被误用(与权限无关地全部 422)。
"""
role_id = await _seed_role(db_session, "agent")
agent = create_test_agent(user_id="rbac_agent", name="RBAC坐席")
agent.role = "agent"
db_session.add(agent)
await db_session.flush()
db_session.add(UserRole(id="rbac-ur-2", employee_id="rbac_agent", role_id=role_id, source="manual"))
await db_session.flush()
token = await _login(client, "rbac_agent", "RBAC坐席")
r = await client.get("/admin/users", headers={"Authorization": f"Bearer {token}"})
# 期望 403(权限不足);若源码正确。若为 422 则装饰器误用。
assert r.status_code == 403, f"期望403,实际 {r.status_code}: {r.text[:300]}"