Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/Aspire.Hosting.RemoteHost/Ats/AtsMarshaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace Aspire.Hosting.RemoteHost.Ats;

Expand Down Expand Up @@ -49,7 +50,8 @@ internal sealed class UnmarshalContext
private static readonly JsonSerializerOptions s_jsonOptions = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Converters = { new JsonStringEnumConverter() }
};

/// <summary>
Expand Down
7 changes: 1 addition & 6 deletions src/Aspire.Hosting.RemoteHost/Ats/CapabilityDispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -533,11 +533,6 @@ private static bool IsTypeMismatchException(ArgumentException ex)
/// </summary>
internal static class CapabilityJsonExtensions
{
private static readonly JsonSerializerOptions s_jsonOptions = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

/// <summary>
/// Gets a required string argument.
Expand Down Expand Up @@ -615,7 +610,7 @@ public static T GetRequiredHandle<T>(
{
if (args.TryGetPropertyValue(name, out var node) && node is JsonObject obj)
{
return JsonSerializer.Deserialize<T>(obj.ToJsonString(), s_jsonOptions);
return JsonSerializer.Deserialize<T>(obj.ToJsonString(), AtsMarshaller.JsonOptions);
}
return null;
}
Expand Down
62 changes: 61 additions & 1 deletion tests/Aspire.Hosting.RemoteHost.Tests/AtsMarshallerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ private static AtsContext CreateTestContext()
{
Capabilities = [],
HandleTypes = [],
DtoTypes = [new AtsDtoTypeInfo { TypeId = "test/TestDto", Name = "TestDto", ClrType = typeof(TestDto), Properties = [] }],
DtoTypes = [
new AtsDtoTypeInfo { TypeId = "test/TestDto", Name = "TestDto", ClrType = typeof(TestDto), Properties = [] },
new AtsDtoTypeInfo { TypeId = "test/TestDtoWithEnum", Name = "TestDtoWithEnum", ClrType = typeof(TestDtoWithEnum), Properties = [] }
],
EnumTypes = []
};
}
Expand Down Expand Up @@ -560,6 +563,56 @@ public void MarshalToJson_MarshalsDto()
Assert.Equal(10, jsonObj["count"]?.GetValue<int>());
}

[Fact]
public void JsonOptions_ContainsJsonStringEnumConverter()
{
var options = AtsMarshaller.JsonOptions;

Assert.Contains(options.Converters, c => c is System.Text.Json.Serialization.JsonStringEnumConverter);
}

[Fact]
public void MarshalToJson_MarshalsDtoWithEnumPropertyAsString()
{
var marshaller = CreateMarshaller();
var dto = new TestDtoWithEnum { Label = "item", Status = TestEnum.ValueB };

var result = marshaller.MarshalToJson(dto);

Assert.NotNull(result);
Assert.IsType<JsonObject>(result);
var jsonObj = (JsonObject)result;
Assert.Equal("item", jsonObj["label"]?.GetValue<string>());
Assert.Equal("ValueB", jsonObj["status"]?.GetValue<string>());
}

[Fact]
public void UnmarshalFromJson_UnmarshalsDtoWithEnumPropertyFromString()
{
var (marshaller, context) = CreateMarshallerWithContext();
var json = new JsonObject { ["label"] = "item", ["status"] = "ValueA" };

var result = marshaller.UnmarshalFromJson(json, typeof(TestDtoWithEnum), context);

Assert.NotNull(result);
var dto = Assert.IsType<TestDtoWithEnum>(result);
Assert.Equal("item", dto.Label);
Assert.Equal(TestEnum.ValueA, dto.Status);
}

[Fact]
public void UnmarshalFromJson_UnmarshalsDtoWithEnumPropertyCaseInsensitive()
{
var (marshaller, context) = CreateMarshallerWithContext();
var json = new JsonObject { ["label"] = "test", ["status"] = "valueb" };

var result = marshaller.UnmarshalFromJson(json, typeof(TestDtoWithEnum), context);

Assert.NotNull(result);
var dto = Assert.IsType<TestDtoWithEnum>(result);
Assert.Equal(TestEnum.ValueB, dto.Status);
}

[Fact]
public void UnmarshalFromJson_UnmarshalsEnumFromString()
{
Expand Down Expand Up @@ -643,4 +696,11 @@ private sealed class TestDto
public string? Name { get; set; }
public int Count { get; set; }
}

[AspireDto]
private sealed class TestDtoWithEnum
{
public string? Label { get; set; }
public TestEnum Status { get; set; }
}
}
56 changes: 56 additions & 0 deletions tests/Aspire.Hosting.RemoteHost.Tests/CapabilityDispatcherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,53 @@ public void Invoke_AcceptOptionalEnum_WithoutValue()
Assert.Equal("No value", result.GetValue<string>());
}

[Fact]
public void GetDto_DeserializesEnumPropertyFromString()
{
var args = new JsonObject
{
["dto"] = new JsonObject
{
["label"] = "test-item",
["status"] = "ValueB"
}
};

var result = args.GetDto<TestDtoWithEnum>("dto");

Assert.NotNull(result);
Assert.Equal("test-item", result.Label);
Assert.Equal(TestDispatchEnum.ValueB, result.Status);
}

[Fact]
public void GetDto_DeserializesEnumPropertyFromStringCaseInsensitive()
{
var args = new JsonObject
{
["dto"] = new JsonObject
{
["label"] = "item",
["status"] = "valuec"
}
};

var result = args.GetDto<TestDtoWithEnum>("dto");

Assert.NotNull(result);
Assert.Equal(TestDispatchEnum.ValueC, result.Status);
}

[Fact]
public void GetDto_ReturnsNullWhenPropertyMissing()
{
var args = new JsonObject();

var result = args.GetDto<TestDtoWithEnum>("dto");

Assert.Null(result);
}

private static CapabilityDispatcher CreateDispatcher(params System.Reflection.Assembly[] assemblies)
{
var handles = new HandleRegistry();
Expand Down Expand Up @@ -1431,3 +1478,12 @@ public static string AcceptOptionalEnum(TestDispatchEnum? value = null)
return value.HasValue ? $"Received: {value.Value}" : "No value";
}
}

/// <summary>
/// Test DTO with an enum property for GetDto deserialization tests.
/// </summary>
internal sealed class TestDtoWithEnum
{
public string? Label { get; set; }
public TestDispatchEnum Status { get; set; }
}
Loading