requests[] = ['getPolicy', $policyType, $policyId, $options]; if ($policyType === 'mobileApp') { return new GraphResponse(success: true, data: [ 'payload' => [ 'id' => $policyId, 'displayName' => 'Contoso Portal', 'publisher' => 'Contoso', 'description' => 'Company Portal', '@odata.type' => '#microsoft.graph.win32LobApp', 'createdDateTime' => '2025-01-01T00:00:00Z', 'lastModifiedDateTime' => '2025-01-02T00:00:00Z', 'roleScopeTagIds' => ['0', 'tag-1', 'tag-2'], 'installCommandLine' => 'setup.exe /quiet', 'largeIcon' => ['type' => 'image/png', 'value' => '...'], ], ]); } return new GraphResponse(success: true, data: [ 'payload' => [ 'id' => $policyId, 'displayName' => 'Compliance Alpha', '@odata.type' => '#microsoft.graph.windows10CompliancePolicy', ], ]); } public function getOrganization(array $options = []): GraphResponse { return new GraphResponse(success: true, data: []); } public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse { return new GraphResponse(success: true, data: []); } public function getServicePrincipalPermissions(array $options = []): GraphResponse { return new GraphResponse(success: true, data: []); } public function request(string $method, string $path, array $options = []): GraphResponse { $this->requests[] = [$method, $path]; if (str_contains($path, 'scheduledActionsForRule')) { return new GraphResponse(success: true, data: [ 'value' => [ [ 'ruleName' => 'Default rule', 'scheduledActionConfigurations' => [ [ 'actionType' => 'notification', 'notificationTemplateId' => 'template-123', ], ], ], ], ]); } return new GraphResponse(success: true, data: []); } } it('hydrates compliance policy scheduled actions into snapshots', function () { $client = new PolicySnapshotGraphClient; app()->instance(GraphClientInterface::class, $client); $tenant = Tenant::factory()->create([ 'tenant_id' => 'tenant-compliance', 'app_client_id' => 'client-123', 'app_client_secret' => 'secret-123', 'is_current' => true, ]); $tenant->makeCurrent(); $policy = Policy::factory()->create([ 'tenant_id' => $tenant->id, 'external_id' => 'compliance-123', 'policy_type' => 'deviceCompliancePolicy', 'display_name' => 'Compliance Alpha', 'platform' => 'windows', ]); $service = app(PolicySnapshotService::class); $result = $service->fetch($tenant, $policy); expect($result)->toHaveKey('payload'); expect($result['payload'])->toHaveKey('scheduledActionsForRule'); expect($result['payload']['scheduledActionsForRule'])->toHaveCount(1); expect($result['payload']['scheduledActionsForRule'][0]['scheduledActionConfigurations'][0]['notificationTemplateId']) ->toBe('template-123'); expect($result['metadata']['compliance_actions_hydration'])->toBe('complete'); expect($client->requests[0][0])->toBe('getPolicy'); expect($client->requests[0][1])->toBe('deviceCompliancePolicy'); expect($client->requests[0][2])->toBe('compliance-123'); expect($client->requests[0][3]['expand'] ?? null) ->toBe('scheduledActionsForRule($expand=scheduledActionConfigurations)'); }); it('filters mobile app snapshots to metadata-only keys', function () { $client = new PolicySnapshotGraphClient; app()->instance(GraphClientInterface::class, $client); $tenant = Tenant::factory()->create([ 'tenant_id' => 'tenant-apps', 'app_client_id' => 'client-123', 'app_client_secret' => 'secret-123', 'is_current' => true, ]); $tenant->makeCurrent(); $policy = Policy::factory()->create([ 'tenant_id' => $tenant->id, 'external_id' => 'app-123', 'policy_type' => 'mobileApp', 'display_name' => 'Contoso Portal', 'platform' => 'all', ]); $service = app(PolicySnapshotService::class); $result = $service->fetch($tenant, $policy); expect($result['payload'])->toHaveKeys([ 'id', 'displayName', 'publisher', 'description', '@odata.type', 'createdDateTime', 'lastModifiedDateTime', 'roleScopeTagIds', ]); expect($result['payload']['roleScopeTagIds'])->toBe(['0', 'tag-1', 'tag-2']); expect($result['payload'])->not->toHaveKey('installCommandLine'); expect($result['payload'])->not->toHaveKey('largeIcon'); expect($client->requests[0][0])->toBe('getPolicy'); expect($client->requests[0][1])->toBe('mobileApp'); expect($client->requests[0][2])->toBe('app-123'); expect($client->requests[0][3]['select'] ?? null)->toBeArray(); expect($client->requests[0][3]['select'])->toContain('displayName'); expect($client->requests[0][3]['select'])->toContain('roleScopeTagIds'); expect($client->requests[0][3]['select'])->not->toContain('@odata.type'); });