469 lines
22 KiB
PHP
469 lines
22 KiB
PHP
<?php
|
|
|
|
use App\Models\Tenant;
|
|
use App\Services\Graph\GraphClientInterface;
|
|
use App\Services\Graph\GraphResponse;
|
|
use App\Services\Intune\RbacOnboardingService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
uses(TestCase::class, RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
config()->set('tenantpilot.features.conditional_access', false);
|
|
});
|
|
|
|
function fakeTenant(): Tenant
|
|
{
|
|
return Tenant::create([
|
|
'tenant_id' => '00000000-0000-0000-0000-000000000000',
|
|
'name' => 'Tenant One',
|
|
'app_client_id' => 'app-client-123',
|
|
'app_client_secret' => 'secret',
|
|
'is_current' => true,
|
|
]);
|
|
}
|
|
|
|
it('creates group membership and role assignment', function () {
|
|
$tenant = fakeTenant();
|
|
|
|
$graph = \Mockery::mock(GraphClientInterface::class);
|
|
|
|
$graph->shouldReceive('request')->andReturnUsing(function (string $method, string $path, array $options = []) {
|
|
return match ([$method, $path]) {
|
|
['GET', 'servicePrincipals'] => new GraphResponse(true, ['value' => [['id' => 'sp-1']]]),
|
|
['GET', 'groups'] => new GraphResponse(true, ['value' => []]),
|
|
['POST', 'groups'] => new GraphResponse(true, ['id' => 'group-1']),
|
|
['POST', 'groups/group-1/members/$ref'] => new GraphResponse(true, []),
|
|
['GET', 'deviceManagement/roleAssignments'] => new GraphResponse(true, ['value' => [
|
|
['id' => 'assign-1', 'resourceScopes' => ['/'], 'roleDefinition' => ['id' => 'role-1']],
|
|
]]),
|
|
['GET', 'deviceManagement/roleAssignments/assign-1'] => new GraphResponse(true, ['id' => 'assign-1', 'members' => ['group-1'], 'resourceScopes' => ['/']]),
|
|
['POST', 'deviceManagement/roleAssignments'] => new GraphResponse(true, ['id' => 'assign-1']),
|
|
['GET', 'deviceManagement/deviceConfigurations?$top=1'] => new GraphResponse(true, ['value' => []]),
|
|
['GET', 'deviceManagement/deviceCompliancePolicies?$top=1'] => new GraphResponse(true, ['value' => []]),
|
|
default => throw new RuntimeException("Unexpected Graph request: {$method} {$path}"),
|
|
};
|
|
});
|
|
|
|
app()->instance(GraphClientInterface::class, $graph);
|
|
|
|
$service = app(RbacOnboardingService::class);
|
|
|
|
$result = $service->run($tenant, [
|
|
'group_mode' => 'create',
|
|
'role_definition_id' => 'role-1',
|
|
'role_display_name' => 'Policy and Profile Manager',
|
|
'scope' => 'all_devices',
|
|
], null, 'access-token');
|
|
|
|
expect($result['status'])->toBe('ok');
|
|
expect($result['group_id'])->toBe('group-1');
|
|
expect($result['role_assignment_id'])->toBe('assign-1');
|
|
$tenant->refresh();
|
|
expect($tenant->rbac_group_id)->toBe('group-1');
|
|
expect($tenant->rbac_role_assignment_id)->toBe('assign-1');
|
|
expect($tenant->rbac_role_definition_id)->toBe('role-1');
|
|
expect($tenant->rbac_role_display_name)->toBe('Policy and Profile Manager');
|
|
});
|
|
|
|
it('is idempotent when group membership already exists', function () {
|
|
$tenant = fakeTenant();
|
|
|
|
$graph = \Mockery::mock(GraphClientInterface::class);
|
|
|
|
$graph->shouldReceive('request')->andReturnUsing(function (string $method, string $path) {
|
|
return match ([$method, $path]) {
|
|
['GET', 'servicePrincipals'] => new GraphResponse(true, ['value' => [['id' => 'sp-1']]]),
|
|
['POST', 'groups/group-1/members/$ref'] => new GraphResponse(false, [], 400, [['message' => 'One or more added object references already exist']]),
|
|
['GET', 'deviceManagement/roleAssignments'] => new GraphResponse(true, ['value' => [
|
|
['id' => 'assign-1', 'resourceScopes' => ['/'], 'roleDefinition' => ['id' => 'role-1']],
|
|
]]),
|
|
['GET', 'deviceManagement/roleAssignments/assign-1'] => new GraphResponse(true, ['id' => 'assign-1', 'members' => ['group-1'], 'resourceScopes' => ['/']]),
|
|
['POST', 'deviceManagement/roleAssignments'] => new GraphResponse(true, ['id' => 'assign-1']),
|
|
['GET', 'deviceManagement/deviceConfigurations?$top=1'] => new GraphResponse(true, ['value' => []]),
|
|
['GET', 'deviceManagement/deviceCompliancePolicies?$top=1'] => new GraphResponse(true, ['value' => []]),
|
|
default => new GraphResponse(true, ['value' => []]),
|
|
};
|
|
});
|
|
|
|
app()->instance(GraphClientInterface::class, $graph);
|
|
|
|
$service = app(RbacOnboardingService::class);
|
|
|
|
$result = $service->run($tenant, [
|
|
'group_mode' => 'existing',
|
|
'existing_group_id' => 'group-1',
|
|
'role_definition_id' => 'role-1',
|
|
'role_display_name' => 'Policy and Profile Manager',
|
|
'scope' => 'all_devices',
|
|
], null, 'access-token');
|
|
|
|
expect($result['status'])->toBe('ok');
|
|
expect($result['group_id'])->toBe('group-1');
|
|
});
|
|
|
|
it('requires a role definition id', function () {
|
|
$tenant = fakeTenant();
|
|
|
|
$graph = \Mockery::mock(GraphClientInterface::class);
|
|
$graph->shouldReceive('request')->never();
|
|
app()->instance(GraphClientInterface::class, $graph);
|
|
|
|
$service = app(RbacOnboardingService::class);
|
|
|
|
$result = $service->run($tenant, [
|
|
'group_mode' => 'create',
|
|
'scope' => 'all_devices',
|
|
], null, 'access-token');
|
|
|
|
expect($result['status'])->toBe('error');
|
|
expect(strtolower($result['message']))->toContain('roledefinitionid');
|
|
});
|
|
|
|
it('is idempotent when membership add returns a request-exception style message', function () {
|
|
$tenant = fakeTenant();
|
|
|
|
$graph = \Mockery::mock(GraphClientInterface::class);
|
|
|
|
$message = 'HTTP request returned status code 400: {"error":{"code":"Request_BadRequest","message":"One or more added object references already exist for the following modified properties: \'members\'."}}';
|
|
|
|
$graph->shouldReceive('request')->andReturnUsing(function (string $method, string $path) use ($message) {
|
|
return match ([$method, $path]) {
|
|
['GET', 'servicePrincipals'] => new GraphResponse(true, ['value' => [['id' => 'sp-1']]]),
|
|
['POST', 'groups/group-1/members/$ref'] => new GraphResponse(false, [], 400, [$message]),
|
|
['GET', 'deviceManagement/roleAssignments'] => new GraphResponse(true, ['value' => [
|
|
['id' => 'assign-1', 'resourceScopes' => ['/'], 'roleDefinition' => ['id' => 'role-1']],
|
|
]]),
|
|
['GET', 'deviceManagement/roleAssignments/assign-1'] => new GraphResponse(true, ['id' => 'assign-1', 'members' => ['group-1'], 'resourceScopes' => ['/']]),
|
|
['POST', 'deviceManagement/roleAssignments'] => new GraphResponse(true, ['id' => 'assign-1']),
|
|
['GET', 'deviceManagement/deviceConfigurations?$top=1'] => new GraphResponse(true, ['value' => []]),
|
|
['GET', 'deviceManagement/deviceCompliancePolicies?$top=1'] => new GraphResponse(true, ['value' => []]),
|
|
default => new GraphResponse(true, ['value' => []]),
|
|
};
|
|
});
|
|
|
|
app()->instance(GraphClientInterface::class, $graph);
|
|
|
|
$service = app(RbacOnboardingService::class);
|
|
|
|
$result = $service->run($tenant, [
|
|
'group_mode' => 'existing',
|
|
'existing_group_id' => 'group-1',
|
|
'role_definition_id' => 'role-1',
|
|
'role_display_name' => 'Policy and Profile Manager',
|
|
'scope' => 'all_devices',
|
|
], null, 'access-token');
|
|
|
|
expect($result['status'])->toBe('ok');
|
|
expect($result['group_id'])->toBe('group-1');
|
|
});
|
|
|
|
it('is idempotent when group membership reference already exists', function () {
|
|
$tenant = fakeTenant();
|
|
|
|
$graph = \Mockery::mock(GraphClientInterface::class);
|
|
|
|
$groupId = 'group-1';
|
|
$servicePrincipalId = 'sp-1';
|
|
|
|
$error = [
|
|
'error' => [
|
|
'code' => 'Request_BadRequest',
|
|
'message' => 'One or more added object references already exist for the following modified properties: \'members\'.',
|
|
],
|
|
];
|
|
|
|
$graph->shouldReceive('request')->andReturnUsing(function (string $method, string $path, array $options = []) use ($groupId, $servicePrincipalId, $error) {
|
|
return match ([$method, $path]) {
|
|
['GET', 'servicePrincipals'] => new GraphResponse(true, ['value' => [['id' => $servicePrincipalId]]]),
|
|
['GET', 'groups'] => new GraphResponse(true, ['value' => []]),
|
|
['POST', 'groups'] => new GraphResponse(true, ['id' => $groupId]),
|
|
['POST', "groups/{$groupId}/members/\$ref"] => new GraphResponse(false, $error, 400),
|
|
['GET', 'deviceManagement/roleDefinitions'] => new GraphResponse(true, ['value' => [['id' => 'role-1', 'displayName' => 'Policy and Profile Manager']]]),
|
|
['GET', 'deviceManagement/roleAssignments'] => new GraphResponse(true, ['value' => []]),
|
|
['POST', 'deviceManagement/roleAssignments'] => new GraphResponse(true, ['id' => 'assign-1']),
|
|
['GET', 'deviceManagement/deviceConfigurations?$top=1'] => new GraphResponse(true, ['value' => []]),
|
|
['GET', 'deviceManagement/deviceCompliancePolicies?$top=1'] => new GraphResponse(true, ['value' => []]),
|
|
['GET', 'identity/conditionalAccess/policies?$top=1'] => new GraphResponse(true, ['value' => []]),
|
|
default => throw new RuntimeException("Unexpected Graph request: {$method} {$path}"),
|
|
};
|
|
});
|
|
|
|
app()->instance(GraphClientInterface::class, $graph);
|
|
|
|
$service = app(RbacOnboardingService::class);
|
|
|
|
$result = $service->run($tenant, [
|
|
'group_mode' => 'create',
|
|
'role_definition_id' => 'role-1',
|
|
'role_display_name' => 'Policy and Profile Manager',
|
|
'scope' => 'all_devices',
|
|
], null, 'access-token');
|
|
|
|
expect($result['status'])->toBe('ok');
|
|
expect($result['group_id'])->toBe('group-1');
|
|
expect($result['role_assignment_id'])->toBe('assign-1');
|
|
$tenant->refresh();
|
|
expect($tenant->rbac_group_id)->toBe('group-1');
|
|
expect($tenant->rbac_role_assignment_id)->toBe('assign-1');
|
|
});
|
|
|
|
it('surfaces membership errors with step, path, and status context', function () {
|
|
$tenant = fakeTenant();
|
|
|
|
$graph = \Mockery::mock(GraphClientInterface::class);
|
|
|
|
$graph->shouldReceive('request')->andReturn(
|
|
new GraphResponse(true, ['value' => [['id' => 'sp-1']]]), // service principal
|
|
new GraphResponse(true, ['value' => []]), // existing group lookup
|
|
new GraphResponse(true, ['id' => 'group-1']), // create group
|
|
new GraphResponse(false, ['error' => ['code' => 'Request_BadRequest', 'message' => 'Different failure']], 400), // add member fails
|
|
);
|
|
|
|
app()->instance(GraphClientInterface::class, $graph);
|
|
|
|
$service = app(RbacOnboardingService::class);
|
|
|
|
$result = $service->run($tenant, [
|
|
'group_mode' => 'create',
|
|
'role_definition_id' => 'role-1',
|
|
'role_display_name' => 'Policy and Profile Manager',
|
|
'scope' => 'all_devices',
|
|
], null, 'access-token');
|
|
|
|
expect($result['status'])->toBe('error');
|
|
expect($result['message'])->toContain('step=ensureGroupMembership');
|
|
expect($result['message'])->toContain('path=/groups/group-1/members/$ref');
|
|
expect($result['message'])->toContain('status=400');
|
|
});
|
|
|
|
it('continues when role assignment member fetch fails and creates new assignment', function () {
|
|
$tenant = fakeTenant();
|
|
|
|
$graph = \Mockery::mock(GraphClientInterface::class);
|
|
|
|
$graph->shouldReceive('request')->andReturnUsing(function (string $method, string $path) {
|
|
return match ([$method, $path]) {
|
|
['GET', 'servicePrincipals'] => new GraphResponse(true, ['value' => [['id' => 'sp-1']]]),
|
|
['GET', 'groups'] => new GraphResponse(true, ['value' => []]),
|
|
['POST', 'groups'] => new GraphResponse(true, ['id' => 'group-1']),
|
|
['POST', 'groups/group-1/members/$ref'] => new GraphResponse(true, []),
|
|
['GET', 'deviceManagement/roleAssignments'] => new GraphResponse(true, ['value' => [
|
|
['id' => 'assign-1', 'resourceScopes' => ['/'], 'roleDefinition' => ['id' => 'role-1']],
|
|
]]),
|
|
['GET', 'deviceManagement/roleAssignments/assign-1'] => new GraphResponse(false, [
|
|
'error' => ['code' => 'BadRequest', 'message' => 'Unsupported $expand for members'],
|
|
], 400),
|
|
['POST', 'deviceManagement/roleAssignments'] => new GraphResponse(true, ['id' => 'assign-2']),
|
|
default => new GraphResponse(true, ['value' => []]),
|
|
};
|
|
});
|
|
|
|
app()->instance(GraphClientInterface::class, $graph);
|
|
|
|
$service = app(RbacOnboardingService::class);
|
|
|
|
$result = $service->run($tenant, [
|
|
'group_mode' => 'create',
|
|
'role_definition_id' => 'role-1',
|
|
'role_display_name' => 'Policy and Profile Manager',
|
|
'scope' => 'all_devices',
|
|
], null, 'access-token');
|
|
|
|
expect($result['status'])->toBe('ok');
|
|
expect($result['role_assignment_id'])->toBe('assign-2');
|
|
});
|
|
|
|
it('surfaces role assignment create errors with status and message', function () {
|
|
$tenant = fakeTenant();
|
|
|
|
$graph = \Mockery::mock(GraphClientInterface::class);
|
|
|
|
$graph->shouldReceive('request')->andReturnUsing(function (string $method, string $path) {
|
|
return match ([$method, $path]) {
|
|
['GET', 'servicePrincipals'] => new GraphResponse(true, ['value' => [['id' => 'sp-1']]]),
|
|
['GET', 'groups'] => new GraphResponse(true, ['value' => []]),
|
|
['POST', 'groups'] => new GraphResponse(true, ['id' => 'group-1']),
|
|
['POST', 'groups/group-1/members/$ref'] => new GraphResponse(true, []),
|
|
['GET', 'deviceManagement/roleAssignments'] => new GraphResponse(true, ['value' => []]),
|
|
['POST', 'deviceManagement/roleAssignments'] => new GraphResponse(false, [
|
|
'error' => ['code' => 'BadRequest', 'message' => 'Invalid members@odata.bind'],
|
|
], 400),
|
|
default => new GraphResponse(true, ['value' => []]),
|
|
};
|
|
});
|
|
|
|
app()->instance(GraphClientInterface::class, $graph);
|
|
|
|
$service = app(RbacOnboardingService::class);
|
|
|
|
$result = $service->run($tenant, [
|
|
'group_mode' => 'create',
|
|
'role_definition_id' => 'role-1',
|
|
'role_display_name' => 'Policy and Profile Manager',
|
|
'scope' => 'all_devices',
|
|
], null, 'access-token');
|
|
|
|
expect($result['status'])->toBe('error');
|
|
expect($result['message'])->toContain('step=createRoleAssignment');
|
|
expect($result['message'])->toContain('status=400');
|
|
expect($result['message'])->toContain('Invalid members@odata.bind');
|
|
});
|
|
|
|
it('handles unsupported AAD account API gracefully with manual setup instructions', function () {
|
|
$tenant = fakeTenant();
|
|
|
|
$graph = \Mockery::mock(GraphClientInterface::class);
|
|
|
|
$graph->shouldReceive('request')->andReturnUsing(function (string $method, string $path) {
|
|
return match ([$method, $path]) {
|
|
['GET', 'servicePrincipals'] => new GraphResponse(true, ['value' => [['id' => 'sp-1']]]),
|
|
['GET', 'groups'] => new GraphResponse(true, ['value' => []]),
|
|
['POST', 'groups'] => new GraphResponse(true, ['id' => 'group-1', 'displayName' => 'TenantPilot-Intune-RBAC']),
|
|
['POST', 'groups/group-1/members/$ref'] => new GraphResponse(true, []),
|
|
['GET', 'deviceManagement/roleAssignments'] => new GraphResponse(true, ['value' => []]),
|
|
['POST', 'deviceManagement/roleAssignments'] => new GraphResponse(false, [
|
|
'error' => ['code' => 'BadRequest', 'message' => 'This API is not supported for AAD accounts (no addressUrl for Microsoft.Intune.Rbac,False).'],
|
|
], 400, [], [], ['request_id' => 'req-123', 'client_request_id' => 'client-456']),
|
|
['GET', 'deviceManagement/deviceConfigurations?$top=1'] => new GraphResponse(true, ['value' => []]),
|
|
['GET', 'deviceManagement/deviceCompliancePolicies?$top=1'] => new GraphResponse(true, ['value' => []]),
|
|
default => new GraphResponse(true, ['value' => []]),
|
|
};
|
|
});
|
|
|
|
app()->instance(GraphClientInterface::class, $graph);
|
|
|
|
$service = app(RbacOnboardingService::class);
|
|
|
|
$result = $service->run($tenant, [
|
|
'group_mode' => 'create',
|
|
'role_definition_id' => 'role-1',
|
|
'role_display_name' => 'Policy and Profile Manager',
|
|
'scope' => 'all_devices',
|
|
], null, 'access-token');
|
|
|
|
// Should be partial success, not complete failure
|
|
expect($result['status'])->toBe('manual_assignment_required');
|
|
expect($result['message'])->toContain('Intune RBAC API does not support automated role assignments');
|
|
expect($result['message'])->toContain('partially complete');
|
|
expect($result['message'])->toContain('TenantPilot-Intune-RBAC');
|
|
expect($result['message'])->toContain('group-1');
|
|
expect($result['message'])->toContain('request_id=req-123');
|
|
expect($result['message'])->toContain('client_request_id=client-456');
|
|
expect($result['group_id'])->toBe('group-1');
|
|
expect($result['role_assignment_id'])->toBeNull();
|
|
expect($result['warnings'])->toContain('manual_role_assignment_required');
|
|
expect($result['steps'])->toContain('role_assignment_manual_required');
|
|
});
|
|
|
|
it('lists role assignments without unsupported fields or expands', function () {
|
|
$tenant = fakeTenant();
|
|
$checked = false;
|
|
|
|
$graph = \Mockery::mock(GraphClientInterface::class);
|
|
|
|
$graph->shouldReceive('request')->andReturnUsing(function (string $method, string $path, array $options = []) use (&$checked) {
|
|
return match ([$method, $path]) {
|
|
['GET', 'servicePrincipals'] => new GraphResponse(true, ['value' => [['id' => 'sp-1']]]),
|
|
['GET', 'groups'] => new GraphResponse(true, ['value' => []]),
|
|
['POST', 'groups'] => new GraphResponse(true, ['id' => 'group-1']),
|
|
['POST', 'groups/group-1/members/$ref'] => new GraphResponse(true, []),
|
|
['GET', 'deviceManagement/roleAssignments'] => tap(new GraphResponse(true, ['value' => [[
|
|
'id' => 'assign-1',
|
|
'members' => ['group-1'],
|
|
'resourceScopes' => ['/'],
|
|
'roleDefinition' => ['id' => 'role-1', 'displayName' => 'Policy and Profile Manager'],
|
|
]]]), function () use ($options, &$checked) {
|
|
expect($options['query']['$select'] ?? null)->toBe('id,displayName,resourceScopes,members');
|
|
expect($options['query']['$expand'] ?? null)->toBe('roleDefinition($select=id,displayName)');
|
|
$checked = true;
|
|
}),
|
|
['POST', 'deviceManagement/roleAssignments'] => new GraphResponse(true, ['id' => 'assign-1']),
|
|
['GET', 'deviceManagement/deviceConfigurations?$top=1'] => new GraphResponse(true, ['value' => []]),
|
|
['GET', 'deviceManagement/deviceCompliancePolicies?$top=1'] => new GraphResponse(true, ['value' => []]),
|
|
default => throw new RuntimeException("Unexpected Graph request: {$method} {$path}"),
|
|
};
|
|
});
|
|
|
|
app()->instance(GraphClientInterface::class, $graph);
|
|
|
|
$service = app(RbacOnboardingService::class);
|
|
|
|
$result = $service->run($tenant, [
|
|
'group_mode' => 'create',
|
|
'role_definition_id' => 'role-1',
|
|
'role_display_name' => 'Policy and Profile Manager',
|
|
'scope' => 'all_devices',
|
|
], null, 'access-token');
|
|
|
|
expect($checked)->toBeTrue();
|
|
expect($result['status'])->toBe('ok');
|
|
expect($result['role_assignment_id'])->toBe('assign-1');
|
|
});
|
|
|
|
it('falls back when role assignment members are missing without crashing', function () {
|
|
$tenant = fakeTenant();
|
|
|
|
$graph = \Mockery::mock(GraphClientInterface::class);
|
|
|
|
$graph->shouldReceive('request')->andReturnUsing(function (string $method, string $path) {
|
|
return match ([$method, $path]) {
|
|
['GET', 'servicePrincipals'] => new GraphResponse(true, ['value' => [['id' => 'sp-1']]]),
|
|
['GET', 'groups'] => new GraphResponse(true, ['value' => []]),
|
|
['POST', 'groups'] => new GraphResponse(true, ['id' => 'group-1']),
|
|
['POST', 'groups/group-1/members/$ref'] => new GraphResponse(true, []),
|
|
['GET', 'deviceManagement/roleAssignments'] => new GraphResponse(true, ['value' => [[
|
|
'id' => 'assign-1',
|
|
'resourceScopes' => ['/'],
|
|
'roleDefinition' => ['id' => 'role-1'],
|
|
]]]),
|
|
['GET', 'deviceManagement/roleAssignments/assign-1'] => new GraphResponse(false, [], 400, [
|
|
['error' => ['code' => 'BadRequest', 'message' => 'expand not allowed']],
|
|
]),
|
|
['POST', 'deviceManagement/roleAssignments'] => new GraphResponse(true, ['id' => 'assign-2']),
|
|
['GET', 'deviceManagement/deviceConfigurations?$top=1'] => new GraphResponse(true, ['value' => []]),
|
|
['GET', 'deviceManagement/deviceCompliancePolicies?$top=1'] => new GraphResponse(true, ['value' => []]),
|
|
default => throw new RuntimeException("Unexpected Graph request: {$method} {$path}"),
|
|
};
|
|
});
|
|
|
|
app()->instance(GraphClientInterface::class, $graph);
|
|
|
|
$service = app(RbacOnboardingService::class);
|
|
|
|
$result = $service->run($tenant, [
|
|
'group_mode' => 'create',
|
|
'role_definition_id' => 'role-1',
|
|
'role_display_name' => 'Policy and Profile Manager',
|
|
'scope' => 'all_devices',
|
|
], null, 'access-token');
|
|
|
|
expect($result['status'])->toBe('ok');
|
|
expect($result['role_assignment_id'])->toBe('assign-2');
|
|
});
|
|
|
|
it('fails when service principal is missing', function () {
|
|
$tenant = fakeTenant();
|
|
|
|
$graph = \Mockery::mock(GraphClientInterface::class);
|
|
|
|
$graph->shouldReceive('request')->once()->andReturn(
|
|
new GraphResponse(true, ['value' => []])
|
|
);
|
|
|
|
app()->instance(GraphClientInterface::class, $graph);
|
|
|
|
$service = app(RbacOnboardingService::class);
|
|
|
|
$result = $service->run($tenant, [
|
|
'group_mode' => 'create',
|
|
'role_definition_id' => 'role-1',
|
|
'role_display_name' => 'Policy and Profile Manager',
|
|
'scope' => 'all_devices',
|
|
], null, 'access-token');
|
|
|
|
expect($result['status'])->toBe('error');
|
|
expect(strtolower($result['message']))->toContain('service principal');
|
|
});
|