create(); $connection = ProviderConnection::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'provider' => 'microsoft', 'is_default' => true, 'status' => 'connected', ]); ProviderCredential::factory()->create([ 'provider_connection_id' => (int) $connection->getKey(), ]); $graph = Mockery::mock(GraphClientInterface::class); $graph->shouldReceive('request') ->with( 'GET', '/deviceManagement/roleScopeTags', Mockery::on(function (array $options) use ($connection): bool { return ($options['query']['$select'] ?? null) === 'id,displayName' && ($options['tenant'] ?? null) === $connection->entra_tenant_id && is_string($options['client_id'] ?? null) && is_string($options['client_secret'] ?? null); }), ) ->once() ->andReturn(new GraphResponse( success: true, data: [ 'value' => [ ['id' => '0', 'displayName' => 'Default'], ['id' => '1', 'displayName' => 'Verbund-1'], ['id' => '2', 'displayName' => 'Verbund-2'], ], ], )); $gateway = new ProviderGateway($graph, new CredentialManager); $resolver = new ScopeTagResolver(app(ProviderConnectionResolver::class), $gateway); $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(); $connection = ProviderConnection::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'provider' => 'microsoft', 'is_default' => true, 'status' => 'connected', ]); ProviderCredential::factory()->create([ 'provider_connection_id' => (int) $connection->getKey(), ]); $graph = Mockery::mock(GraphClientInterface::class); $graph->shouldReceive('request') ->once() ->andReturn(new GraphResponse( success: true, data: [ 'value' => [ ['id' => '0', 'displayName' => 'Default'], ], ], )); $gateway = new ProviderGateway($graph, new CredentialManager); $resolver = new ScopeTagResolver(app(ProviderConnectionResolver::class), $gateway); $result1 = $resolver->resolve(['0'], $tenant); $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(); $graph = Mockery::mock(GraphClientInterface::class); $gateway = new ProviderGateway($graph, new CredentialManager); $resolver = new ScopeTagResolver(app(ProviderConnectionResolver::class), $gateway); $result = $resolver->resolve([], $tenant); expect($result)->toBe([]); }); test('handles 403 forbidden gracefully', function () { $tenant = Tenant::factory()->create(); $connection = ProviderConnection::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'provider' => 'microsoft', 'is_default' => true, 'status' => 'connected', ]); ProviderCredential::factory()->create([ 'provider_connection_id' => (int) $connection->getKey(), ]); $graph = Mockery::mock(GraphClientInterface::class); $graph->shouldReceive('request') ->once() ->andReturn(new GraphResponse( success: false, status: 403, data: [], )); $gateway = new ProviderGateway($graph, new CredentialManager); $resolver = new ScopeTagResolver(app(ProviderConnectionResolver::class), $gateway); $result = $resolver->resolve(['0', '1'], $tenant); expect($result)->toBe([]); }); test('filters returned scope tags to requested IDs', function () { $tenant = Tenant::factory()->create(); $connection = ProviderConnection::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'provider' => 'microsoft', 'is_default' => true, 'status' => 'connected', ]); ProviderCredential::factory()->create([ 'provider_connection_id' => (int) $connection->getKey(), ]); $graph = Mockery::mock(GraphClientInterface::class); $graph->shouldReceive('request') ->once() ->andReturn(new GraphResponse( success: true, data: [ 'value' => [ ['id' => '0', 'displayName' => 'Default'], ['id' => '1', 'displayName' => 'Verbund-1'], ['id' => '2', 'displayName' => 'Verbund-2'], ], ], )); $gateway = new ProviderGateway($graph, new CredentialManager); $resolver = new ScopeTagResolver(app(ProviderConnectionResolver::class), $gateway); $result = $resolver->resolve(['0', '2'], $tenant); expect($result)->toHaveCount(2); expect($result[0])->toBe(['id' => '0', 'displayName' => 'Default']); expect($result[2])->toBe(['id' => '2', 'displayName' => 'Verbund-2']); });