public async Task TestAggregateHandlerDefaultRequestType()
        {
            var configWatcher = new MockConfigWatcher(true, new Dictionary <string, (string, IEnumerable <IMessage>)>());

            using var server = DiscoveryServerBuilder
                               .CreateFor(configWatcher)
                               .ConfigureDiscoveryService <AggregatedDiscoveryService>()
                               .Build();
            await server.StartAsync();

            var client = new AggregatedDiscoveryServiceClient(CreateGrpcChannel());

            var duplex     = client.StreamAggregatedResources();
            var clientTask = HandleResponses(duplex.ResponseStream);

            // Leave off the type URL. For ADS requests it should fail because the type URL is required.
            await duplex.RequestStream.WriteAsync(new DiscoveryRequest
            {
                Node = NODE,
            });

            await duplex.RequestStream.CompleteAsync();

            var(responseErrors, responses, completed, error) = await clientTask;
        }
        public async Task TestCallbacksOnError()
        {
            int streamClosesWithErrors = 0;
            var callbacks = Substitute.For <IDiscoveryServerCallbacks>();

            callbacks
            .When(x => x.OnStreamCloseWithError(Arg.Any <long>(), Arg.Any <string>(), Arg.Any <Exception>()))
            .Do(_ => Interlocked.Increment(ref streamClosesWithErrors));

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

            using var server = DiscoveryServerBuilder
                               .CreateFor(configWatcher, callbacks)
                               .ConfigureDiscoveryService <AggregatedDiscoveryService>()
                               .Build();

            await server.StartAsync();

            var client = new AggregatedDiscoveryServiceClient(CreateGrpcChannel());
            var ctx    = new CancellationTokenSource();
            var duplex = client.StreamAggregatedResources(cancellationToken: ctx.Token);
            await duplex.RequestStream.WriteAsync(new DiscoveryRequest());

            ctx.Cancel();
            WaitUntil(ref streamClosesWithErrors, 1, TimeSpan.FromSeconds(1));
            streamClosesWithErrors.Should().Be(1);
        }
        public async Task TestSendError()
        {
            var configWatcher = new MockConfigWatcher(false, CreateResponses());

            using var server = DiscoveryServerBuilder
                               .CreateFor(configWatcher)
                               .ConfigureDiscoveryService <AggregatedDiscoveryService>()
                               .Build();
            await server.StartAsync();

            var client = new AggregatedDiscoveryServiceClient(CreateGrpcChannel());

            foreach (var typeUrl in Resources.TYPE_URLS)
            {
                var duplex     = client.StreamAggregatedResources();
                var clientTask = HandleResponses(duplex.ResponseStream, sendError: true);


                await duplex.RequestStream.WriteAsync(new DiscoveryRequest
                {
                    Node    = NODE,
                    TypeUrl = typeUrl
                });

                var(responseErrors, responses, completed, error) = await clientTask;
                completed.Should().BeFalse();
                error.Should().BeTrue();
                responseErrors.Should().BeEmpty();
            }
        }
        public async Task TestWatchClosed()
        {
            var configWatcher = new MockConfigWatcher(true, new Dictionary <string, (string, IEnumerable <IMessage>)>());

            using var server = DiscoveryServerBuilder
                               .CreateFor(configWatcher)
                               .ConfigureDiscoveryService <AggregatedDiscoveryService>()
                               .Build();
            await server.StartAsync();

            var client = new AggregatedDiscoveryServiceClient(CreateGrpcChannel());

            foreach (var typeUrl in Resources.TYPE_URLS)
            {
                // MockDiscoveryResponseObserver responseObserver = new MockDiscoveryResponseObserver();
                var ctx        = new CancellationTokenSource();
                var duplex     = client.StreamAggregatedResources(cancellationToken: ctx.Token);
                var clientTask = HandleResponses(duplex.ResponseStream);

                await duplex.RequestStream.WriteAsync(new DiscoveryRequest
                {
                    Node    = NODE,
                    TypeUrl = typeUrl
                });

                ctx.Cancel();
                var(responseErrors, responses, completed, error) = await clientTask;
                error.Should().BeTrue();
                completed.Should().BeFalse();
                responseErrors.Should().BeEmpty();
            }
        }
        public async Task TestStaleNonce()
        {
            var configWatcher = new MockConfigWatcher(false, CreateResponses());

            using var server = DiscoveryServerBuilder
                               .CreateFor(configWatcher)
                               .ConfigureDiscoveryService <AggregatedDiscoveryService>()
                               .Build();
            await server.StartAsync();

            var client = new AggregatedDiscoveryServiceClient(CreateGrpcChannel());

            foreach (var typeUrl in Resources.TYPE_URLS)
            {
                var duplex     = client.StreamAggregatedResources();
                var clientTask = HandleResponses(duplex.ResponseStream);

                await duplex.RequestStream.WriteAsync(new DiscoveryRequest
                {
                    Node    = NODE,
                    TypeUrl = typeUrl,
                });

                // Stale request, should not create a new watch.
                await duplex.RequestStream.WriteAsync(new DiscoveryRequest
                {
                    Node          = NODE,
                    TypeUrl       = typeUrl,
                    ResponseNonce = "xyz"
                });

                // Fresh request, should create a new watch.
                await duplex.RequestStream.WriteAsync(new DiscoveryRequest
                {
                    Node          = NODE,
                    TypeUrl       = typeUrl,
                    ResponseNonce = "1",
                    VersionInfo   = "0"
                });

                await duplex.RequestStream.CompleteAsync();

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

                // Assert that 2 watches have been created for this resource type.
                configWatcher.counts[typeUrl].Should().Be(2);
            }
        }
        public async Task TestAggregatedHandler()
        {
            var configWatcher = new MockConfigWatcher(false, CreateResponses());

            using var server = DiscoveryServerBuilder
                               .CreateFor(configWatcher)
                               .ConfigureDiscoveryService <AggregatedDiscoveryService>()
                               .Build();

            await server.StartAsync();

            var client = new AggregatedDiscoveryServiceClient(CreateGrpcChannel());

            var duplex     = client.StreamAggregatedResources();
            var clientTask = Task.Run(async() => await HandleResponses(duplex.ResponseStream));

            await duplex.RequestStream.WriteAsync(new DiscoveryRequest
            {
                Node    = NODE,
                TypeUrl = Resources.LISTENER_TYPE_URL
            });

            await duplex.RequestStream.WriteAsync(new DiscoveryRequest
            {
                Node    = NODE,
                TypeUrl = Resources.CLUSTER_TYPE_URL,
            });

            var clusterRequest = new DiscoveryRequest
            {
                Node    = NODE,
                TypeUrl = Resources.ENDPOINT_TYPE_URL,
            };

            clusterRequest.ResourceNames.Add(CLUSTER_NAME);
            await duplex.RequestStream.WriteAsync(clusterRequest);

            var routeRequest = new DiscoveryRequest
            {
                Node    = NODE,
                TypeUrl = Resources.ROUTE_TYPE_URL,
            };

            routeRequest.ResourceNames.Add(ROUTE_NAME);
            await duplex.RequestStream.WriteAsync(routeRequest);

            var secretRequest = new DiscoveryRequest
            {
                Node    = NODE,
                TypeUrl = Resources.SECRET_TYPE_URL,
            };

            secretRequest.ResourceNames.Add(SECRET_NAME);
            await duplex.RequestStream.WriteAsync(secretRequest);

            await duplex.RequestStream.CompleteAsync();

            var(responseErrors, responses, completed, error) = await clientTask;
            completed.Should().BeTrue();
            error.Should().BeFalse();
            responseErrors.Should().BeEmpty();

            foreach (var typeUrl in Resources.TYPE_URLS)
            {
                configWatcher.counts.Should().Contain(typeUrl, 1);
            }

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

            foreach (var typeUrl in Resources.TYPE_URLS)
            {
                responses.Should().Contain(r => r.TypeUrl == typeUrl && r.VersionInfo == VERSION,
                                           "missing expected response of type %s", typeUrl);
            }
        }
        public async Task TestCallbacksAggregateHandler()
        {
            var assertionErrors = new List <string>();
            int streamCloses = 0, streamOpens = 0, streamRequests = 0, streamResponses = 0;
            var callbacks = Substitute.For <IDiscoveryServerCallbacks>();

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

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

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

            callbacks
            .When(x => x.OnStreamRequest(Arg.Any <long>(), Arg.Any <DiscoveryRequest>()))
            .Do(_ => Interlocked.Increment(ref streamRequests));

            callbacks
            .When(x => x.OnStreamResponse(Arg.Any <long>(), Arg.Any <DiscoveryRequest>(), Arg.Any <DiscoveryResponse>()))
            .Do(_ => Interlocked.Increment(ref streamResponses));


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

            using var server = DiscoveryServerBuilder
                               .CreateFor(configWatcher, callbacks)
                               .ConfigureDiscoveryService <AggregatedDiscoveryService>()
                               .Build();
            await server.StartAsync();

            var client     = new AggregatedDiscoveryServiceClient(CreateGrpcChannel());
            var duplex     = client.StreamAggregatedResources();
            var clientTask = HandleResponses(duplex.ResponseStream);

            await duplex.RequestStream.WriteAsync(new DiscoveryRequest
            {
                Node    = NODE,
                TypeUrl = Resources.LISTENER_TYPE_URL
            });

            WaitUntil(ref streamOpens, 1);
            streamOpens.Should().Be(1);

            await duplex.RequestStream.WriteAsync(new DiscoveryRequest
            {
                Node    = NODE,
                TypeUrl = Resources.CLUSTER_TYPE_URL
            });

            var cluster = new DiscoveryRequest
            {
                Node    = NODE,
                TypeUrl = Resources.ENDPOINT_TYPE_URL,
            };

            cluster.ResourceNames.Add(CLUSTER_NAME);

            await duplex.RequestStream.WriteAsync(cluster);

            var route = new DiscoveryRequest
            {
                Node    = NODE,
                TypeUrl = Resources.ROUTE_TYPE_URL
            };

            route.ResourceNames.Add(ROUTE_NAME);
            await duplex.RequestStream.WriteAsync(route);

            var secret = new DiscoveryRequest
            {
                Node    = NODE,
                TypeUrl = Resources.SECRET_TYPE_URL,
            };

            secret.ResourceNames.Add(SECRET_NAME);
            await duplex.RequestStream.WriteAsync(secret);


            WaitUntil(ref streamRequests, Resources.TYPE_URLS.Count());
            streamRequests.Should().Be(Resources.TYPE_URLS.Count());

            WaitUntil(ref streamRequests, Resources.TYPE_URLS.Count());
            streamResponses.Should().Be(Resources.TYPE_URLS.Count());

            // Send another round of requests. These should not trigger any responses.
            streamResponses = 0;
            streamRequests  = 0;

            await duplex.RequestStream.WriteAsync(new DiscoveryRequest
            {
                Node          = NODE,
                ResponseNonce = "0",
                VersionInfo   = VERSION,
                TypeUrl       = Resources.LISTENER_TYPE_URL
            });

            await duplex.RequestStream.WriteAsync(new DiscoveryRequest
            {
                Node          = NODE,
                ResponseNonce = "1",
                TypeUrl       = Resources.CLUSTER_TYPE_URL,
                VersionInfo   = VERSION,
            });

            var cluster2 = new DiscoveryRequest
            {
                Node          = NODE,
                ResponseNonce = "2",
                TypeUrl       = Resources.ENDPOINT_TYPE_URL,
                VersionInfo   = VERSION
            };

            cluster2.ResourceNames.Add(CLUSTER_NAME);
            await duplex.RequestStream.WriteAsync(cluster2);

            var route2 = new DiscoveryRequest
            {
                Node          = NODE,
                ResponseNonce = "3",
                TypeUrl       = Resources.ROUTE_TYPE_URL,
                VersionInfo   = VERSION
            };

            route2.ResourceNames.Add(ROUTE_NAME);
            await duplex.RequestStream.WriteAsync(route2);

            var secert2 = new DiscoveryRequest
            {
                Node          = NODE,
                ResponseNonce = "4",
                TypeUrl       = Resources.SECRET_TYPE_URL,
                VersionInfo   = VERSION,
            };

            secert2.ResourceNames.Add(SECRET_NAME);
            await duplex.RequestStream.WriteAsync(secert2);

            WaitUntil(ref streamRequests, Resources.TYPE_URLS.Count());
            streamRequests.Should().Be(Resources.TYPE_URLS.Count());
            streamResponses.Should().Be(0);

            await duplex.RequestStream.CompleteAsync();

            WaitUntil(ref streamCloses, 1);
            streamCloses.Should().Be(1);

            assertionErrors.Should().BeEmpty();
        }