roleRepo = $this->createMock(RoleRepositoryInterface::class); $this->settingsGateway = $this->createMock(DirectorySettingsGateway::class); $this->auditService = $this->createMock(SystemAuditService::class); $this->service = new RoleService( $this->roleRepo, $this->settingsGateway, $this->auditService ); } public function testCreateFromAdminSucceeds(): void { $this->roleRepo ->expects($this->once()) ->method('existsByCode') ->with('EDITOR', 0) ->willReturn(false); $this->roleRepo ->expects($this->once()) ->method('create') ->willReturn(42); $this->roleRepo ->expects($this->once()) ->method('find') ->with(42) ->willReturn(['id' => 42, 'uuid' => 'uuid-42', 'description' => 'Editor']); $this->settingsGateway ->expects($this->never()) ->method('setDefaultRoleId'); $this->auditService ->expects($this->once()) ->method('record') ->with('admin.roles.create', 'success', $this->anything()); $result = $this->service->createFromAdmin([ 'description' => 'Editor', 'code' => 'EDITOR', 'active' => 1, ], 5); $this->assertTrue($result['ok']); $this->assertSame('uuid-42', $result['uuid']); $this->assertSame(42, $result['id']); $this->assertEmpty($result['warnings']); } public function testCreateFromAdminReturnsDuplicateCodeAsWarning(): void { $this->roleRepo ->expects($this->any()) ->method('existsByCode') ->with('DUPE', 0) ->willReturn(true); $this->roleRepo ->expects($this->any()) ->method('create') ->willReturn(50); $this->roleRepo ->expects($this->any()) ->method('find') ->with(50) ->willReturn(['id' => 50, 'uuid' => 'uuid-50', 'description' => 'Dupe Role']); $this->auditService ->expects($this->once()) ->method('record'); $result = $this->service->createFromAdmin([ 'description' => 'Dupe Role', 'code' => 'DUPE', 'active' => 1, ], 1); $this->assertTrue($result['ok']); $this->assertNotEmpty($result['warnings']); $this->assertEmpty($result['errors'] ?? []); } public function testCreateFromAdminRejectsEmptyDescription(): void { $this->roleRepo ->expects($this->never()) ->method('create'); $result = $this->service->createFromAdmin([ 'description' => ' ', 'code' => '', 'active' => 1, ]); $this->assertFalse($result['ok']); $this->assertNotEmpty($result['errors']); } public function testCreateFromAdminReturnsErrorWhenRepositoryCreateFails(): void { $this->roleRepo ->expects($this->once()) ->method('existsByCode') ->with('EDITOR', 0) ->willReturn(false); $this->roleRepo ->expects($this->once()) ->method('create') ->willReturn(false); $this->roleRepo ->expects($this->never()) ->method('find'); $this->auditService ->expects($this->never()) ->method('record'); $result = $this->service->createFromAdmin([ 'description' => 'Editor', 'code' => 'EDITOR', 'active' => 1, ], 5); $this->assertFalse($result['ok']); $this->assertNotEmpty($result['errors']); } public function testCreateFromAdminSetsDefaultRoleWhenRequested(): void { $this->roleRepo ->method('existsByCode') ->willReturn(false); $this->roleRepo ->method('create') ->willReturn(99); $this->roleRepo ->expects($this->any()) ->method('find') ->with(99) ->willReturn(['id' => 99, 'uuid' => 'uuid-99', 'description' => 'Default']); $this->settingsGateway ->expects($this->once()) ->method('setDefaultRoleId') ->with(99); $this->auditService ->expects($this->once()) ->method('record'); $result = $this->service->createFromAdmin([ 'description' => 'Default', 'code' => '', 'active' => 1, 'is_default' => 1, ], 3); $this->assertTrue($result['ok']); } public function testUpdateFromAdminSucceeds(): void { $this->roleRepo ->expects($this->once()) ->method('existsByCode') ->with('MGR', 10) ->willReturn(false); $this->roleRepo ->expects($this->once()) ->method('find') ->with(10) ->willReturn(['id' => 10, 'uuid' => 'uuid-10', 'active' => 1]); $this->roleRepo ->expects($this->once()) ->method('update') ->with(10, $this->anything()) ->willReturn(true); $this->auditService ->expects($this->once()) ->method('record') ->with('admin.roles.update', 'success', $this->anything()); $result = $this->service->updateFromAdmin(10, [ 'description' => 'Manager', 'code' => 'MGR', 'active' => 1, ], 5); $this->assertTrue($result['ok']); } public function testUpdateFromAdminReturnsDuplicateCodeAsWarning(): void { $this->roleRepo ->expects($this->once()) ->method('existsByCode') ->with('ADMIN', 10) ->willReturn(true); $this->roleRepo ->expects($this->once()) ->method('find') ->with(10) ->willReturn(['id' => 10, 'uuid' => 'uuid-10', 'active' => 1]); $this->roleRepo ->expects($this->once()) ->method('update') ->with(10, $this->anything()) ->willReturn(true); $this->auditService ->expects($this->once()) ->method('record'); $result = $this->service->updateFromAdmin(10, [ 'description' => 'Manager', 'code' => 'ADMIN', 'active' => 1, ], 5); $this->assertTrue($result['ok']); $this->assertNotEmpty($result['warnings']); $this->assertEmpty($result['errors'] ?? []); } public function testUpdateFromAdminReturnsErrorWhenRepositoryUpdateFails(): void { $this->roleRepo ->expects($this->once()) ->method('existsByCode') ->with('MGR', 10) ->willReturn(false); $this->roleRepo ->expects($this->once()) ->method('find') ->with(10) ->willReturn(['id' => 10, 'uuid' => 'uuid-10', 'active' => 1]); $this->roleRepo ->expects($this->once()) ->method('update') ->with(10, $this->anything()) ->willReturn(false); $this->auditService ->expects($this->never()) ->method('record'); $result = $this->service->updateFromAdmin(10, [ 'description' => 'Manager', 'code' => 'MGR', 'active' => 1, ], 5); $this->assertFalse($result['ok']); $this->assertNotEmpty($result['errors']); } public function testDeleteByUuidSucceeds(): void { $role = ['id' => 7, 'uuid' => 'uuid-7', 'description' => 'Guest']; $this->roleRepo ->expects($this->once()) ->method('findByUuid') ->with('uuid-7') ->willReturn($role); $this->roleRepo ->expects($this->once()) ->method('delete') ->with(7) ->willReturn(true); $this->auditService ->expects($this->once()) ->method('record') ->with('admin.roles.delete', 'success', $this->anything()); $result = $this->service->deleteByUuid('uuid-7'); $this->assertTrue($result['ok']); $this->assertSame($role, $result['role']); } public function testDeleteByUuidProtectsAdminRole(): void { $this->roleRepo ->expects($this->once()) ->method('findByUuid') ->with('uuid-admin') ->willReturn(['id' => 1, 'uuid' => 'uuid-admin', 'description' => 'Admin']); $this->roleRepo ->expects($this->never()) ->method('delete'); $result = $this->service->deleteByUuid('uuid-admin'); $this->assertFalse($result['ok']); $this->assertSame(403, $result['status']); $this->assertSame('admin_role_protected', $result['error']); } public function testDeleteByUuidReturnsNotFoundForEmptyUuid(): void { $this->roleRepo ->expects($this->never()) ->method('findByUuid'); $result = $this->service->deleteByUuid(' '); $this->assertFalse($result['ok']); $this->assertSame(404, $result['status']); $this->assertSame('not_found', $result['error']); } public function testDeleteByUuidReturnsDeleteFailedOnRepoFailure(): void { $this->roleRepo ->expects($this->once()) ->method('findByUuid') ->with('uuid-8') ->willReturn(['id' => 8, 'uuid' => 'uuid-8', 'description' => 'Viewer']); $this->roleRepo ->expects($this->once()) ->method('delete') ->with(8) ->willReturn(false); $this->auditService ->expects($this->never()) ->method('record'); $result = $this->service->deleteByUuid('uuid-8'); $this->assertFalse($result['ok']); $this->assertSame(500, $result['status']); $this->assertSame('delete_failed', $result['error']); } }