$responses */ public function __construct(private array $responses) {} public function listPolicies(string $policyType, array $options = []): GraphResponse { return new GraphResponse(success: true, data: []); } public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse { return new GraphResponse(success: true, data: []); } 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' => $method, 'path' => $path, 'options' => $options, ]; return array_shift($this->responses) ?? new GraphResponse(success: true, data: []); } } it('returns a failure when the foundation contract is missing', function () { $tenant = Tenant::factory()->create(); $client = new FoundationSnapshotGraphClient([]); app()->instance(GraphClientInterface::class, $client); $service = app(FoundationSnapshotService::class); $result = $service->fetchAll($tenant, 'unknownFoundation'); expect($result['items'])->toBeEmpty(); expect($result['failures'])->toHaveCount(1); expect($result['failures'][0]['foundation_type'])->toBe('unknownFoundation'); }); it('hydrates foundation snapshots across pages with metadata', function () { config()->set('graph_contracts.types.assignmentFilter', [ 'resource' => 'deviceManagement/assignmentFilters', 'allowed_select' => ['id', 'displayName'], ]); $tenant = Tenant::factory()->create([ 'tenant_id' => 'tenant-123', 'app_client_id' => 'client-123', 'app_client_secret' => 'secret-123', ]); $responses = [ new GraphResponse( success: true, data: [ 'value' => [ ['id' => 'filter-1', 'displayName' => 'Filter One'], ], '@odata.nextLink' => 'https://graph.microsoft.com/beta/deviceManagement/assignmentFilters?$skiptoken=abc', ], ), new GraphResponse( success: true, data: [ 'value' => [ ['id' => 'filter-2', 'displayName' => 'Filter Two'], ], ], ), ]; $client = new FoundationSnapshotGraphClient($responses); app()->instance(GraphClientInterface::class, $client); $service = app(FoundationSnapshotService::class); $result = $service->fetchAll($tenant, 'assignmentFilter'); expect($result['items'])->toHaveCount(2); expect($result['items'][0]['source_id'])->toBe('filter-1'); expect($result['items'][0]['metadata']['displayName'])->toBe('Filter One'); expect($result['items'][0]['metadata']['kind'])->toBe('assignmentFilter'); expect($result['items'][1]['source_id'])->toBe('filter-2'); expect($client->requests[0]['path'])->toBe('deviceManagement/assignmentFilters'); expect($client->requests[0]['options']['query']['$select'])->toBe(['id', 'displayName']); expect($client->requests[1]['path'])->toBe('deviceManagement/assignmentFilters?$skiptoken=abc'); expect($client->requests[1]['options']['query'])->toBe([]); });