114 lines
4.9 KiB
Python
114 lines
4.9 KiB
Python
# -*- 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]}"
|