create(); $mockGraphClient = Mockery::mock(MicrosoftGraphClient::class); $mockGraphClient->shouldReceive('request') ->with('GET', '/deviceManagement/roleScopeTags', Mockery::on(function ($options) use ($tenant) { return $options['query']['$select'] === 'id,displayName' && $options['tenant'] === $tenant->external_id && $options['client_id'] === $tenant->app_client_id && $options['client_secret'] === $tenant->app_client_secret; })) ->once() ->andReturn(new GraphResponse( success: true, data: [ 'value' => [ ['id' => '0', 'displayName' => 'Default'], ['id' => '1', 'displayName' => 'Verbund-1'], ['id' => '2', 'displayName' => 'Verbund-2'], ], ] )); $mockLogger = Mockery::mock(GraphLogger::class); $resolver = new ScopeTagResolver($mockGraphClient, $mockLogger); $result = $resolver->resolve(['0', '1', '2'], $tenant); expect($result)->toBe([ ['id' => '0', 'displayName' => 'Default'], ['id' => '1', 'displayName' => 'Verbund-1'], ['id' => '2', 'displayName' => 'Verbund-2'], ]); }); test('caches scope tag objects for 1 hour', function () { $tenant = Tenant::factory()->create(); $mockGraphClient = Mockery::mock(MicrosoftGraphClient::class); $mockGraphClient->shouldReceive('request') ->once() // Only called once due to caching ->andReturn(new GraphResponse( success: true, data: [ 'value' => [ ['id' => '0', 'displayName' => 'Default'], ], ] )); $mockLogger = Mockery::mock(GraphLogger::class); $resolver = new ScopeTagResolver($mockGraphClient, $mockLogger); // First call - fetches from API $result1 = $resolver->resolve(['0'], $tenant); // Second call - should use cache $result2 = $resolver->resolve(['0'], $tenant); expect($result1)->toBe([['id' => '0', 'displayName' => 'Default']]); expect($result2)->toBe([['id' => '0', 'displayName' => 'Default']]); }); test('returns empty array for empty input', function () { $tenant = Tenant::factory()->create(); $mockGraphClient = Mockery::mock(MicrosoftGraphClient::class); $mockLogger = Mockery::mock(GraphLogger::class); $resolver = new ScopeTagResolver($mockGraphClient, $mockLogger); $result = $resolver->resolve([], $tenant); expect($result)->toBe([]); }); test('handles 403 forbidden gracefully', function () { $tenant = Tenant::factory()->create(); $mockGraphClient = Mockery::mock(MicrosoftGraphClient::class); $mockGraphClient->shouldReceive('request') ->once() ->andReturn(new GraphResponse( success: false, status: 403, data: [] )); $mockLogger = Mockery::mock(GraphLogger::class); $resolver = new ScopeTagResolver($mockGraphClient, $mockLogger); $result = $resolver->resolve(['0', '1'], $tenant); // Should return empty array when 403 expect($result)->toBe([]); }); test('filters returned scope tags to requested IDs', function () { $tenant = Tenant::factory()->create(); $mockGraphClient = Mockery::mock(MicrosoftGraphClient::class); $mockGraphClient->shouldReceive('request') ->once() ->andReturn(new GraphResponse( success: true, data: [ 'value' => [ ['id' => '0', 'displayName' => 'Default'], ['id' => '1', 'displayName' => 'Verbund-1'], ['id' => '2', 'displayName' => 'Verbund-2'], ], ] )); $mockLogger = Mockery::mock(GraphLogger::class); $resolver = new ScopeTagResolver($mockGraphClient, $mockLogger); // Request only IDs 0 and 2 $result = $resolver->resolve(['0', '2'], $tenant); expect($result)->toHaveCount(2); // Note: array_filter preserves keys, so result will be [0 => ..., 2 => ...] expect($result[0])->toBe(['id' => '0', 'displayName' => 'Default']); expect($result[2])->toBe(['id' => '2', 'displayName' => 'Verbund-2']); });