Example #1
0
        public async Task AsyncUnaryCall_MissingStatus_ThrowError()
        {
            // Arrange
            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).DefaultTimeout();
                var response      = ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent, grpcStatusCode: null);
                return(response);
            });
            var invoker = HttpClientCallInvokerFactory.Create(httpClient);

            // Act
            var call = invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest());

            // Assert
            var ex = await ExceptionAssert.ThrowsAsync <RpcException>(() => call.ResponseAsync).DefaultTimeout();

            Assert.AreEqual("No grpc-status found on response.", ex.Status.Detail);
            Assert.AreEqual(StatusCode.Cancelled, ex.StatusCode);
            Assert.AreEqual(StatusCode.Cancelled, call.GetStatus().StatusCode);
        }
Example #2
0
        public async Task AsyncClientStreamingCall_CompleteAndWriteAfterResult_Error()
        {
            // Arrange
            var requestContent = new MemoryStream();

            var callCount  = 0;
            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                Interlocked.Increment(ref callCount);

                _ = request.Content !.ReadAsStreamAsync();

                var reply = new HelloReply {
                    Message = "Hello world"
                };
                var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();

                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });
            var serviceConfig = ServiceConfigHelpers.CreateHedgingServiceConfig();
            var invoker       = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig);

            // Act
            var call = invoker.AsyncClientStreamingCall <HelloRequest, HelloReply>(ClientTestHelpers.GetServiceMethod(MethodType.ClientStreaming), string.Empty, new CallOptions());

            // Assert
            var responseMessage = await call.ResponseAsync.DefaultTimeout();

            Assert.AreEqual("Hello world", responseMessage.Message);

            requestContent.Seek(0, SeekOrigin.Begin);

            await call.RequestStream.CompleteAsync().DefaultTimeout();

            var ex = await ExceptionAssert.ThrowsAsync <InvalidOperationException>(() => call.RequestStream.WriteAsync(new HelloRequest {
                Name = "1"
            })).DefaultTimeout();

            Assert.AreEqual("Request stream has already been completed.", ex.Message);
        }
Example #3
0
        public async Task AsyncUnaryCall_Success_HttpRequestMessagePopulated()
        {
            // Arrange
            HttpRequestMessage?httpRequestMessage = null;

            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                httpRequestMessage = request;

                HelloReply reply = new HelloReply
                {
                    Message = "Hello world"
                };

                var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();

                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });
            var invoker = HttpClientCallInvokerFactory.Create(httpClient);

            // Act
            var rs = await invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest());

            // Assert
            Assert.AreEqual("Hello world", rs.Message);

            Assert.IsNotNull(httpRequestMessage);
            Assert.AreEqual(new Version(2, 0), httpRequestMessage !.Version);
            Assert.AreEqual(HttpMethod.Post, httpRequestMessage.Method);
            Assert.AreEqual(new Uri("https://localhost/ServiceName/MethodName"), httpRequestMessage.RequestUri);
            Assert.AreEqual(new MediaTypeHeaderValue("application/grpc"), httpRequestMessage.Content.Headers.ContentType);
            Assert.AreEqual(GrpcProtocolConstants.TEHeader, httpRequestMessage.Headers.TE.Single());
            Assert.AreEqual("identity,gzip,deflate", httpRequestMessage.Headers.GetValues(GrpcProtocolConstants.MessageAcceptEncodingHeader).Single());

            var userAgent = httpRequestMessage.Headers.UserAgent.Single();

            Assert.AreEqual(GrpcProtocolConstants.UserAgentHeader, userAgent);
            Assert.AreEqual("grpc-dotnet", userAgent.Product.Name);
            Assert.IsTrue(!string.IsNullOrEmpty(userAgent.Product.Version));
        }
Example #4
0
        public async Task AsyncUnaryCall_Success_LogToFactory()
        {
            // Arrange
            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                // Trigger request stream serialization
                await request.Content !.ReadAsStreamAsync().DefaultTimeout();

                var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply
                {
                    Message = "Hello world"
                }).DefaultTimeout();

                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });

            var testSink      = new TestSink();
            var loggerFactory = new TestLoggerFactory(testSink, true);

            var invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory);

            // Act
            var rs = await invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest());

            // Assert
            Assert.AreEqual("Hello world", rs.Message);

            var logs = testSink.Writes.Where(w => w.LogLevel >= Microsoft.Extensions.Logging.LogLevel.Debug).ToList();

            Assert.AreEqual("Starting gRPC call. Method type: 'Unary', URI: 'https://localhost/ServiceName/MethodName'.", logs[0].State.ToString());
            AssertScope(logs[0]);

            Assert.AreEqual("Sending message.", logs[1].State.ToString());
            AssertScope(logs[1]);

            Assert.AreEqual("Reading message.", logs[2].State.ToString());
            AssertScope(logs[2]);

            Assert.AreEqual("Finished gRPC call.", logs[3].State.ToString());
            AssertScope(logs[3]);
Example #5
0
        public async Task AsyncUnaryCall_Success_RequestContentSent()
        {
            // Arrange
            HttpContent?content = null;

            var handler = TestHttpMessageHandler.Create(async request =>
            {
                content = request.Content;

                HelloReply reply = new HelloReply
                {
                    Message = "Hello world"
                };

                var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();

                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });
            var invoker = HttpClientCallInvokerFactory.Create(handler, "http://localhost");

            // Act
            var rs = await invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" });

            // Assert
            Assert.AreEqual("Hello world", rs.Message);

            Assert.IsNotNull(content);

            var requestContent = await content !.ReadAsStreamAsync().DefaultTimeout();
            var requestMessage = await StreamSerializationHelper.ReadMessageAsync(
                requestContent,
                ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer,
                GrpcProtocolConstants.IdentityGrpcEncoding,
                maximumMessageSize : null,
                GrpcProtocolConstants.DefaultCompressionProviders,
                singleMessage : true,
                CancellationToken.None).DefaultTimeout();

            Assert.AreEqual("World", requestMessage !.Name);
        }
Example #6
0
        public async Task AsyncUnaryCall_OneAttempt_Success(int maxAttempts)
        {
            // Arrange
            var tcs        = new TaskCompletionSource <object?>(TaskCreationOptions.RunContinuationsAsynchronously);
            var callCount  = 0;
            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                Interlocked.Increment(ref callCount);

                await tcs.Task;

                await request.Content !.CopyToAsync(new MemoryStream());

                var reply = new HelloReply {
                    Message = "Hello world"
                };
                var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();
                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });
            var serviceConfig = ServiceConfigHelpers.CreateHedgingServiceConfig(maxAttempts: maxAttempts);
            var invoker       = HttpClientCallInvokerFactory.Create(
                httpClient,
                serviceConfig: serviceConfig,
                configure: o => o.MaxRetryAttempts = maxAttempts);

            // Act
            var call = invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest {
                Name = "World"
            });

            // Assert
            await TestHelpers.AssertIsTrueRetryAsync(() => callCount == maxAttempts, "All calls made at once.");

            tcs.SetResult(null);

            var rs = await call.ResponseAsync.DefaultTimeout();

            Assert.AreEqual("Hello world", rs.Message);
            Assert.AreEqual(StatusCode.OK, call.GetStatus().StatusCode);
        }
Example #7
0
        public async Task AsyncUnaryCall_NoTrailers_WinHttpHandler_ThrowsError()
        {
            // Arrange
            var httpMessageHandler = TestHttpMessageHandler.Create(async request =>
            {
                var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).DefaultTimeout();
                var response = ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent);
                response.RequestMessage.Properties.Remove("__ResponseTrailers");
                response.Headers.Add("custom", "ABC");
                return response;
            });
            var invoker = HttpClientCallInvokerFactory.Create(new WinHttpHandler(httpMessageHandler), "https://localhost");

            // Act
            var call = invoker.AsyncUnaryCall<HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest());
            var ex = await ExceptionAssert.ThrowsAsync<RpcException>(() => call.ResponseAsync).DefaultTimeout();

            // Assert
            Assert.AreEqual(StatusCode.Cancelled, ex.StatusCode);
            Assert.AreEqual("No grpc-status found on response. Using gRPC with WinHttp has Windows and package version requirements. See https://aka.ms/aspnet/grpc/netstandard for details.", ex.Status.Detail);
            Assert.AreEqual(0, ex.Trailers.Count);
        }
Example #8
0
        public async Task AsyncUnaryCall_BadTrailersType_ThrowsError()
        {
            // Arrange
            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).DefaultTimeout();
                var response = ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent);
                response.RequestMessage.Properties["__ResponseTrailers"] = new object();
                response.Headers.Add("custom", "ABC");
                return response;
            });
            var invoker = HttpClientCallInvokerFactory.Create(httpClient);

            // Act
            var call = invoker.AsyncUnaryCall<HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest());
            var ex = await ExceptionAssert.ThrowsAsync<RpcException>(() => call.ResponseAsync).DefaultTimeout();

            // Assert
            Assert.AreEqual(StatusCode.Cancelled, ex.StatusCode);
            Assert.AreEqual("No grpc-status found on response.", ex.Status.Detail);
            Assert.AreEqual(0, ex.Trailers.Count);
        }
Example #9
0
        public async Task AsyncUnaryCall_PushbackExpicitDelay_DelayForSpecifiedDuration()
        {
            // Arrange
            Task?delayTask  = null;
            var  callCount  = 0;
            var  httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                callCount++;
                if (callCount == 1)
                {
                    await request.Content !.CopyToAsync(new MemoryStream());
                    delayTask = Task.Delay(100);
                    return(ResponseUtils.CreateHeadersOnlyResponse(HttpStatusCode.OK, StatusCode.Unavailable, retryPushbackHeader: "200"));
                }

                var reply = new HelloReply {
                    Message = "Hello world"
                };
                var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();
                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });
            var serviceConfig = ServiceConfigHelpers.CreateRetryServiceConfig(backoffMultiplier: 1);
            var invoker       = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig);

            // Act
            var call = invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest {
                Name = "World"
            });

            // Delay of 100ms will finish before second record which has a pushback delay of 200ms
            var completedTask = await Task.WhenAny(call.ResponseAsync, delayTask !).DefaultTimeout();

            var rs = await call.ResponseAsync.DefaultTimeout();

            // Assert
            Assert.AreEqual(delayTask, completedTask); // Response task should finish after
            Assert.AreEqual(2, callCount);
            Assert.AreEqual("Hello world", rs.Message);
        }
Example #10
0
        public async Task AsyncUnaryCall_Success_SuccussCommitLogged()
        {
            // Arrange
            var testSink = new TestSink();
            var services = new ServiceCollection();

            services.AddLogging(b =>
            {
                b.AddProvider(new TestLoggerProvider(testSink));
            });
            services.AddNUnitLogger();
            var provider = services.BuildServiceProvider();

            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                var content = request.Content !;
                await content.CopyToAsync(new MemoryStream());

                var reply = new HelloReply {
                    Message = "Hello world"
                };
                var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();

                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });
            var serviceConfig = ServiceConfigHelpers.CreateRetryServiceConfig();
            var invoker       = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory: provider.GetRequiredService <ILoggerFactory>(), serviceConfig: serviceConfig);

            // Act
            var call = invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.GetServiceMethod(MethodType.Unary), string.Empty, new CallOptions(), new HelloRequest {
                Name = "World"
            });
            await call.ResponseAsync.DefaultTimeout();

            // Assert
            var log = testSink.Writes.Single(w => w.EventId.Name == "CallCommited");

            Assert.AreEqual("Call commited. Reason: ResponseHeadersReceived", log.State.ToString());
        }
        public async Task AsyncUnaryCall_Success_ResponseHeadersPopulated()
        {
            // Arrange
            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                HelloReply reply = new HelloReply
                {
                    Message = "Hello world"
                };

                var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();
                var response      = ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent);
                response.Headers.Server.Add(new ProductInfoHeaderValue("TestName", "1.0"));
                response.Headers.Add("custom", "ABC");
                response.Headers.Add("binary-bin", Convert.ToBase64String(Encoding.UTF8.GetBytes("Hello world")));
                return(response);
            });
            var invoker = HttpClientCallInvokerFactory.Create(httpClient);

            // Act
            var call             = invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest());
            var responseHeaders1 = await call.ResponseHeadersAsync.DefaultTimeout();

            var responseHeaders2 = await call.ResponseHeadersAsync.DefaultTimeout();

            // Assert
            Assert.AreSame(responseHeaders1, responseHeaders2);

            var header = responseHeaders1.Single(h => h.Key == "server");

            Assert.AreEqual("TestName/1.0", header.Value);

            header = responseHeaders1.Single(h => h.Key == "custom");
            Assert.AreEqual("ABC", header.Value);

            header = responseHeaders1.Single(h => h.Key == "binary-bin");
            Assert.AreEqual(true, header.IsBinary);
            CollectionAssert.AreEqual(Encoding.UTF8.GetBytes("Hello world"), header.ValueBytes);
        }
Example #12
0
        public async Task AsyncUnaryCall_InvalidStatus_ThrowError()
        {
            // Arrange
            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).DefaultTimeout();
                var response      = ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent, grpcStatusCode: null);
                response.TrailingHeaders.Add(GrpcProtocolConstants.StatusTrailer, "value");
                return(response);
            });
            var invoker = HttpClientCallInvokerFactory.Create(httpClient);

            // Act
            var call = invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest());

            // Assert
            await ExceptionAssert.ThrowsAsync <InvalidOperationException>(() => call.ResponseAsync).DefaultTimeout();

            var ex = Assert.Throws <InvalidOperationException>(() => call.GetStatus());

            Assert.AreEqual("Unexpected grpc-status value: value", ex.Message);
        }
        public async Task CompositeCallCredentialsWithHttps_MetadataOnRequest()
        {
            // Arrange
            HttpRequestHeaders?requestHeaders = null;
            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                requestHeaders = request.Headers;

                var reply = new HelloReply {
                    Message = "Hello world"
                };
                var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();
                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });
            var invoker = HttpClientCallInvokerFactory.Create(httpClient);

            var first = CallCredentials.FromInterceptor(new AsyncAuthInterceptor((context, metadata) => {
                metadata.Add("first_authorization", "FIRST_SECRET_TOKEN");
                return(Task.CompletedTask);
            }));
            var second = CallCredentials.FromInterceptor(new AsyncAuthInterceptor((context, metadata) => {
                metadata.Add("second_authorization", "SECOND_SECRET_TOKEN");
                return(Task.CompletedTask);
            }));
            var third = CallCredentials.FromInterceptor(new AsyncAuthInterceptor((context, metadata) => {
                metadata.Add("third_authorization", "THIRD_SECRET_TOKEN");
                return(Task.CompletedTask);
            }));

            // Act
            var callCredentials = CallCredentials.Compose(first, second, third);
            var call            = invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(credentials: callCredentials), new HelloRequest());
            await call.ResponseAsync.DefaultTimeout();

            // Assert
            Assert.AreEqual("FIRST_SECRET_TOKEN", requestHeaders !.GetValues("first_authorization").Single());
            Assert.AreEqual("SECOND_SECRET_TOKEN", requestHeaders !.GetValues("second_authorization").Single());
            Assert.AreEqual("THIRD_SECRET_TOKEN", requestHeaders !.GetValues("third_authorization").Single());
        }
Example #14
0
        public async Task AsyncUnaryCall_HeadersReturned_ReturnsTrailers()
        {
            // Arrange
            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).DefaultTimeout();
                var response      = ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent);
                response.Headers.Add("custom", "ABC");
                response.TrailingHeaders.Add("custom-header", "value");
                return(response);
            });
            var invoker = HttpClientCallInvokerFactory.Create(httpClient);

            // Act
            var call            = invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest());
            var responseHeaders = await call.ResponseHeadersAsync.DefaultTimeout();

            var trailers = call.GetTrailers();

            // Assert
            Assert.AreEqual("value", trailers.Single(t => t.Key == "custom-header").Value);
        }
Example #15
0
        private async Task <HttpResponseMessage> HandleRequest(HttpRequestMessage request)
        {
            var requestStream = await request.Content !.ReadAsStreamAsync().DefaultTimeout();

            var helloRequest = await StreamSerializationHelper.ReadMessageAsync(
                requestStream,
                ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer,
                "gzip",
                maximumMessageSize : null,
                GrpcProtocolConstants.DefaultCompressionProviders,
                singleMessage : true,
                CancellationToken.None).DefaultTimeout();

            var reply = new HelloReply
            {
                Message = "Hello " + helloRequest !.Name
            };

            var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();

            return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
        }
        public void Intercept_InterceptorOrder_ExecutedInReversedOrder()
        {
            // Arrange
            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply {
                    Message = "PASS"
                }).DefaultTimeout();
                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });

            var stringBuilder = new StringBuilder();
            var invoker       = HttpClientCallInvokerFactory.Create(httpClient);

            // Act
            var callInvoker = invoker.Intercept(metadata =>
            {
                stringBuilder.Append("interceptor1");
                return(metadata);
            }).Intercept(new CallbackInterceptor(() => stringBuilder.Append("array1")),
                         new CallbackInterceptor(() => stringBuilder.Append("array2")),
                         new CallbackInterceptor(() => stringBuilder.Append("array3")))
                              .Intercept(metadata =>
            {
                stringBuilder.Append("interceptor2");
                return(metadata);
            }).Intercept(metadata =>
            {
                stringBuilder.Append("interceptor3");
                return(metadata);
            });

            var result = callInvoker.BlockingUnaryCall(ClientTestHelpers.ServiceMethod, Host, new CallOptions(), new HelloRequest());

            // Assert
            Assert.AreEqual("PASS", result.Message);
            Assert.AreEqual("interceptor3interceptor2array1array2array3interceptor1", stringBuilder.ToString());
        }
Example #17
0
        public async Task AsyncUnaryCall_SetSecondDeadline_RequestMessageContainsDeadlineHeader()
        {
            // Arrange
            HttpRequestMessage?httpRequestMessage = null;

            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                httpRequestMessage = request;

                var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).DefaultTimeout();
                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });
            var invoker = HttpClientCallInvokerFactory.Create(
                httpClient,
                systemClock: new TestSystemClock(new DateTime(2019, 11, 29, 1, 1, 1, DateTimeKind.Utc)));

            // Act
            await invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline : invoker.Channel.Clock.UtcNow.AddSeconds(1)), new HelloRequest());

            // Assert
            Assert.IsNotNull(httpRequestMessage);
            Assert.AreEqual("1S", httpRequestMessage !.Headers.GetValues(GrpcProtocolConstants.TimeoutHeader).Single());
        }
Example #18
0
        public async Task AsyncUnaryCall_MultipleStatusHeaders_ThrowError()
        {
            // Arrange
            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).DefaultTimeout();
                var response      = ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent, grpcStatusCode: StatusCode.Aborted);
                response.TrailingHeaders.Add(GrpcProtocolConstants.MessageTrailer, "one");
                response.TrailingHeaders.Add(GrpcProtocolConstants.MessageTrailer, "two");
                return(response);
            });
            var invoker = HttpClientCallInvokerFactory.Create(httpClient);

            // Act
            var call = invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest());

            // Assert
            var ex = await ExceptionAssert.ThrowsAsync <RpcException>(() => call.ResponseAsync).DefaultTimeout();

            Assert.AreEqual("Multiple grpc-message headers.", ex.Status.Detail);
            Assert.AreEqual(StatusCode.Cancelled, ex.StatusCode);
            Assert.AreEqual(StatusCode.Cancelled, call.GetStatus().StatusCode);
        }
Example #19
0
        public async Task Intercept_WrapClientStream_ClientStreamWrapperExecuted()
        {
            // Arrange
            var serviceMethod = new Method <string, string>(MethodType.Unary, "ServiceName", "Unary", Marshallers.StringMarshaller, Marshallers.StringMarshaller);

            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                var requestContent = await request.Content.ReadAsStreamAsync();
                await requestContent.CopyToAsync(new MemoryStream());

                var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply {
                    Message = "PASS"
                }).DefaultTimeout();
                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });

            var stringBuilder = new StringBuilder();
            var invoker       = HttpClientCallInvokerFactory.Create(httpClient);

            // Act
            var callInvoker = invoker.Intercept(new ClientStreamingCountingInterceptor());

            var call = callInvoker.AsyncClientStreamingCall(serviceMethod, Host, new CallOptions());
            await call.RequestStream.WriteAsync("A");

            await call.RequestStream.WriteAsync("B");

            await call.RequestStream.WriteAsync("C");

            await call.RequestStream.CompleteAsync();

            // Assert
            Assert.AreEqual("3", await call.ResponseAsync);

            Assert.AreEqual(StatusCode.OK, call.GetStatus().StatusCode);
            Assert.IsNotNull(call.GetTrailers());
        }
        public async Task AsyncServerStreamingCall_MessagesReturnedTogether_MessagesReceived()
        {
            // Arrange
            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                var streamContent = await ClientTestHelpers.CreateResponseContent(
                    new HelloReply
                {
                    Message = "Hello world 1"
                },
                    new HelloReply
                {
                    Message = "Hello world 2"
                }).DefaultTimeout();

                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });
            var invoker = HttpClientCallInvokerFactory.Create(httpClient);

            // Act
            var call = invoker.AsyncServerStreamingCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest());

            var responseStream = call.ResponseStream;

            // Assert
            Assert.IsNull(responseStream.Current);

            Assert.IsTrue(await responseStream.MoveNext(CancellationToken.None).DefaultTimeout());
            Assert.IsNotNull(responseStream.Current);
            Assert.AreEqual("Hello world 1", responseStream.Current.Message);

            Assert.IsTrue(await responseStream.MoveNext(CancellationToken.None).DefaultTimeout());
            Assert.IsNotNull(responseStream.Current);
            Assert.AreEqual("Hello world 2", responseStream.Current.Message);

            Assert.IsFalse(await responseStream.MoveNext(CancellationToken.None).DefaultTimeout());
        }
Example #21
0
        public async Task ResolverReturnsNoAddresses_DisposeWhileWaitForReady_Error()
        {
            // Arrange
            var testMessageHandler = TestHttpMessageHandler.Create(async request =>
            {
                var reply = new HelloReply {
                    Message = "Hello world"
                };
                var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();
                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });

            var services = new ServiceCollection();
            var resolver = new TestResolver();

            services.AddSingleton <ResolverFactory>(new TestResolverFactory(resolver));
            services.AddSingleton <ISubchannelTransportFactory>(new TestSubchannelTransportFactory());

            var invoker = HttpClientCallInvokerFactory.Create(testMessageHandler, "test:///localhost", configure: o =>
            {
                o.Credentials     = ChannelCredentials.Insecure;
                o.ServiceProvider = services.BuildServiceProvider();
            });

            // Act
            var callOptions = new CallOptions().WithWaitForReady();
            var call        = invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, callOptions, new HelloRequest());

            var exTask = ExceptionAssert.ThrowsAsync <RpcException>(() => call.ResponseAsync);

            call.Dispose();

            var ex = await exTask.DefaultTimeout();

            Assert.AreEqual(StatusCode.Cancelled, ex.StatusCode);
            Assert.AreEqual("gRPC call disposed.", ex.Status.Detail);
        }
Example #22
0
        public async Task AsyncUnaryCall_ErrorParsingTrailers_ThrowsError()
        {
            // Arrange
            var testSink      = new TestSink();
            var loggerFactory = new TestLoggerFactory(testSink, true);

            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).DefaultTimeout();
                var response      = ResponseUtils.CreateResponse(
                    HttpStatusCode.OK,
                    streamContent,
                    grpcStatusCode: StatusCode.Aborted,
                    customTrailers: new Dictionary <string, string>
                {
                    ["blah-bin"] = "!"
                });
                response.Headers.Add("custom", "ABC");
                return(response);
            });
            var invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory);

            // Act
            var call = invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest());
            var ex   = await ExceptionAssert.ThrowsAsync <RpcException>(() => call.ResponseAsync).DefaultTimeout();

            // Assert
            Assert.AreEqual(StatusCode.Aborted, ex.StatusCode);
            Assert.AreEqual(null, ex.Status.Detail);
            Assert.AreEqual(0, ex.Trailers.Count);

            var log = testSink.Writes.Single(w => w.EventId.Name == "ErrorParsingTrailers");

            Assert.AreEqual("Error parsing trailers.", log.State.ToString());
            Assert.AreEqual("Invalid Base-64 header value.", log.Exception.Message);
        }
Example #23
0
        public void InterceptMetadata_AddRequestHeader_HeaderInRequest()
        {
            // Arrange
            const string HeaderKey   = "x-client-interceptor";
            const string HeaderValue = "hello-world";

            string?requestHeaderValue = null;
            var    httpClient         = ClientTestHelpers.CreateTestClient(async request =>
            {
                if (request.Headers.TryGetValues(HeaderKey, out var values))
                {
                    requestHeaderValue = values.Single();
                }

                var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply {
                    Message = "PASS"
                }).DefaultTimeout();
                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });

            var invoker = HttpClientCallInvokerFactory.Create(httpClient);

            // Act
            var callInvoker = invoker.Intercept(metadata =>
            {
                metadata = metadata ?? new Metadata();
                metadata.Add(new Metadata.Entry(HeaderKey, HeaderValue));
                return(metadata);
            });

            var result = callInvoker.BlockingUnaryCall(ClientTestHelpers.ServiceMethod, Host, new CallOptions(), new HelloRequest());

            // Assert
            Assert.AreEqual("PASS", result.Message);
            Assert.AreEqual(HeaderValue, requestHeaderValue);
        }
        public async Task CallCredentialsWithHttp_NoMetadataOnRequest()
        {
            // Arrange
            bool?hasAuthorizationValue = null;
            var  httpClient            = ClientTestHelpers.CreateTestClient(async request =>
            {
                hasAuthorizationValue = request.Headers.TryGetValues("authorization", out _);

                var reply = new HelloReply {
                    Message = "Hello world"
                };
                var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();
                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            }, new Uri("http://localhost"));

            var testSink      = new TestSink();
            var loggerFactory = new TestLoggerFactory(testSink, true);

            var invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory);

            // Act
            var callCredentials = CallCredentials.FromInterceptor((context, metadata) =>
            {
                metadata.Add("authorization", "SECRET_TOKEN");
                return(Task.CompletedTask);
            });
            var call = invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(credentials: callCredentials), new HelloRequest());
            await call.ResponseAsync.DefaultTimeout();

            // Assert
            Assert.AreEqual(false, hasAuthorizationValue);

            var log = testSink.Writes.Single(w => w.EventId.Name == "CallCredentialsNotUsed");

            Assert.AreEqual("The configured CallCredentials were not used because the call does not use TLS.", log.State.ToString());
        }
Example #25
0
        public async Task AsyncClientStreamingCall_Success_RequestContentSent()
        {
            // Arrange
            PushStreamContent <HelloRequest, HelloReply>?content = null;

            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                content = (PushStreamContent <HelloRequest, HelloReply>)request.Content !;
                await content.PushComplete.DefaultTimeout();

                HelloReply reply = new HelloReply
                {
                    Message = "Hello world"
                };

                var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();

                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });
            var invoker = HttpClientCallInvokerFactory.Create(httpClient);

            // Act
            var call = invoker.AsyncClientStreamingCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions());

            // Assert
            Assert.IsNotNull(call);
            Assert.IsNotNull(content);

            var responseTask = call.ResponseAsync;

            Assert.IsFalse(responseTask.IsCompleted, "Response not returned until client stream is complete.");

            var streamTask = content !.ReadAsStreamAsync().DefaultTimeout();

            await call.RequestStream.WriteAsync(new HelloRequest { Name = "1" }).DefaultTimeout();

            await call.RequestStream.WriteAsync(new HelloRequest { Name = "2" }).DefaultTimeout();

            await call.RequestStream.CompleteAsync().DefaultTimeout();

            var requestContent = await streamTask.DefaultTimeout();

            var requestMessage = await requestContent.ReadMessageAsync(
                NullLogger.Instance,
                ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer,
                GrpcProtocolConstants.IdentityGrpcEncoding,
                maximumMessageSize : null,
                GrpcProtocolConstants.DefaultCompressionProviders,
                singleMessage : false,
                CancellationToken.None).AsTask().DefaultTimeout();

            Assert.AreEqual("1", requestMessage !.Name);
            requestMessage = await requestContent.ReadMessageAsync(
                NullLogger.Instance,
                ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer,
                GrpcProtocolConstants.IdentityGrpcEncoding,
                maximumMessageSize : null,
                GrpcProtocolConstants.DefaultCompressionProviders,
                singleMessage : false,
                CancellationToken.None).AsTask().DefaultTimeout();

            Assert.AreEqual("2", requestMessage !.Name);

            var responseMessage = await responseTask.DefaultTimeout();

            Assert.AreEqual("Hello world", responseMessage.Message);
        }
Example #26
0
        public async Task AsyncUnaryCall_CompressMetadataSentWithRequest_RequestMessageCompressed(bool compressionDisabledOnOptions)
        {
            // Arrange
            HttpRequestMessage?httpRequestMessage = null;
            HelloRequest?      helloRequest       = null;
            bool?isRequestNotCompressed           = null;

            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                httpRequestMessage = request;

                var requestData        = await request.Content.ReadAsByteArrayAsync();
                isRequestNotCompressed = requestData[0] == 0;

                helloRequest = await StreamExtensions.ReadSingleMessageAsync(
                    new MemoryStream(requestData),
                    NullLogger.Instance,
                    ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer,
                    "gzip",
                    maximumMessageSize: null,
                    GrpcProtocolConstants.DefaultCompressionProviders,
                    CancellationToken.None);

                HelloReply reply = new HelloReply
                {
                    Message = "Hello world"
                };

                var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();

                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });

            var compressionProviders = GrpcProtocolConstants.DefaultCompressionProviders.Values.ToList();

            compressionProviders.Add(new TestCompressionProvider());

            var invoker = HttpClientCallInvokerFactory.Create(httpClient, configure: o => o.CompressionProviders = compressionProviders);

            var compressionMetadata = CreateClientCompressionMetadata("gzip");
            var callOptions         = new CallOptions(headers: compressionMetadata);

            if (compressionDisabledOnOptions)
            {
                callOptions = callOptions.WithWriteOptions(new WriteOptions(WriteFlags.NoCompress));
            }

            // Act
            var call = invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, callOptions, new HelloRequest
            {
                Name = "Hello"
            });

            // Assert
            var response = await call.ResponseAsync;

            Assert.IsNotNull(response);
            Assert.AreEqual("Hello world", response.Message);

            Debug.Assert(httpRequestMessage != null);
            Assert.AreEqual("identity,gzip,deflate,test", httpRequestMessage.Headers.GetValues(GrpcProtocolConstants.MessageAcceptEncodingHeader).Single());
            Assert.AreEqual("gzip", httpRequestMessage.Headers.GetValues(GrpcProtocolConstants.MessageEncodingHeader).Single());
            Assert.AreEqual(false, httpRequestMessage.Headers.Contains(GrpcProtocolConstants.CompressionRequestAlgorithmHeader));

            Debug.Assert(helloRequest != null);
            Assert.AreEqual("Hello", helloRequest.Name);

            Assert.AreEqual(compressionDisabledOnOptions, isRequestNotCompressed);
        }
Example #27
0
        public async Task AsyncClientStreamingCall_Success_RequestContentSent()
        {
            // Arrange
            var requestContentTcs = new TaskCompletionSource <Task <Stream> >(TaskCreationOptions.RunContinuationsAsynchronously);

            PushStreamContent <HelloRequest, HelloReply>?content = null;

            var handler = TestHttpMessageHandler.Create(async request =>
            {
                content        = (PushStreamContent <HelloRequest, HelloReply>)request.Content !;
                var streamTask = content.ReadAsStreamAsync();
                requestContentTcs.SetResult(streamTask);

                // Wait for RequestStream.CompleteAsync()
                await streamTask;

                HelloReply reply = new HelloReply
                {
                    Message = "Hello world"
                };

                var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();

                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });
            var invoker = HttpClientCallInvokerFactory.Create(handler, "http://localhost");

            // Act
            var call = invoker.AsyncClientStreamingCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions());
            var requestContentTask = await requestContentTcs.Task.DefaultTimeout();

            // Assert
            Assert.IsNotNull(call);
            Assert.IsNotNull(content);

            var responseTask = call.ResponseAsync;

            Assert.IsFalse(responseTask.IsCompleted, "Response not returned until client stream is complete.");


            await call.RequestStream.WriteAsync(new HelloRequest { Name = "1" }).DefaultTimeout();

            await call.RequestStream.WriteAsync(new HelloRequest { Name = "2" }).DefaultTimeout();

            await call.RequestStream.CompleteAsync().DefaultTimeout();

            var requestContent = await requestContentTask.DefaultTimeout();

            var requestMessage = await StreamSerializationHelper.ReadMessageAsync(
                requestContent,
                ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer,
                GrpcProtocolConstants.IdentityGrpcEncoding,
                maximumMessageSize : null,
                GrpcProtocolConstants.DefaultCompressionProviders,
                singleMessage : false,
                CancellationToken.None).DefaultTimeout();

            Assert.AreEqual("1", requestMessage !.Name);
            requestMessage = await StreamSerializationHelper.ReadMessageAsync(
                requestContent,
                ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer,
                GrpcProtocolConstants.IdentityGrpcEncoding,
                maximumMessageSize : null,
                GrpcProtocolConstants.DefaultCompressionProviders,
                singleMessage : false,
                CancellationToken.None).DefaultTimeout();

            Assert.AreEqual("2", requestMessage !.Name);

            var responseMessage = await responseTask.DefaultTimeout();

            Assert.AreEqual("Hello world", responseMessage.Message);
        }
        public async Task PickAsync_HedgingWithDrop_ThrowsError()
        {
            // Arrange
            string?authority          = null;
            var    testMessageHandler = TestHttpMessageHandler.Create(async request =>
            {
                authority = request.RequestUri !.Authority;
                var reply = new HelloReply {
                    Message = "Hello world"
                };

                var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout();

                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });

            var services = new ServiceCollection();

            services.AddNUnitLogger();
            services.AddSingleton <TestResolver>();
            services.AddSingleton <ResolverFactory, TestResolverFactory>();
            DropLoadBalancer?loadBalancer = null;

            services.AddSingleton <LoadBalancerFactory>(new DropLoadBalancerFactory(c =>
            {
                loadBalancer = new DropLoadBalancer(c);
                return(loadBalancer);
            }));
            services.AddSingleton <ISubchannelTransportFactory>(new TestSubchannelTransportFactory());

            var invoker = HttpClientCallInvokerFactory.Create(testMessageHandler, "test:///localhost", configure: o =>
            {
                o.Credentials     = ChannelCredentials.Insecure;
                o.ServiceProvider = services.BuildServiceProvider();
                o.ServiceConfig   = new ServiceConfig
                {
                    MethodConfigs =
                    {
                        new MethodConfig
                        {
                            Names         = { MethodName.Default     },
                            HedgingPolicy = new HedgingPolicy
                            {
                                MaxAttempts         = 5,
                                HedgingDelay        = TimeSpan.FromMinutes(10),
                                NonFatalStatusCodes ={ StatusCode.DataLoss                    }
                            }
                        }
                    },
                    LoadBalancingConfigs = { new LoadBalancingConfig("drop") }
                };
            });

            // Act
            var call = invoker.AsyncUnaryCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions().WithWaitForReady(), new HelloRequest());

            // Assert
            var ex = await ExceptionAssert.ThrowsAsync <RpcException>(() => call.ResponseAsync).DefaultTimeout();

            Assert.AreEqual(StatusCode.DataLoss, ex.StatusCode);

            Assert.AreEqual(1, loadBalancer !.PickCount);
        }
Example #29
0
        public void DiagnosticListener_MakeCall_ActivityWritten()
        {
            // Arrange
            HttpRequestMessage? requestMessage  = null;
            HttpResponseMessage?responseMessage = null;
            var httpClient = ClientTestHelpers.CreateTestClient(async request =>
            {
                requestMessage = request;

                var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).DefaultTimeout();
                responseMessage   = ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent, grpcStatusCode: StatusCode.Aborted);
                responseMessage.TrailingHeaders().Add(GrpcProtocolConstants.MessageTrailer, "value");
                return(responseMessage);
            });
            var invoker = HttpClientCallInvokerFactory.Create(httpClient);

            var result = new List <KeyValuePair <string, object?> >();

            var dataMessageMarshaller = new Marshaller <DataMessage>(m => m.ToByteArray(), data => DataMessage.Parser.ParseFrom(data));
            var dataMessageMethod     = ClientTestHelpers.GetServiceMethod <DataMessage, DataMessage>(
                MethodType.DuplexStreaming,
                dataMessageMarshaller,
                dataMessageMarshaller);

            // Act
            HttpRequestMessage? requestMessage1  = null;
            HttpResponseMessage?responseMessage1 = null;
            HttpRequestMessage? requestMessage2  = null;
            HttpResponseMessage?responseMessage2 = null;

            using (GrpcDiagnostics.DiagnosticListener.Subscribe(new ObserverToList <KeyValuePair <string, object?> >(result)))
            {
                var c1 = invoker.AsyncDuplexStreamingCall <HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions());
                c1.Dispose();

                requestMessage1  = requestMessage;
                responseMessage1 = responseMessage;

                var c2 = invoker.AsyncDuplexStreamingCall <DataMessage, DataMessage>(dataMessageMethod, string.Empty, new CallOptions());
                c2.Dispose();

                requestMessage2  = requestMessage;
                responseMessage2 = responseMessage;
            }

            // Assert
            Assert.AreEqual(4, result.Count);

            // First call
            Assert.AreEqual(GrpcDiagnostics.ActivityStartKey, result[0].Key);
            Assert.AreEqual(requestMessage1, GetValueFromAnonymousType <HttpRequestMessage>(result[0].Value !, "Request"));
            Assert.AreEqual(GrpcDiagnostics.ActivityStopKey, result[1].Key);
            Assert.AreEqual(requestMessage1, GetValueFromAnonymousType <HttpRequestMessage>(result[1].Value !, "Request"));
            Assert.AreEqual(responseMessage1, GetValueFromAnonymousType <HttpResponseMessage>(result[1].Value !, "Response"));

            // Second call
            Assert.AreEqual(GrpcDiagnostics.ActivityStartKey, result[2].Key);
            Assert.AreEqual(requestMessage2, GetValueFromAnonymousType <HttpRequestMessage>(result[2].Value !, "Request"));
            Assert.AreEqual(GrpcDiagnostics.ActivityStopKey, result[3].Key);
            Assert.AreEqual(requestMessage2, GetValueFromAnonymousType <HttpRequestMessage>(result[3].Value !, "Request"));
            Assert.AreEqual(responseMessage2, GetValueFromAnonymousType <HttpResponseMessage>(result[3].Value !, "Response"));

            // Check types are expected
            Assert.AreEqual(typeof(GrpcCall.ActivityStartData), result[0].Value !.GetType());
            Assert.AreEqual(typeof(GrpcCall.ActivityStopData), result[1].Value !.GetType());
            Assert.AreEqual(result[0].Value !.GetType(), result[2].Value !.GetType());
            Assert.AreEqual(result[1].Value !.GetType(), result[3].Value !.GetType());

            // Check values are unique for each call
            Assert.AreNotEqual(result[0].Value, result[2].Value);
            Assert.AreNotEqual(result[1].Value, result[3].Value);
        }
        public async Task CreateClient_ServerCallContextAndUserCancellationToken_PropogatedDeadlineAndCancellation(Canceller canceller)
        {
            // Arrange
            var baseAddress = new Uri("http://localhost");
            var deadline    = DateTime.UtcNow.AddDays(1);
            var contextCts  = new CancellationTokenSource();
            var userCts     = new CancellationTokenSource();
            var tcs         = new TaskCompletionSource <object?>(TaskCreationOptions.RunContinuationsAsynchronously);

            CallOptions options = default;

            var handler = TestHttpMessageHandler.Create(async(r, token) =>
            {
                token.Register(() => tcs.SetCanceled());

                await tcs.Task;

                var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).DefaultTimeout();
                return(ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent));
            });

            var services = new ServiceCollection();

            services.AddOptions();
            services.AddSingleton(CreateHttpContextAccessorWithServerCallContext(deadline, contextCts.Token));
            services
            .AddGrpcClient <Greeter.GreeterClient>(o =>
            {
                o.Address = baseAddress;
            })
            .EnableCallContextPropagation()
            .AddInterceptor(() => new CallbackInterceptor(o => options = o))
            .ConfigurePrimaryHttpMessageHandler(() => handler);

            var serviceProvider = services.BuildServiceProvider(validateScopes: true);

            var clientFactory = CreateGrpcClientFactory(serviceProvider);
            var client        = clientFactory.CreateClient <Greeter.GreeterClient>(nameof(Greeter.GreeterClient));

            // Act
            using var call = client.SayHelloAsync(new HelloRequest(), cancellationToken: userCts.Token);
            var responseTask = call.ResponseAsync;

            // Assert
            Assert.AreEqual(deadline, options.Deadline);

            // CancellationToken passed to call is a linked cancellation token.
            // It's created from the context and user tokens.
            Assert.AreNotEqual(contextCts.Token, options.CancellationToken);
            Assert.AreNotEqual(userCts.Token, options.CancellationToken);
            Assert.AreNotEqual(CancellationToken.None, options.CancellationToken);

            Assert.IsFalse(responseTask.IsCompleted);

            // Either CTS should cancel call.
            switch (canceller)
            {
            case Canceller.Context:
                contextCts.Cancel();
                break;

            case Canceller.User:
                userCts.Cancel();
                break;
            }

            var ex = await ExceptionAssert.ThrowsAsync <RpcException>(() => responseTask).DefaultTimeout();

            Assert.AreEqual(StatusCode.Cancelled, ex.StatusCode);
            ex = await ExceptionAssert.ThrowsAsync <RpcException>(() => call.ResponseHeadersAsync).DefaultTimeout();

            Assert.AreEqual(StatusCode.Cancelled, ex.StatusCode);

            Assert.AreEqual(StatusCode.Cancelled, call.GetStatus().StatusCode);
            Assert.Throws <InvalidOperationException>(() => call.GetTrailers());
        }