2026-09-01 21:03:38 +02:00
<? php
declare ( strict_types = 1 );
require_once __DIR__ . '/../app/Domain/User/PasswordPolicy.php' ;
require_once __DIR__ . '/../app/Domain/User/UserRecord.php' ;
require_once __DIR__ . '/../app/Domain/User/RoleRecord.php' ;
require_once __DIR__ . '/../app/Domain/User/PermissionMatrix.php' ;
require_once __DIR__ . '/../app/Domain/User/UserAdminService.php' ;
require_once __DIR__ . '/../app/Domain/User/RolePermissionService.php' ;
use App\Domain\User\RolePermissionService ;
use App\Domain\User\UserAdminService ;
function user_admin_assert_same ( mixed $expected , mixed $actual , string $message ) : void
{
if ( $expected !== $actual ) {
throw new RuntimeException ( $message . " \n Expected: " . var_export ( $expected , true ) . " \n Actual: " . var_export ( $actual , true ));
}
}
$users = new UserAdminService ();
$existing = [
[ 'id' => 7 , 'name' => 'Alice Example' , 'email' => 'alice@example.test' , 'role_name' => 'Accounts' , 'role_id' => 2 , 'is_active' => true ],
[ 'id' => 8 , 'name' => 'Bob Example' , 'email' => 'bob@example.test' , 'role_name' => 'Technician' , 'role_id' => 3 , 'is_active' => false ],
];
$edited = $users -> validateForEdit ( 7 , [
'name' => ' Alice Updated ' , 'email' => ' ALICE.UPDATED@EXAMPLE.TEST ' , 'role_id' => '3' , 'is_active' => 'yes' ,
], $existing );
user_admin_assert_same ( true , $edited [ 'valid' ], 'A valid user edit should pass.' );
user_admin_assert_same ( 'alice.updated@example.test' , $edited [ 'email' ], 'User edit email should be normalized.' );
user_admin_assert_same ( 7 , $edited [ 'id' ], 'User edit should retain the explicit ID.' );
$duplicate = $users -> validateForEdit ( 7 , [
'name' => 'Alice Updated' , 'email' => ' bob@example.test ' , 'role_id' => 2 , 'is_active' => true ,
], $existing );
user_admin_assert_same ( false , $duplicate [ 'valid' ], 'A duplicate user email should fail edit validation.' );
if ( ! isset ( $duplicate [ 'errors' ][ 'email' ])) throw new RuntimeException ( 'Duplicate user email should produce an email error.' );
user_admin_assert_same (
[ 'valid' => true , 'id' => 7 , 'is_active' => false , 'errors' => []],
$users -> validateDeactivate ( $existing [ 0 ]),
'Active users should be deactivatable.'
);
user_admin_assert_same (
[ 'valid' => true , 'id' => 8 , 'is_active' => true , 'errors' => []],
$users -> validateReactivate ( $existing [ 1 ]),
'Inactive users should be reactivatable.'
);
$protected = [ 'id' => 1 , 'name' => 'Root' , 'email' => 'root@example.test' , 'role_name' => ' Administrator ' , 'role_id' => 1 , 'is_active' => true ];
$protectedResult = $users -> validateDeactivate ( $protected );
user_admin_assert_same ( false , $protectedResult [ 'valid' ], 'The Administrator account must not be deactivated.' );
if ( ! isset ( $protectedResult [ 'errors' ][ 'role' ])) throw new RuntimeException ( 'Protected Administrator deactivation should produce a role error.' );
$reset = $users -> validatePasswordReset ( $existing [ 0 ], 'Unique&Secure123' );
user_admin_assert_same ( true , $reset [ 'valid' ], 'A strong password reset should pass.' );
if ( array_key_exists ( 'password' , $reset )) throw new RuntimeException ( 'Password reset validation must not return plaintext passwords.' );
$weakReset = $users -> validatePasswordReset ( $existing [ 0 ], 'Password123!' );
user_admin_assert_same ( false , $weakReset [ 'valid' ], 'Weak password reset input should fail.' );
if ( ! isset ( $weakReset [ 'errors' ][ 'password' ])) throw new RuntimeException ( 'Weak password reset should produce a password error.' );
$payloadReset = $users -> validateReset ([ 'password' => 'Unique&Secure123' ]);
user_admin_assert_same ( true , $payloadReset [ 'valid' ], 'Password-only reset payloads should be supported.' );
2026-09-01 21:47:35 +02:00
// Administrator identity is authoritative from the immutable user ID, even when
// a controller passes only editable fields and omits current role/name fields.
$protectedEdit = $users -> validateEdit ( 1 , [
'name' => 'Renamed' , 'email' => 'admin.updated@example.test' , 'role_id' => 2 , 'is_active' => true ,
]);
user_admin_assert_same ( false , $protectedEdit [ 'valid' ], 'User ID 1 must remain protected without current fields.' );
if ( ! isset ( $protectedEdit [ 'errors' ][ 'role_id' ])) throw new RuntimeException ( 'Administrator reassignment must be rejected from ID alone.' );
user_admin_assert_same ( false , $users -> validateDeactivate ([ 'id' => 1 , 'is_active' => true ])[ 'valid' ], 'Administrator deactivation must be rejected from ID alone.' );
2026-09-01 21:03:38 +02:00
$roles = new RolePermissionService ();
$available = [ 'clients.view' , 'clients.manage' , 'reports.view' ];
$assignment = $roles -> validateAssignment ([ 'id' => 4 , 'name' => 'Support' ], [ ' CLIENTS.VIEW ' , 'reports.view' , 'clients.view' ], $available );
user_admin_assert_same ( true , $assignment [ 'valid' ], 'Known permissions should be assignable to a custom role.' );
user_admin_assert_same ([ 'clients.view' , 'reports.view' ], $assignment [ 'permissions' ], 'Permission assignments should be canonical and de-duplicated.' );
$unknown = $roles -> validateAssignment ([ 'id' => 4 , 'name' => 'Support' ], [ 'clients.view' , 'users.delete' ], $available );
user_admin_assert_same ( false , $unknown [ 'valid' ], 'Unknown permissions must not be assignable.' );
if ( ! isset ( $unknown [ 'errors' ][ 'permissions' ])) throw new RuntimeException ( 'Unknown permission should produce a permissions error.' );
$protectedRole = $roles -> validateAssignment ([ 'id' => 1 , 'name' => 'Administrator' ], [ 'clients.view' ], $available );
user_admin_assert_same ( false , $protectedRole [ 'valid' ], 'Administrator permissions must be protected.' );
if ( ! isset ( $protectedRole [ 'errors' ][ 'role' ])) throw new RuntimeException ( 'Administrator permission changes should produce a role error.' );
printf ( "User administration service tests: 8 passed \n " );