Skip to content

Commit

Permalink
Fix deserialization error for parameter-less method
Browse files Browse the repository at this point in the history
  • Loading branch information
mayuki committed Nov 20, 2024
1 parent 81feccc commit e8cd4b9
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 2 deletions.
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
<PackageVersion Include="PolySharp" Version="1.13.2" />
<PackageVersion Include="StackExchange.Redis" Version="2.0.601" />
<PackageVersion Include="System.Text.Json" Version="9.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="3.3.4" />
</ItemGroup>
<!-- for tests -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ public void BindUnary<TRequest, TResponse, TRawRequest, TRawResponse>(IMagicOnio
var memStream = new MemoryStream();
await context.Request.BodyReader.CopyToAsync(memStream);

var request = grpcMethod.RequestMarshaller.ContextualDeserializer(new DeserializationContextImpl(memStream.ToArray()));
// If the request type is `Nil` (parameter-less method), we always ignore the request body.
TRawRequest request = (typeof(TRequest) == typeof(Nil))
? (TRawRequest)(object)Box.Create(Nil.Default)
: grpcMethod.RequestMarshaller.ContextualDeserializer(new DeserializationContextImpl(memStream.ToArray()));

var response = await unaryMethodHandler(handle.Instance, request, serverCallContext);

context.Response.ContentType = "application/json";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
builder.ConfigureServices(services =>
{
services.AddKeyedSingleton<ConcurrentDictionary<string, object>>(ItemsKey);
services.AddMagicOnion();
OnConfigureMagicOnionBuilder(services.AddMagicOnion());
});
builder.Configure(app =>
{
Expand All @@ -47,6 +47,8 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
});
}

protected virtual void OnConfigureMagicOnionBuilder(IMagicOnionServerBuilder builder){}

protected abstract IEnumerable<Type> GetServiceImplementationTypes();

public WebApplicationFactory<Program> WithMagicOnionOptions(Action<MagicOnionOptions> configure)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Microsoft.Extensions.DependencyInjection;

namespace MagicOnion.Server.JsonTranscoding.Tests;

public class JsonTranscodingEnabledMagicOnionApplicationFactory<T> : MagicOnionApplicationFactory<T>
{
protected override void OnConfigureMagicOnionBuilder(IMagicOnionServerBuilder builder)
{
builder.AddJsonTranscoding();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,16 @@
<ItemGroup>
<PackageReference Include="coverlet.collector" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="System.Text.Json" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\MagicOnion.Server.JsonTranscoding\MagicOnion.Server.JsonTranscoding.csproj" />
<ProjectReference Include="..\MagicOnion.Server.InternalTesting\MagicOnion.Server.InternalTesting.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
Expand Down
130 changes: 130 additions & 0 deletions tests/MagicOnion.Server.JsonTranscoding.Tests/UnaryFunctionalTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System.Collections.Concurrent;
using System.Net;
using System.Net.Http.Headers;
using System.Text.Json;
using MessagePack;
using Microsoft.Extensions.DependencyInjection;

namespace MagicOnion.Server.JsonTranscoding.Tests;

public class UnaryFunctionalTests(JsonTranscodingEnabledMagicOnionApplicationFactory<TestService> factory) : IClassFixture<JsonTranscodingEnabledMagicOnionApplicationFactory<TestService>>
{
[Fact]
public async Task NotImplemented()
{
// Arrange
var httpClient = factory.CreateDefaultClient();

// Act
var response = await httpClient.PostAsync($"http://localhost/_/ITestService/NotImplemented", new StringContent(string.Empty, new MediaTypeHeaderValue("application/json")));

// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}

[Fact]
public async Task Method_NoParameter_NoResult()
{
// Arrange
var httpClient = factory.CreateDefaultClient();

// Act
var response = await httpClient.PostAsync($"http://localhost/_/ITestService/Method_NoParameter_NoResult", new StringContent(string.Empty, new MediaTypeHeaderValue("application/json")));
var content = await response.Content.ReadAsStringAsync();

// Assert
var result = default(object); // null
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(result, JsonSerializer.Deserialize<object>(content));
Assert.Equal("application/json", response.Content.Headers.ContentType?.ToString());
Assert.True((bool)factory.Items.GetValueOrDefault($"{nameof(Method_NoParameter_NoResult)}.Called", false));
}

[Fact]
public async Task Method_NoParameter_ResultRefType()
{
// Arrange
var httpClient = factory.CreateDefaultClient();

// Act
var response = await httpClient.PostAsync($"http://localhost/_/ITestService/Method_NoParameter_ResultRefType", new StringContent(string.Empty, new MediaTypeHeaderValue("application/json")));
var content = await response.Content.ReadAsStringAsync();

// Assert
var result = nameof(Method_NoParameter_ResultRefType); // string
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(result, JsonSerializer.Deserialize<string>(content));
Assert.Equal("application/json", response.Content.Headers.ContentType?.ToString());
}

[Fact]
public async Task Method_NoParameter_ResultComplexType()
{
// Arrange
var httpClient = factory.CreateDefaultClient();

// Act
var response = await httpClient.PostAsync($"http://localhost/_/ITestService/Method_NoParameter_ResultComplexType", new StringContent(string.Empty, new MediaTypeHeaderValue("application/json")));
var content = await response.Content.ReadAsStringAsync();

// Assert
object[] result = [1234, "Alice", true, new object[] { 98765432100, "Hello!" }];
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(JsonSerializer.Serialize(result), JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(content)));
Assert.Equal("application/json", response.Content.Headers.ContentType?.ToString());
}
}

public interface ITestService : IService<ITestService>
{
UnaryResult Method_NoParameter_NoResult();
UnaryResult<string> Method_NoParameter_ResultRefType();
UnaryResult<TestResponse> Method_NoParameter_ResultComplexType();
}

[MessagePackObject]
public class TestResponse
{
[Key(0)]
public int A { get; set; }
[Key(1)]
public required string B { get; init; }
[Key(2)]
public bool C { get; set; }
[Key(3)]
public required InnerResponse Inner { get; init; }

[MessagePackObject]
public class InnerResponse
{
[Key(0)]
public long D { get; set; }
[Key(1)]
public required string E { get; init; }
}
}

public class TestService([FromKeyedServices(MagicOnionApplicationFactory.ItemsKey)] ConcurrentDictionary<string, object> items) : ServiceBase<ITestService>, ITestService
{
public UnaryResult Method_NoParameter_NoResult()
{
items[$"{nameof(Method_NoParameter_NoResult)}.Called"] = true;
return default;
}

public UnaryResult<string> Method_NoParameter_ResultRefType()
=> UnaryResult.FromResult(nameof(Method_NoParameter_ResultRefType));

public UnaryResult<TestResponse> Method_NoParameter_ResultComplexType()
=> UnaryResult.FromResult(new TestResponse()
{
A = 1234,
B = "Alice",
C = true,
Inner = new TestResponse.InnerResponse()
{
D = 98765432100,
E = "Hello!",
},
});
}
2 changes: 2 additions & 0 deletions tests/MagicOnion.Server.JsonTranscoding.Tests/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
global using Xunit;
global using MagicOnion.Server.InternalTesting;

0 comments on commit e8cd4b9

Please sign in to comment.