159 lines
5.7 KiB
Python
159 lines
5.7 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
"""
|
|
Unit tests for RBAC bootstrap initialization.
|
|
Tests that bootstrap creates correct rules and initial data.
|
|
"""
|
|
|
|
from unittest.mock import Mock, patch
|
|
|
|
from modules.interfaces.interfaceBootstrap import (
|
|
initRootMandate,
|
|
initAdminUser,
|
|
initEventUser,
|
|
initRbacRules,
|
|
_createDefaultRoleRules,
|
|
_createTableSpecificRules,
|
|
)
|
|
from modules.datamodels.datamodelUam import UserInDB, Mandate
|
|
from modules.datamodels.datamodelRbac import AccessRule, AccessRuleContext
|
|
from modules.datamodels.datamodelUam import AccessLevel
|
|
|
|
|
|
class TestRbacBootstrap:
|
|
"""Test RBAC bootstrap initialization."""
|
|
|
|
def testInitRootMandateCreatesIfNotExists(self):
|
|
"""Test that initRootMandate creates mandate if it doesn't exist."""
|
|
db = Mock()
|
|
db.getRecordset = Mock(return_value=[])
|
|
db.recordCreate = Mock(return_value={"id": "mandate1", "name": "Root"})
|
|
|
|
mandateId = initRootMandate(db)
|
|
|
|
assert mandateId == "mandate1"
|
|
db.recordCreate.assert_called_once()
|
|
callArgs = db.recordCreate.call_args
|
|
assert isinstance(callArgs[0][1], Mandate)
|
|
assert callArgs[0][1].name == "root"
|
|
assert callArgs[0][1].label == "Root"
|
|
|
|
def testInitRootMandateReturnsExisting(self):
|
|
"""Test that initRootMandate returns existing mandate ID."""
|
|
db = Mock()
|
|
db.getRecordset = Mock(return_value=[{"id": "existing_mandate"}])
|
|
|
|
mandateId = initRootMandate(db)
|
|
|
|
assert mandateId == "existing_mandate"
|
|
db.recordCreate.assert_not_called()
|
|
|
|
def testInitAdminUserCreatesWithSysadminRole(self):
|
|
"""Test that initAdminUser creates user with isSysAdmin=True."""
|
|
db = Mock()
|
|
db.getRecordset = Mock(return_value=[])
|
|
db.recordCreate = Mock(return_value={"id": "admin1", "username": "admin"})
|
|
|
|
with patch("modules.interfaces.interfaceBootstrap._getPasswordHash", return_value="hashed"):
|
|
userId = initAdminUser(db, "mandate1")
|
|
|
|
assert userId == "admin1"
|
|
db.recordCreate.assert_called_once()
|
|
callArgs = db.recordCreate.call_args
|
|
user = callArgs[0][1]
|
|
assert isinstance(user, UserInDB)
|
|
assert user.username == "admin"
|
|
assert user.isSysAdmin is True
|
|
|
|
def testInitEventUserCreatesWithSysadminRole(self):
|
|
"""Test that initEventUser creates user with isSysAdmin=True."""
|
|
db = Mock()
|
|
db.getRecordset = Mock(return_value=[])
|
|
db.recordCreate = Mock(return_value={"id": "event1", "username": "event"})
|
|
|
|
with patch("modules.interfaces.interfaceBootstrap._getPasswordHash", return_value="hashed"):
|
|
userId = initEventUser(db, "mandate1")
|
|
|
|
assert userId == "event1"
|
|
db.recordCreate.assert_called_once()
|
|
callArgs = db.recordCreate.call_args
|
|
user = callArgs[0][1]
|
|
assert isinstance(user, UserInDB)
|
|
assert user.username == "event"
|
|
assert user.isSysAdmin is True
|
|
|
|
def testCreateDefaultRoleRules(self):
|
|
"""Test that _createDefaultRoleRules creates admin + viewer generic DATA rules."""
|
|
db = Mock()
|
|
db.recordCreate = Mock()
|
|
|
|
with patch(
|
|
"modules.interfaces.interfaceBootstrap._getRoleId",
|
|
side_effect=lambda d, label: f"rid-{label}",
|
|
):
|
|
_createDefaultRoleRules(db)
|
|
|
|
assert db.recordCreate.call_count == 2
|
|
created = [c[0][1] for c in db.recordCreate.call_args_list]
|
|
byRoleId = {r.roleId: r for r in created}
|
|
|
|
adminRule = byRoleId["rid-admin"]
|
|
assert adminRule.context == AccessRuleContext.DATA
|
|
assert adminRule.item is None
|
|
assert adminRule.view is True
|
|
assert adminRule.read == AccessLevel.GROUP
|
|
assert adminRule.create == AccessLevel.GROUP
|
|
|
|
viewerRule = byRoleId["rid-viewer"]
|
|
assert viewerRule.read == AccessLevel.GROUP
|
|
assert viewerRule.create == AccessLevel.NONE
|
|
|
|
def testCreateTableSpecificRules(self):
|
|
"""Test that _createTableSpecificRules creates table-specific rules."""
|
|
db = Mock()
|
|
db.recordCreate = Mock()
|
|
|
|
with patch(
|
|
"modules.interfaces.interfaceBootstrap._getRoleId",
|
|
side_effect=lambda d, label: f"rid-{label}",
|
|
):
|
|
_createTableSpecificRules(db)
|
|
|
|
assert db.recordCreate.call_count > 0
|
|
|
|
mandateCalls = [
|
|
call for call in db.recordCreate.call_args_list if call[0][1].item == "data.uam.Mandate"
|
|
]
|
|
assert len(mandateCalls) > 0
|
|
|
|
for call in mandateCalls:
|
|
rule = call[0][1]
|
|
assert rule.view is False
|
|
assert rule.read == AccessLevel.NONE
|
|
|
|
def testInitRbacRulesSkipsIfExists(self):
|
|
"""When AccessRules already exist, init skips full init; ensure-* hooks are not exercised here."""
|
|
db = Mock()
|
|
db.getRecordset = Mock(return_value=[{"id": "existing_rule"}])
|
|
db.recordCreate = Mock()
|
|
|
|
with patch("modules.interfaces.interfaceBootstrap._ensureUiContextRules"):
|
|
with patch("modules.interfaces.interfaceBootstrap._ensureDataContextRules"):
|
|
initRbacRules(db)
|
|
|
|
db.recordCreate.assert_not_called()
|
|
|
|
def testInitRbacRulesCreatesIfNotExists(self):
|
|
"""Test that initRbacRules creates rules when the AccessRule table is empty."""
|
|
db = Mock()
|
|
db.recordCreate = Mock()
|
|
db.recordModify = Mock()
|
|
db.getRecordset = Mock(return_value=[])
|
|
|
|
with patch(
|
|
"modules.interfaces.interfaceBootstrap._getRoleId",
|
|
side_effect=lambda d, label: f"rid-{label}",
|
|
):
|
|
initRbacRules(db)
|
|
|
|
assert db.recordCreate.call_count > 0
|