2026-09-01 20:43:24 +02:00
|
|
|
<?php
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
require_once __DIR__ . '/../app/Domain/User/RoleRecord.php';
|
|
|
|
|
require_once __DIR__ . '/../app/Domain/User/PermissionMatrix.php';
|
2026-09-01 21:47:35 +02:00
|
|
|
require_once __DIR__ . '/../app/Domain/User/RolePermissionService.php';
|
2026-09-01 20:43:24 +02:00
|
|
|
|
|
|
|
|
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.');
|
|
|
|
|
|
2026-09-01 21:47:35 +02:00
|
|
|
// 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.');
|
|
|
|
|
|
2026-09-01 20:43:24 +02:00
|
|
|
$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");
|