TenantAtlas/tests/Unit/TenantPermissionServiceTest.php
ahmido 1acbf8cc54 feat(spec-088): remove tenant graphOptions legacy path (#105)
## Summary
- remove tenant-based Graph options access from runtime service paths and enforce provider-only resolution
- add `MicrosoftGraphOptionsResolver` and `ProviderConfigurationRequiredException` for centralized, actionable provider-config errors
- turn `Tenant::graphOptions()` into a fail-fast kill switch to prevent legacy runtime usage
- add and update tests (including guardrail) to enforce no reintroduction in `app/`
- update Spec 088 artifacts (`spec`, `plan`, `research`, `tasks`, checklist)

## Validation
- `vendor/bin/sail bin pint --dirty`
- `vendor/bin/sail artisan test --compact --filter=NoLegacyTenantGraphOptions`
- `vendor/bin/sail artisan test --compact tests/Feature/Filament`
- `CI=1 vendor/bin/sail artisan test --compact`

## Notes
- Branch includes the guardrail test for legacy callsite detection in `app/`.
- Full suite currently green: 1227 passed, 5 skipped.

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box>
Reviewed-on: #105
2026-02-12 10:14:44 +00:00

162 lines
5.1 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;
uses(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',
]);
ensureDefaultProviderConnection($tenant, 'microsoft');
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',
]);
ensureDefaultProviderConnection($tenant, 'microsoft');
$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',
]);
ensureDefaultProviderConnection($tenant, 'microsoft');
$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();
ensureDefaultProviderConnection($tenant, 'microsoft');
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);
});