public async Task TestSeparateHandlersDefaultRequestType()
        {
            var configWatcher = new MockConfigWatcher(false, CreateResponses());

            using var server = DiscoveryServerBuilder
                               .CreateFor(configWatcher)
                               .ConfigureDiscoveryService <ClusterDiscoveryService>()
                               .ConfigureDiscoveryService <EndpointDiscoveryService>()
                               .ConfigureDiscoveryService <ListenerDiscoveryService>()
                               .ConfigureDiscoveryService <RouteDiscoveryService>()
                               .ConfigureDiscoveryService <SecretDiscoveryService>()
                               .Build();
            await server.StartAsync();

            var channel        = CreateGrpcChannel();
            var clusterClient  = new ClusterDiscoveryServiceClient(channel);
            var endpointClient = new EndpointDiscoveryServiceClient(channel);
            var listenerClient = new ListenerDiscoveryServiceClient(channel);
            var routeClient    = new RouteDiscoveryServiceClient(channel);
            var secretClient   = new SecretDiscoveryServiceClient(channel);

            foreach (var typeUrl in Resources.TYPE_URLS)
            {
                Task <(List <string>, List <DiscoveryResponse>, bool, bool)>   clientTask = null;
                AsyncDuplexStreamingCall <DiscoveryRequest, DiscoveryResponse> duplex     = null;
                switch (typeUrl)
                {
                case Resources.CLUSTER_TYPE_URL:
                    duplex     = clusterClient.StreamClusters();
                    clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream));
                    break;

                case Resources.ENDPOINT_TYPE_URL:
                    duplex     = endpointClient.StreamEndpoints();
                    clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream));
                    break;

                case Resources.LISTENER_TYPE_URL:
                    duplex     = listenerClient.StreamListeners();
                    clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream));
                    break;

                case Resources.ROUTE_TYPE_URL:
                    duplex     = routeClient.StreamRoutes();
                    clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream));
                    break;

                case Resources.SECRET_TYPE_URL:
                    duplex     = secretClient.StreamSecrets();
                    clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream));
                    break;

                default:
                    Assert.True(false, "Unsupported resource type: " + typeUrl);
                    break;
                }

                // Leave off the type URL. For xDS requests it should default to the value for that handler's type.
                var discoveryRequest = new DiscoveryRequest
                {
                    Node = NODE,
                };

                await duplex.RequestStream.WriteAsync(discoveryRequest);

                await duplex.RequestStream.CompleteAsync();

                var(responseErrors, _, _, _) = await clientTask;

                responseErrors.Should().BeEmpty();
            }
        }
        public async Task TestCallbacksSeparateHandlers()
        {
            var streamCloses    = new ConcurrentDictionary <string, StrongBox <int> >();
            var streamOpens     = new ConcurrentDictionary <string, StrongBox <int> >();
            var streamRequests  = new ConcurrentDictionary <string, StrongBox <int> >();
            var streamResponses = new ConcurrentDictionary <string, StrongBox <int> >();

            Resources.TYPE_URLS.ForEach(typeUrl =>
            {
                streamCloses[typeUrl]    = new StrongBox <int>(0);
                streamOpens[typeUrl]     = new StrongBox <int>(0);
                streamRequests[typeUrl]  = new StrongBox <int>(0);
                streamResponses[typeUrl] = new StrongBox <int>(0);
            });

            var assertionErrors = new List <string>();
            var callbacks       = Substitute.For <IDiscoveryServerCallbacks>();

            void ValidateTypeUrl(string typeUrl, string caller)
            {
                if (!Resources.TYPE_URLS.Contains(typeUrl))
                {
                    assertionErrors.Add($"{caller}#typeUrl => expected {Resources.ANY_TYPE_URL}, got {typeUrl}");
                }
            }

            callbacks
            .When(x => x.OnStreamClose(Arg.Any <long>(), Arg.Any <string>()))
            .Do(args =>
            {
                var typeUrl = args.ArgAt <string>(1);
                ValidateTypeUrl(typeUrl, "OnStreamClose");
                Interlocked.Increment(ref streamCloses[typeUrl].Value);
            });

            callbacks
            .When(x => x.OnStreamOpen(Arg.Any <long>(), Arg.Any <string>()))
            .Do(args =>
            {
                var typeUrl = args.ArgAt <string>(1);
                ValidateTypeUrl(typeUrl, "OnStreamOpen");
                Interlocked.Increment(ref streamOpens[typeUrl].Value);
            });

            callbacks
            .When(x => x.OnStreamRequest(Arg.Any <long>(), Arg.Any <DiscoveryRequest>()))
            .Do(args => Interlocked.Increment(ref streamRequests[args.ArgAt <DiscoveryRequest>(1).TypeUrl].Value));

            callbacks
            .When(x => x.OnStreamResponse(Arg.Any <long>(), Arg.Any <DiscoveryRequest>(), Arg.Any <DiscoveryResponse>()))
            .Do(args => Interlocked.Increment(ref streamResponses[args.ArgAt <DiscoveryRequest>(1).TypeUrl].Value));

            var configWatcher = new MockConfigWatcher(false, CreateResponses());

            using var server = DiscoveryServerBuilder
                               .CreateFor(configWatcher, callbacks)
                               .ConfigureDiscoveryService <ClusterDiscoveryService>()
                               .ConfigureDiscoveryService <EndpointDiscoveryService>()
                               .ConfigureDiscoveryService <ListenerDiscoveryService>()
                               .ConfigureDiscoveryService <RouteDiscoveryService>()
                               .ConfigureDiscoveryService <SecretDiscoveryService>()
                               .Build();
            await server.StartAsync();

            var channel        = CreateGrpcChannel();
            var clusterClient  = new ClusterDiscoveryServiceClient(channel);
            var endpointClient = new EndpointDiscoveryServiceClient(channel);
            var listenerClient = new ListenerDiscoveryServiceClient(channel);
            var routeClient    = new RouteDiscoveryServiceClient(channel);
            var secretClient   = new SecretDiscoveryServiceClient(channel);

            foreach (var typeUrl in Resources.TYPE_URLS)
            {
                Task <(List <string>, List <DiscoveryResponse>, bool, bool)>   clientTask = null;
                AsyncDuplexStreamingCall <DiscoveryRequest, DiscoveryResponse> duplex     = null;
                switch (typeUrl)
                {
                case Resources.CLUSTER_TYPE_URL:
                    duplex     = clusterClient.StreamClusters();
                    clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream));
                    break;

                case Resources.ENDPOINT_TYPE_URL:
                    duplex     = endpointClient.StreamEndpoints();
                    clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream));
                    break;

                case Resources.LISTENER_TYPE_URL:
                    duplex     = listenerClient.StreamListeners();
                    clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream));
                    break;

                case Resources.ROUTE_TYPE_URL:
                    duplex     = routeClient.StreamRoutes();
                    clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream));
                    break;

                case Resources.SECRET_TYPE_URL:
                    duplex     = secretClient.StreamSecrets();
                    clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream));
                    break;

                default:
                    Assert.True(false, "Unsupported resource type: " + typeUrl);
                    break;
                }

                var discoveryRequest = new DiscoveryRequest
                {
                    Node    = NODE,
                    TypeUrl = typeUrl
                };

                await duplex.RequestStream.WriteAsync(discoveryRequest);

                WaitUntil(ref streamOpens[typeUrl].Value, 1);
                streamOpens[typeUrl].Value.Should().Be(1);

                WaitUntil(ref streamRequests[typeUrl].Value, 1);
                streamRequests[typeUrl].Value.Should().Be(1);

                await duplex.RequestStream.CompleteAsync();

                WaitUntil(ref streamResponses[typeUrl].Value, 1);
                streamResponses[typeUrl].Value.Should().Be(1);

                WaitUntil(ref streamCloses[typeUrl].Value, 1);
                streamCloses[typeUrl].Value.Should().Be(1);
            }

            assertionErrors.Should().BeEmpty();
        }
        public async Task TestSeparateHandlers()
        {
            var configWatcher = new MockConfigWatcher(false, CreateResponses());

            using var server = DiscoveryServerBuilder
                               .CreateFor(configWatcher)
                               .ConfigureDiscoveryService <ClusterDiscoveryService>()
                               .ConfigureDiscoveryService <EndpointDiscoveryService>()
                               .ConfigureDiscoveryService <ListenerDiscoveryService>()
                               .ConfigureDiscoveryService <RouteDiscoveryService>()
                               .ConfigureDiscoveryService <SecretDiscoveryService>()
                               .Build();
            await server.StartAsync();

            var channel        = CreateGrpcChannel();
            var clusterClient  = new ClusterDiscoveryServiceClient(channel);
            var endpointClient = new EndpointDiscoveryServiceClient(channel);
            var listenerClient = new ListenerDiscoveryServiceClient(channel);
            var routeClient    = new RouteDiscoveryServiceClient(channel);
            var secretClient   = new SecretDiscoveryServiceClient(channel);

            foreach (var typeUrl in Resources.TYPE_URLS)
            {
                var discoveryRequest = new DiscoveryRequest
                {
                    Node    = NODE,
                    TypeUrl = typeUrl
                };
                Task <(List <string>, List <DiscoveryResponse>, bool, bool)>   clientTask = null;
                AsyncDuplexStreamingCall <DiscoveryRequest, DiscoveryResponse> duplex     = null;
                switch (typeUrl)
                {
                case Resources.CLUSTER_TYPE_URL:
                    duplex     = clusterClient.StreamClusters();
                    clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream));
                    break;

                case Resources.ENDPOINT_TYPE_URL:
                    duplex     = endpointClient.StreamEndpoints();
                    clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream));
                    discoveryRequest.ResourceNames.Add(CLUSTER_NAME);
                    break;

                case Resources.LISTENER_TYPE_URL:
                    duplex     = listenerClient.StreamListeners();
                    clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream));
                    break;

                case Resources.ROUTE_TYPE_URL:
                    duplex     = routeClient.StreamRoutes();
                    clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream));
                    discoveryRequest.ResourceNames.Add(ROUTE_NAME);
                    break;

                case Resources.SECRET_TYPE_URL:
                    duplex     = secretClient.StreamSecrets();
                    clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream));
                    discoveryRequest.ResourceNames.Add(SECRET_NAME);
                    break;

                default:
                    Assert.True(false, "Unsupported resource type: " + typeUrl);
                    break;
                }

                await duplex.RequestStream.WriteAsync(discoveryRequest);

                await duplex.RequestStream.CompleteAsync();

                var(responseErrors, responses, completed, error) = await clientTask;

                completed.Should().BeTrue();
                error.Should().BeFalse();
                responseErrors.Should().BeEmpty();

                configWatcher.counts.Should().Contain(typeUrl, 1);
                responses.Should().Contain(
                    r => r.TypeUrl == typeUrl && r.VersionInfo == VERSION,
                    "missing expected response of type %s", typeUrl);
            }

            configWatcher.counts.Should().HaveCount(Resources.TYPE_URLS.Count());
        }