Files
JobcardSystem/tests/RolePermissionTest.php
T

134 lines
5.6 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/../app/Domain/User/RoleRecord.php';
require_once __DIR__ . '/../app/Domain/User/PermissionMatrix.php';
require_once __DIR__ . '/../app/Domain/User/RolePermissionService.php';
use App\Domain\User\PermissionMatrix;
use App\Domain\User\RoleRecord;
function role_permission_assert_same(mixed $expected, mixed $actual, string $message): void
{
if ($expected !== $actual) {
throw new RuntimeException($message . "\nExpected: " . var_export($expected, true) . "\nActual: " . var_export($actual, true));
}
}
function role_permission_assert_throws(callable $callback, string $message): void
{
try {
$callback();
} catch (LogicException) {
return;
}
throw new RuntimeException($message);
}
$roles = new RoleRecord();
role_permission_assert_same([
'name' => 'Support Team',
'description' => 'Handles customer support',
], $roles->normalize([
'name' => ' Support Team ',
'description' => ' Handles customer support ',
'permissions' => ['users.manage'],
]), 'Role normalization should trim supported fields and ignore unrelated fields.');
$valid = $roles->validate(['name' => 'Support Team', 'description' => str_repeat('x', 255)]);
role_permission_assert_same(true, $valid['valid'], 'A schema-sized custom role should validate.');
role_permission_assert_same([], $valid['errors'], 'Valid role should have no errors.');
$invalid = $roles->validate(['name' => "\x01", 'description' => str_repeat('x', 256)]);
role_permission_assert_same(false, $invalid['valid'], 'Invalid role data should report invalid.');
if (!isset($invalid['errors']['name'], $invalid['errors']['description'])) {
throw new RuntimeException('Role validation should report name and description errors.');
}
role_permission_assert_throws(
fn() => $roles->assertCanRename(['name' => 'Administrator'], 'Security Administrator'),
'Administrator must not be renamed.'
);
role_permission_assert_throws(
fn() => $roles->assertCanDelete(['name' => ' administrator ']),
'Administrator must not be deleted.'
);
role_permission_assert_throws(
fn() => $roles->assertCanChangePermissions(['name' => 'Administrator']),
'Administrator permissions must not be changed.'
);
role_permission_assert_throws(
fn() => $roles->assertCanRename(['name' => 'Accounts'], ' administrator '),
'A custom role must not be renamed to Administrator.'
);
role_permission_assert_same(true, $roles->canRename(['name' => 'Accounts'], 'Support'), 'Custom roles should be renameable.');
role_permission_assert_same(false, $roles->canDelete(['name' => 'Administrator']), 'Administrator deletion safeguard should be queryable.');
// ID 1 is authoritative for the protected role; current name is not required.
$adminEdit = (new App\Domain\User\RolePermissionService())->validateEdit(1, [
'name' => 'Renamed Administrator', 'description' => 'changed', 'permissions' => ['clients.view'],
]);
role_permission_assert_same(false, $adminEdit['valid'], 'Role ID 1 must remain protected without current_name.');
if (!isset($adminEdit['errors']['role'])) throw new RuntimeException('Administrator rename/permission changes must be rejected from ID alone.');
role_permission_assert_same(false, $roles->canDelete(['id' => 1]), 'Role ID 1 must not be deletable without a name.');
$customCreate = (new App\Domain\User\RolePermissionService())->validateForCreate([
'name' => ' Dispatch ', 'description' => ' Handles dispatch ', 'permissions' => ['CLIENTS.VIEW'],
], ['clients.view']);
role_permission_assert_same(true, $customCreate['valid'], 'Custom role create DTO should validate and normalize permissions.');
role_permission_assert_same('Dispatch', $customCreate['name'], 'Custom role names should be normalized in create DTOs.');
role_permission_assert_same(['clients.view'], $customCreate['permissions'], 'Create DTO should include canonical permissions.');
$customDelete = (new App\Domain\User\RolePermissionService())->validateDelete(4, ['id' => 4, 'name' => 'Dispatch']);
role_permission_assert_same(['valid' => true, 'id' => 4, 'errors' => []], $customDelete, 'Custom role delete DTO should be safe and controller-ready.');
$display = $roles->display([
'id' => 4,
'name' => 'Support Team',
'description' => 'Handles support',
'created_at' => '2026-09-01 08:00:00',
'permissions' => ['clients.view'],
'password_hash' => 'secret',
'internal_notes' => 'omit',
]);
role_permission_assert_same([
'id' => 4,
'name' => 'Support Team',
'description' => 'Handles support',
'created_at' => '2026-09-01 08:00:00',
'permissions' => ['clients.view'],
], $display, 'Role display projection must allow-list safe fields.');
$permissions = new PermissionMatrix();
role_permission_assert_same([
'clients.view',
'jobcards.manage',
'reports.view',
], $permissions->normalize([
' clients.view ',
'jobcards.manage',
'clients.view',
'',
'reports.view',
' ',
]), 'Permissions should be trimmed, blank entries removed, and duplicates de-duplicated.');
role_permission_assert_same([
'clients.view',
'jobcards.manage',
], $permissions->normalize(['CLIENTS.VIEW', 'clients.view', ' jobcards.manage ']), 'Permission normalization should use canonical lower-case names.');
role_permission_assert_same([
'id' => 4,
'name' => 'Support Team',
'permissions' => ['clients.view', 'jobcards.manage'],
], $permissions->display([
'id' => 4,
'name' => 'Support Team',
'permissions' => ['clients.view', 'jobcards.manage', 'clients.view'],
'password' => 'secret',
'token' => 'secret',
]), 'Permission display projection must be safe and normalized.');
printf("Role and permission tests: 10 passed\n");