166 lines
6.5 KiB
Python
166 lines
6.5 KiB
Python
|
|
"""角色授权并集、即时撤权与独立工资月份锁,不再使用个人覆盖或全局绑定。"""
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from pydantic import ValidationError
|
||
|
|
from sqlalchemy import select
|
||
|
|
|
||
|
|
from app.core.exceptions import BadRequestError, ForbiddenError
|
||
|
|
from app.models import Permission, Role, RolePermission, User, UserRole
|
||
|
|
from app.schemas.authorization import AuthorizationPreviewIn
|
||
|
|
from app.services import authorization_service as service
|
||
|
|
from app.services.permission_catalog import CATALOG
|
||
|
|
from tests.unit.test_tplus_workbench import work_session as work_session
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def auth_case(work_session):
|
||
|
|
session = work_session
|
||
|
|
user = User(username="auth_test", password="test_hash", status=1)
|
||
|
|
role = Role(role_code="auth_role", role_name="测试权限", status=1)
|
||
|
|
session.add_all([user, role])
|
||
|
|
session.flush()
|
||
|
|
session.add(UserRole(user_id=user.id, role_id=role.id))
|
||
|
|
for code in (
|
||
|
|
"base:personnel:view",
|
||
|
|
"base:personnel:regions",
|
||
|
|
"payroll:view",
|
||
|
|
"payroll:calculate",
|
||
|
|
):
|
||
|
|
permission = session.scalar(select(Permission).where(Permission.perm_code == code))
|
||
|
|
session.add(RolePermission(role_id=role.id, permission_id=permission.id))
|
||
|
|
session.commit()
|
||
|
|
return session, user, role
|
||
|
|
|
||
|
|
|
||
|
|
def test_用户权限仅继承角色且来源清晰(auth_case):
|
||
|
|
session, user, _ = auth_case
|
||
|
|
result = service.calculate(session, user)
|
||
|
|
assert set(result["permissions"]) == {
|
||
|
|
"base:personnel:view",
|
||
|
|
"base:personnel:regions",
|
||
|
|
"payroll:view",
|
||
|
|
"payroll:calculate",
|
||
|
|
}
|
||
|
|
assert result["sources"]["payroll:calculate"] == {
|
||
|
|
"roles": ["auth_role"],
|
||
|
|
"override": None,
|
||
|
|
"effective": True,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def test_操作缺少模块查看权限时不生效(auth_case):
|
||
|
|
session, user, role = auth_case
|
||
|
|
permission = session.scalar(
|
||
|
|
select(Permission).where(Permission.perm_code == "base:personnel:view")
|
||
|
|
)
|
||
|
|
link = session.scalar(
|
||
|
|
select(RolePermission).where(
|
||
|
|
RolePermission.role_id == role.id, RolePermission.permission_id == permission.id
|
||
|
|
)
|
||
|
|
)
|
||
|
|
session.delete(link)
|
||
|
|
session.flush()
|
||
|
|
assert "base:personnel:regions" not in service.calculate(session, user)["permissions"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_多个角色权限取并集且无需指定制作人(auth_case):
|
||
|
|
session, user, role = auth_case
|
||
|
|
other = Role(role_code="auth_extra", role_name="额外职责", status=1)
|
||
|
|
session.add(other)
|
||
|
|
session.flush()
|
||
|
|
permission = session.scalar(
|
||
|
|
select(Permission).where(Permission.perm_code == "payroll:allocation")
|
||
|
|
)
|
||
|
|
session.add(RolePermission(role_id=other.id, permission_id=permission.id))
|
||
|
|
session.add(UserRole(user_id=user.id, role_id=other.id))
|
||
|
|
session.flush()
|
||
|
|
assert service.setting(session).maker_user_id is None
|
||
|
|
assert "payroll:allocation" in service.calculate(session, user)["permissions"]
|
||
|
|
service.require_permission(session, user, "payroll:calculate")
|
||
|
|
service.require_permission(session, user, "payroll:allocation")
|
||
|
|
role.status = 0
|
||
|
|
session.flush()
|
||
|
|
assert "payroll:allocation" not in service.calculate(session, user)["permissions"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_角色停用立即撤销旧会话权限(auth_case):
|
||
|
|
session, user, role = auth_case
|
||
|
|
role.status = 0
|
||
|
|
session.flush()
|
||
|
|
assert not service.calculate(session, user)["permissions"]
|
||
|
|
with pytest.raises(ForbiddenError):
|
||
|
|
service.require_permission(session, user, "payroll:calculate")
|
||
|
|
|
||
|
|
|
||
|
|
def test_管理员无需绑定即可使用已配置工资功能(auth_case):
|
||
|
|
session, user, _ = auth_case
|
||
|
|
role = session.scalar(select(Role).where(Role.role_code == "admin"))
|
||
|
|
user.user_roles.append(UserRole(role_id=role.id))
|
||
|
|
session.flush()
|
||
|
|
assert set(service.calculate(session, user)["permissions"]) == set(CATALOG)
|
||
|
|
assert service.setting(session).maker_user_id is None
|
||
|
|
service.require_permission(session, user, "payroll:calculate")
|
||
|
|
|
||
|
|
|
||
|
|
def test_多个获授权账号都能核算且旧绑定不影响判定(auth_case):
|
||
|
|
session, user, role = auth_case
|
||
|
|
other = User(username="another_operator", password="test_hash", status=1)
|
||
|
|
session.add(other)
|
||
|
|
session.flush()
|
||
|
|
session.add(UserRole(user_id=other.id, role_id=role.id))
|
||
|
|
service.setting(session).maker_user_id = user.id
|
||
|
|
session.flush()
|
||
|
|
for actor in (user, other):
|
||
|
|
service.require_permission(session, actor, "payroll:calculate")
|
||
|
|
assert service.session_info(session, actor)["can_write_payroll"]
|
||
|
|
assert service.session_info(session, actor)["maker_required_permissions"] == []
|
||
|
|
with pytest.raises(ForbiddenError):
|
||
|
|
service.require_permission(session, other, "settlements:generate")
|
||
|
|
|
||
|
|
|
||
|
|
def test_旧个人覆盖不再接受且无法另开授权入口(auth_case):
|
||
|
|
session, user, _ = auth_case
|
||
|
|
with pytest.raises(ValidationError):
|
||
|
|
AuthorizationPreviewIn(
|
||
|
|
role_codes=["auth_role"],
|
||
|
|
overrides=[{"permission_code": "payroll:calculate", "effect": "DENY"}],
|
||
|
|
)
|
||
|
|
with pytest.raises(BadRequestError, match="角色授权"):
|
||
|
|
service.calculate(session, user, overrides={"payroll:calculate": "DENY"})
|
||
|
|
|
||
|
|
|
||
|
|
def test_普通角色不能获得管理员专用权限(auth_case):
|
||
|
|
session, user, role = auth_case
|
||
|
|
permission = session.scalar(
|
||
|
|
select(Permission).where(Permission.perm_code == "system:roles:update")
|
||
|
|
)
|
||
|
|
session.add(RolePermission(role_id=role.id, permission_id=permission.id))
|
||
|
|
session.flush()
|
||
|
|
assert "system:roles:update" not in service.calculate(session, user)["permissions"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_停用账号不保留角色权限(auth_case):
|
||
|
|
session, user, _ = auth_case
|
||
|
|
user.status = 0
|
||
|
|
assert not service.calculate(session, user)["permissions"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_取消全局绑定仍保留月份实际经办人与生成锁(auth_case):
|
||
|
|
from datetime import datetime
|
||
|
|
|
||
|
|
from app.models import PayrollMonth
|
||
|
|
from app.services import payroll_months
|
||
|
|
|
||
|
|
session, user, role = auth_case
|
||
|
|
other = User(username="second_month_operator", password="test_hash", status=1)
|
||
|
|
session.add(other)
|
||
|
|
session.flush()
|
||
|
|
session.add(UserRole(user_id=other.id, role_id=role.id))
|
||
|
|
session.add(PayrollMonth(month="2091-11", maker_id=user.id, maker_name=user.username, generated_at=datetime(2091, 11, 30)))
|
||
|
|
session.flush()
|
||
|
|
assert service.setting(session).maker_user_id is None
|
||
|
|
with pytest.raises(ForbiddenError, match="其他制作人"):
|
||
|
|
payroll_months.lock_month(session, "2091-11", other)
|
||
|
|
with pytest.raises(BadRequestError, match="已锁定"):
|
||
|
|
payroll_months.ensure_allocation_editable(session, "2091-11", user)
|