// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.Json; using Grpc.Core; using IntegrationTestsWebsite; using Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests.Infrastructure; using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal; using Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.Infrastructure; using Microsoft.AspNetCore.InternalTesting; using Xunit.Abstractions; namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests; public class UnaryTests : IntegrationTestBase { public UnaryTests(GrpcTestFixture fixture, ITestOutputHelper outputHelper) : base(fixture, outputHelper) { } [Fact] public async Task GetWithRouteParameter_MatchUrl_SuccessResult() { // Arrange Task UnaryMethod(HelloRequest request, ServerCallContext context) { return Task.FromResult(new HelloReply { Message = $"Hello {request.Name}!" }); } var method = Fixture.DynamicGrpc.AddUnaryMethod( UnaryMethod, Greeter.Descriptor.FindMethodByName("SayHello")); var client = new HttpClient(Fixture.Handler) { BaseAddress = new Uri("http://localhost") }; // Act var response = await client.GetAsync("/v1/greeter/test").DefaultTimeout(); var responseStream = await response.Content.ReadAsStreamAsync(); using var result = await JsonDocument.ParseAsync(responseStream); // Assert Assert.Equal("Hello test!", result.RootElement.GetProperty("message").GetString()); } [Fact] public async Task WriteResponseHeadersAsync_SendHeaders_HeadersSentBeforeResult() { // Arrange var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); async Task UnaryMethod(HelloRequest request, ServerCallContext context) { await context.WriteResponseHeadersAsync(new Metadata { new Metadata.Entry("test", "value!") }); await tcs.Task; return new HelloReply { Message = $"Hello {request.Name}!" }; } var method = Fixture.DynamicGrpc.AddUnaryMethod( UnaryMethod, Greeter.Descriptor.FindMethodByName("SayHello")); var client = new HttpClient(Fixture.Handler) { BaseAddress = new Uri("http://localhost") }; // Act var response = await client.GetAsync("/v1/greeter/test", HttpCompletionOption.ResponseHeadersRead).DefaultTimeout(); var responseStream = await response.Content.ReadAsStreamAsync(); var resultTask = JsonDocument.ParseAsync(responseStream); // Assert Assert.Equal("value!", response.Headers.GetValues("test").Single()); Assert.False(resultTask.IsCompleted); tcs.SetResult(); using var result = await resultTask.DefaultTimeout(); Assert.Equal("Hello test!", result.RootElement.GetProperty("message").GetString()); } [Fact] public async Task WriteResponseHeadersAsync_CallTwice_Error() { // Arrange var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); async Task UnaryMethod(HelloRequest request, ServerCallContext context) { await context.WriteResponseHeadersAsync(new Metadata { new Metadata.Entry("test", "value!") }); await tcs.Task; await context.WriteResponseHeadersAsync(new Metadata { new Metadata.Entry("test", "value 2!") }); return new HelloReply { Message = $"Hello {request.Name}!" }; } var method = Fixture.DynamicGrpc.AddUnaryMethod( UnaryMethod, Greeter.Descriptor.FindMethodByName("SayHello")); var client = new HttpClient(Fixture.Handler) { BaseAddress = new Uri("http://localhost") }; // Act var response = await client.GetAsync("/v1/greeter/test", HttpCompletionOption.ResponseHeadersRead).DefaultTimeout(); var responseStream = await response.Content.ReadAsStreamAsync(); var resultTask = JsonDocument.ParseAsync(responseStream); // Assert Assert.Equal("value!", response.Headers.GetValues("test").Single()); Assert.False(resultTask.IsCompleted); tcs.SetResult(); using var result = await resultTask.DefaultTimeout(); Assert.Equal("Exception was thrown by handler. InvalidOperationException: Response headers can only be sent once per call.", result.RootElement.GetProperty("message").GetString()); } [Fact] public async Task AuthContext_BasicRequest_Unauthenticated() { // Arrange AuthContext? authContext = null; Task UnaryMethod(HelloRequest request, ServerCallContext context) { authContext = context.AuthContext; return Task.FromResult(new HelloReply { Message = $"Hello {request.Name}!" }); } var method = Fixture.DynamicGrpc.AddUnaryMethod( UnaryMethod, Greeter.Descriptor.FindMethodByName("SayHello")); var client = new HttpClient(Fixture.Handler) { BaseAddress = new Uri("http://localhost") }; // Act var response = await client.GetAsync("/v1/greeter/test").DefaultTimeout(); var responseStream = await response.Content.ReadAsStreamAsync(); using var result = await JsonDocument.ParseAsync(responseStream); // Assert Assert.False(authContext!.IsPeerAuthenticated); Assert.Equal("Hello test!", result.RootElement.GetProperty("message").GetString()); } [Theory] [InlineData(null)] [InlineData("utf-8")] [InlineData("utf-16")] [InlineData("latin1")] public async Task Request_SupportedCharset_Success(string? charset) { // Arrange Task UnaryMethod(HelloRequest request, ServerCallContext context) { return Task.FromResult(new HelloReply { Message = $"Hello {request.Name}!" }); } var method = Fixture.DynamicGrpc.AddUnaryMethod( UnaryMethod, Greeter.Descriptor.FindMethodByName("SayHelloPost")); var encoding = JsonRequestHelpers.GetEncodingFromCharset(charset); var contentType = charset != null ? "application/json; charset=" + charset : "application/json"; var client = new HttpClient(Fixture.Handler) { BaseAddress = new Uri("http://localhost") }; var requestMessage = new HelloRequest { Name = "test" }; var content = new ByteArrayContent((encoding ?? Encoding.UTF8).GetBytes(requestMessage.ToString())); content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); // Act var response = await client.PostAsync("/v1/greeter", content).DefaultTimeout(); var responseText = await response.Content.ReadAsStringAsync(); using var result = JsonDocument.Parse(responseText); // Assert Assert.Equal("application/json", response.Content.Headers.ContentType!.MediaType); Assert.Equal(encoding?.WebName ?? "utf-8", response.Content.Headers.ContentType!.CharSet); Assert.Equal("Hello test!", result.RootElement.GetProperty("message").GetString()); } [Theory] [InlineData("FAKE", "InvalidOperationException: Unable to read the request as JSON because the request content type charset 'FAKE' is not a known encoding.")] [InlineData("UTF-7", "InvalidOperationException: Unable to read the request as JSON because the request content type charset 'UTF-7' is not a known encoding.")] public async Task Request_UnsupportedCharset_Error(string? charset, string errorMessage) { // Arrange Task UnaryMethod(HelloRequest request, ServerCallContext context) { return Task.FromResult(new HelloReply { Message = $"Hello {request.Name}!" }); } var method = Fixture.DynamicGrpc.AddUnaryMethod( UnaryMethod, Greeter.Descriptor.FindMethodByName("SayHelloPost")); var contentType = "application/json; charset=" + charset; var client = new HttpClient(Fixture.Handler) { BaseAddress = new Uri("http://localhost") }; var requestMessage = new HelloRequest { Name = "test" }; var content = new ByteArrayContent(Encoding.UTF8.GetBytes(requestMessage.ToString())); content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); // Act var response = await client.PostAsync("/v1/greeter", content).DefaultTimeout(); var responseText = await response.Content.ReadAsStringAsync(); using var result = JsonDocument.Parse(responseText); // Assert Assert.Equal("application/json", response.Content.Headers.ContentType!.MediaType); Assert.Equal("utf-8", response.Content.Headers.ContentType!.CharSet); Assert.Contains(errorMessage, result.RootElement.GetProperty("message").GetString()); } [Fact] public async Task Request_SendEnumString_Success() { // Arrange Task UnaryMethod(EnumHelloRequest request, ServerCallContext context) { return Task.FromResult(new HelloReply { Message = $"Hello {request.Name}!" }); } var method = Fixture.DynamicGrpc.AddUnaryMethod( UnaryMethod, Greeter.Descriptor.FindMethodByName("SayHelloPostEnum")); var client = new HttpClient(Fixture.Handler) { BaseAddress = new Uri("http://localhost") }; var requestMessage = new EnumHelloRequest { Name = NameOptions.Jane }; var content = new ByteArrayContent(Encoding.UTF8.GetBytes(requestMessage.ToString())); content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); // Act var response = await client.PostAsync("/v1/greeter_enum", content).DefaultTimeout(); var responseText = await response.Content.ReadAsStringAsync(); using var result = JsonDocument.Parse(responseText); // Assert Assert.Equal("application/json", response.Content.Headers.ContentType!.MediaType); Assert.Equal("utf-8", response.Content.Headers.ContentType!.CharSet); Assert.Equal("Hello Jane!", result.RootElement.GetProperty("message").GetString()); } }