graphClient = Mockery::mock(MicrosoftGraphClient::class); $this->logger = Mockery::mock(GraphLogger::class); $this->fetcher = new AssignmentFetcher($this->graphClient, $this->logger); }); test('primary endpoint success', function () { $tenantId = 'tenant-123'; $policyId = 'policy-456'; $assignments = [ ['id' => 'assign-1', 'target' => ['@odata.type' => '#microsoft.graph.groupAssignmentTarget', 'groupId' => 'group-1']], ['id' => 'assign-2', 'target' => ['@odata.type' => '#microsoft.graph.groupAssignmentTarget', 'groupId' => 'group-2']], ]; $this->graphClient ->shouldReceive('get') ->once() ->with("/deviceManagement/configurationPolicies/{$policyId}/assignments", $tenantId) ->andReturn(['value' => $assignments]); $this->logger ->shouldReceive('logDebug') ->once() ->with('Fetched assignments via primary endpoint', Mockery::any()); $result = $this->fetcher->fetch($tenantId, $policyId); expect($result)->toBe($assignments); }); test('fallback on empty response', function () { $tenantId = 'tenant-123'; $policyId = 'policy-456'; $assignments = [ ['id' => 'assign-1', 'target' => ['@odata.type' => '#microsoft.graph.groupAssignmentTarget', 'groupId' => 'group-1']], ]; // Primary returns empty $this->graphClient ->shouldReceive('get') ->once() ->with("/deviceManagement/configurationPolicies/{$policyId}/assignments", $tenantId) ->andReturn(['value' => []]); // Fallback returns assignments $this->graphClient ->shouldReceive('get') ->once() ->with('/deviceManagement/configurationPolicies', $tenantId, [ '$expand' => 'assignments', '$filter' => "id eq '{$policyId}'", ]) ->andReturn(['value' => [['id' => $policyId, 'assignments' => $assignments]]]); $this->logger ->shouldReceive('logDebug') ->twice(); $result = $this->fetcher->fetch($tenantId, $policyId); expect($result)->toBe($assignments); }); test('fail soft on error', function () { $tenantId = 'tenant-123'; $policyId = 'policy-456'; $this->graphClient ->shouldReceive('get') ->once() ->andThrow(new GraphException('Graph API error', 500, ['request_id' => 'request-id-123'])); $this->logger ->shouldReceive('logWarning') ->once() ->with('Failed to fetch assignments', Mockery::on(function ($context) use ($tenantId, $policyId) { return $context['tenant_id'] === $tenantId && $context['policy_id'] === $policyId && isset($context['context']['request_id']); })); $result = $this->fetcher->fetch($tenantId, $policyId); expect($result)->toBe([]); }); test('returns empty array when both endpoints return empty', function () { $tenantId = 'tenant-123'; $policyId = 'policy-456'; // Primary returns empty $this->graphClient ->shouldReceive('get') ->once() ->with("/deviceManagement/configurationPolicies/{$policyId}/assignments", $tenantId) ->andReturn(['value' => []]); // Fallback returns empty $this->graphClient ->shouldReceive('get') ->once() ->with('/deviceManagement/configurationPolicies', $tenantId, Mockery::any()) ->andReturn(['value' => []]); $this->logger ->shouldReceive('logDebug') ->times(2); $result = $this->fetcher->fetch($tenantId, $policyId); expect($result)->toBe([]); }); test('fallback handles missing assignments key', function () { $tenantId = 'tenant-123'; $policyId = 'policy-456'; // Primary returns empty $this->graphClient ->shouldReceive('get') ->once() ->andReturn(['value' => []]); // Fallback returns policy without assignments key $this->graphClient ->shouldReceive('get') ->once() ->andReturn(['value' => [['id' => $policyId]]]); $this->logger ->shouldReceive('logDebug') ->times(2); $result = $this->fetcher->fetch($tenantId, $policyId); expect($result)->toBe([]); });