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::factory()->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::factory()->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::factory()->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); }); it('persists permissions with workspace_id even when model events are disabled', function () { $tenant = Tenant::factory()->create(); ensureDefaultProviderConnection($tenant, 'microsoft'); TenantPermission::withoutEvents(function () use ($tenant): void { app(TenantPermissionService::class)->compare($tenant); }); $this->assertDatabaseHas('tenant_permissions', [ 'tenant_id' => $tenant->id, 'workspace_id' => $tenant->workspace_id, ]); }); it('persists permissions when the tenant instance does not have workspace_id loaded', function () { $tenant = Tenant::factory()->create(); ensureDefaultProviderConnection($tenant, 'microsoft'); $tenantWithoutWorkspaceId = Tenant::query() ->select(['id', 'tenant_id', 'external_id', 'name', 'status', 'environment']) ->findOrFail((int) $tenant->getKey()); expect($tenantWithoutWorkspaceId->getAttribute('workspace_id'))->toBeNull(); app(TenantPermissionService::class)->compare($tenantWithoutWorkspaceId); $this->assertDatabaseHas('tenant_permissions', [ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, ]); }); it('does not persist when tenant workspace_id is missing', function () { $tenant = Tenant::withoutEvents(function (): Tenant { return Tenant::create([ 'tenant_id' => 'tenant-no-workspace', 'external_id' => 'tenant-no-workspace', 'name' => 'Tenant No Workspace', 'status' => Tenant::STATUS_ACTIVE, 'environment' => 'other', 'workspace_id' => null, ]); }); app(TenantPermissionService::class)->compare($tenant, persist: true); expect(TenantPermission::query()->where('tenant_id', (int) $tenant->getKey())->count())->toBe(0); });