What Changed Removed per-file uses(TestCase::class ...) bindings in Unit tests to avoid Pest v4 “folder already uses the test case” discovery failure (kept RefreshDatabase where needed). Updated the backup scheduling job test to pass the newly required BulkOperationService when manually calling RunBackupScheduleJob::handle(). Where Unit (bulk cleanup across 56 files) RunBackupScheduleJobTest.php Verification ./vendor/bin/sail test → 443 passed, 5 skipped Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local> Reviewed-on: #45
467 lines
22 KiB
PHP
467 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;
|
|
|
|
uses(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');
|
|
});
|