TenantAtlas/tests/Unit/TenantPermissionServiceTest.php
ahmido d2dbc52a32 feat(006): foundations + assignment mapping and preview-only restore guard (#7)
## Summary
- Capture and restore foundation types (assignment filters, scope tags, notification templates) with deterministic mapping.
- Apply foundation mappings during restore (scope tags on policy payloads, assignment filter mapping with skip reasons).
- Improve restore run UX (item selection, rerun action, preview-only badges).
- Enforce preview-only policy types (e.g. Conditional Access) during execution.

## Testing
- ./vendor/bin/sail artisan test tests/Feature/Filament/ConditionalAccessPreviewOnlyTest.php

## Notes
- Specs/plan/tasks updated under specs/006-sot-foundations-assignments.
- No migrations.

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local>
Reviewed-on: #7
2025-12-26 23:44:31 +00:00

156 lines
4.9 KiB
PHP

<?php
use App\Models\Tenant;
use App\Models\TenantPermission;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphResponse;
use App\Services\Intune\TenantPermissionService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
function requiredPermissions(): array
{
$service = app(TenantPermissionService::class);
$required = $service->getRequiredPermissions();
if (empty($required)) {
test()->markTestSkipped('No required permissions configured.');
}
return $required;
}
it('returns ok when all permissions exist', function () {
// Mock GraphClient to return all permissions as granted
$this->mock(GraphClientInterface::class, function ($mock) {
$mock->shouldReceive('getServicePrincipalPermissions')
->andReturn(new GraphResponse(true, [
'value' => collect(config('intune_permissions.permissions', []))
->map(fn ($p) => ['value' => $p['key']])
->toArray(),
]));
});
$tenant = Tenant::create([
'tenant_id' => 'tenant-ok',
'name' => 'Tenant OK',
]);
foreach (requiredPermissions() as $permission) {
TenantPermission::create([
'tenant_id' => $tenant->id,
'permission_key' => $permission['key'],
'status' => 'granted',
]);
}
$result = app(TenantPermissionService::class)->compare($tenant);
expect($result['overall_status'])->toBe('granted');
expect(TenantPermission::where('tenant_id', $tenant->id)->where('status', 'granted')->count())
->toBe(count(requiredPermissions()));
});
it('marks missing permissions when not granted', function () {
$permissions = requiredPermissions();
// Mock GraphClient to return only first permission as granted
$this->mock(GraphClientInterface::class, function ($mock) use ($permissions) {
$mock->shouldReceive('getServicePrincipalPermissions')
->andReturn(new GraphResponse(true, [
'permissions' => [$permissions[0]['key']],
]));
});
$tenant = Tenant::create([
'tenant_id' => 'tenant-missing',
'name' => 'Tenant Missing',
]);
$first = $permissions[0]['key'];
TenantPermission::create([
'tenant_id' => $tenant->id,
'permission_key' => $first,
'status' => 'ok',
]);
// Use liveCheck=true to trigger Graph API call
$result = app(TenantPermissionService::class)->compare($tenant, null, true, true);
expect($result['overall_status'])->toBe('missing');
$missingKey = $permissions[1]['key'] ?? null;
if ($missingKey) {
$this->assertDatabaseHas('tenant_permissions', [
'tenant_id' => $tenant->id,
'permission_key' => $missingKey,
'status' => 'missing',
]);
}
});
it('reports error statuses from graph comparison', function () {
// Mock GraphClient to return an error
$this->mock(GraphClientInterface::class, function ($mock) {
$mock->shouldReceive('getServicePrincipalPermissions')
->andReturn(new GraphResponse(false, [], 500, ['Graph API error']));
});
$tenant = Tenant::create([
'tenant_id' => 'tenant-error',
'name' => 'Tenant Error',
]);
$permissions = requiredPermissions();
$first = $permissions[0]['key'];
$result = app(TenantPermissionService::class)->compare($tenant, [
$first => [
'status' => 'error',
'details' => ['message' => 'forbidden'],
],
]);
expect($result['overall_status'])->toBe('error');
$this->assertDatabaseHas('tenant_permissions', [
'tenant_id' => $tenant->id,
'permission_key' => $first,
'status' => 'error',
]);
});
it('ignores configured stub permissions when requested', function () {
$originalPermissions = config('intune_permissions.permissions');
$originalStub = config('intune_permissions.granted_stub');
config()->set('intune_permissions.permissions', [
[
'key' => 'DeviceManagementRBAC.ReadWrite.All',
'type' => 'application',
'description' => null,
'features' => [],
],
]);
config()->set('intune_permissions.granted_stub', ['DeviceManagementRBAC.ReadWrite.All']);
$tenant = Tenant::factory()->create();
TenantPermission::create([
'tenant_id' => $tenant->id,
'permission_key' => 'DeviceManagementRBAC.ReadWrite.All',
'status' => 'granted',
'details' => ['source' => 'configured'],
]);
$result = app(TenantPermissionService::class)->compare($tenant, persist: false, useConfiguredStub: false);
expect($result['overall_status'])->toBe('missing');
expect($result['permissions'][0]['status'])->toBe('missing');
config()->set('intune_permissions.permissions', $originalPermissions);
config()->set('intune_permissions.granted_stub', $originalStub);
});