diff --git a/.autover/changes/56a61b2c-4302-4077-9dc6-3fc3bc60a998.json b/.autover/changes/56a61b2c-4302-4077-9dc6-3fc3bc60a998.json new file mode 100644 index 00000000..4dba22cc --- /dev/null +++ b/.autover/changes/56a61b2c-4302-4077-9dc6-3fc3bc60a998.json @@ -0,0 +1,11 @@ +{ + "Projects": [ + { + "Name": "AWS.Messaging", + "Type": "Minor", + "ChangelogMessages": [ + "Update MessageSerializer to store data as actual json. Fixes #168" + ] + } + ] +} \ No newline at end of file diff --git a/src/AWS.Messaging/MessageEnvelope.cs b/src/AWS.Messaging/MessageEnvelope.cs index 557acc7f..25a2db79 100644 --- a/src/AWS.Messaging/MessageEnvelope.cs +++ b/src/AWS.Messaging/MessageEnvelope.cs @@ -44,6 +44,12 @@ public abstract class MessageEnvelope [JsonPropertyName("time")] public DateTimeOffset TimeStamp { get; set; } = DateTimeOffset.MinValue; + /// + /// The data content type. + /// + [JsonPropertyName("datacontenttype")] + public string? DataContentType { get; set; } + /// /// This stores different metadata that is not modeled as a top-level property in MessageEnvelope class. /// These entries will also be serialized as top-level properties when sending the message, which diff --git a/src/AWS.Messaging/Serialization/EnvelopeSerializer.cs b/src/AWS.Messaging/Serialization/EnvelopeSerializer.cs index 29749f43..d2fcc84d 100644 --- a/src/AWS.Messaging/Serialization/EnvelopeSerializer.cs +++ b/src/AWS.Messaging/Serialization/EnvelopeSerializer.cs @@ -1,13 +1,12 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using Amazon.SQS.Model; using AWS.Messaging.Configuration; -using AWS.Messaging.Internal; using AWS.Messaging.Serialization.Helpers; +using AWS.Messaging.Internal; using AWS.Messaging.Services; using Microsoft.Extensions.Logging; using AWS.Messaging.Serialization.Parsers; @@ -103,10 +102,23 @@ public async ValueTask SerializeAsync(MessageEnvelope envelope) ["source"] = envelope.Source?.ToString(), ["specversion"] = envelope.Version, ["type"] = envelope.MessageTypeIdentifier, - ["time"] = envelope.TimeStamp, - ["data"] = _messageSerializer.Serialize(message) + ["time"] = envelope.TimeStamp }; + var messageSerializerResults = _messageSerializer.Serialize(message); + + blob["datacontenttype"] = messageSerializerResults.ContentType; + + if (IsJsonContentType(messageSerializerResults.ContentType)) + { + blob["data"] = JsonNode.Parse(messageSerializerResults.Data); + } + else + { + blob["data"] = messageSerializerResults.Data; + + } + // Write any Metadata as top-level keys // This may be useful for any extensions defined in // https://github.com/cloudevents/spec/tree/main/cloudevents/extensions @@ -174,6 +186,34 @@ public async ValueTask ConvertToEnvelopeAsync(Message s } } + private bool IsJsonContentType(string? dataContentType) + { + if (string.IsNullOrWhiteSpace(dataContentType)) + { + // If dataContentType is not specified, it should be treated as "application/json" + return true; + } + + // Remove any parameters from the content type + var mediaType = dataContentType.Split(';')[0].Trim().ToLower(); + + // Check if the media type is "application/json" + if (mediaType == "application/json") + { + return true; + } + + // Check if the media subtype is "json" or ends with "+json" + var parts = mediaType.Split('/'); + if (parts.Length == 2) + { + var subtype = parts[1]; + return subtype == "json" || subtype.EndsWith("+json"); + } + + return false; + } + private (MessageEnvelope Envelope, SubscriberMapping Mapping) DeserializeEnvelope(string envelopeString) { using var document = JsonDocument.Parse(envelopeString); @@ -204,6 +244,7 @@ public async ValueTask ConvertToEnvelopeAsync(Message s envelope.Version = JsonPropertyHelper.GetRequiredProperty(root, "specversion", element => element.GetString()!); envelope.MessageTypeIdentifier = JsonPropertyHelper.GetRequiredProperty(root, "type", element => element.GetString()!); envelope.TimeStamp = JsonPropertyHelper.GetRequiredProperty(root, "time", element => element.GetDateTimeOffset()); + envelope.DataContentType = JsonPropertyHelper.GetStringProperty(root, "datacontenttype"); // Handle metadata - copy any properties that aren't standard envelope properties foreach (var property in root.EnumerateObject()) @@ -215,7 +256,10 @@ public async ValueTask ConvertToEnvelopeAsync(Message s } // Deserialize the message content using the custom serializer - var dataContent = JsonPropertyHelper.GetRequiredProperty(root, "data", element => element.GetString()!); + var dataContent = JsonPropertyHelper.GetRequiredProperty(root, "data", element => + IsJsonContentType(envelope.DataContentType) + ? element.GetRawText() + : element.GetString()!); var message = _messageSerializer.Deserialize(dataContent, subscriberMapping.MessageType); envelope.SetMessage(message); diff --git a/src/AWS.Messaging/Serialization/IMessageSerializer.cs b/src/AWS.Messaging/Serialization/IMessageSerializer.cs index 93f870b6..11e56bae 100644 --- a/src/AWS.Messaging/Serialization/IMessageSerializer.cs +++ b/src/AWS.Messaging/Serialization/IMessageSerializer.cs @@ -9,10 +9,11 @@ namespace AWS.Messaging.Serialization; public interface IMessageSerializer { /// - /// Serializes the .NET message object into a string. + /// Serializes the .NET message object into a string and specifies the content type of the serialized data. /// /// The .NET object that will be serialized. - string Serialize(object message); + /// A containing the serialized string and its content type. + MessageSerializerResults Serialize(object message); /// /// Deserializes the raw string message into the .NET type. diff --git a/src/AWS.Messaging/Serialization/MessageSerializer.cs b/src/AWS.Messaging/Serialization/MessageSerializer.cs index 6084e1d9..085cfce6 100644 --- a/src/AWS.Messaging/Serialization/MessageSerializer.cs +++ b/src/AWS.Messaging/Serialization/MessageSerializer.cs @@ -73,7 +73,7 @@ public object Deserialize(string message, Type deserializedType) Justification = "Consumers relying on trimming would have been required to call the AddAWSMessageBus overload that takes in JsonSerializerContext that will be used here to avoid the call that requires unreferenced code.")] [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("ReflectionAnalysis", "IL3050", Justification = "Consumers relying on trimming would have been required to call the AddAWSMessageBus overload that takes in JsonSerializerContext that will be used here to avoid the call that requires unreferenced code.")] - public string Serialize(object message) + public MessageSerializerResults Serialize(object message) { try { @@ -100,7 +100,7 @@ public string Serialize(object message) _logger.LogTrace("Serialized the message object to a raw string with a content length of {ContentLength}.", jsonString.Length); } - return jsonString; + return new MessageSerializerResults(jsonString, "application/json"); } catch (JsonException) when (!_messageConfiguration.LogMessageContent) { diff --git a/src/AWS.Messaging/Serialization/MessageSerializerResults.cs b/src/AWS.Messaging/Serialization/MessageSerializerResults.cs new file mode 100644 index 00000000..510fb215 --- /dev/null +++ b/src/AWS.Messaging/Serialization/MessageSerializerResults.cs @@ -0,0 +1,34 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace AWS.Messaging.Serialization; + +/// +/// Represents the results of a message serialization operation, containing both the serialized data +/// and its corresponding content type. +/// +public class MessageSerializerResults +{ + /// + /// Initializes a new instance of the MessageSerializerResults class. + /// + /// The serialized message data as a string. + /// The MIME content type of the serialized data. + public MessageSerializerResults(string data, string contentType) + { + Data = data; + ContentType = contentType; + } + + /// + /// Gets or sets the MIME content type of the serialized data. + /// Common values include "application/json" or "application/xml". + /// + public string ContentType { get; } + + /// + /// Gets or sets the serialized message data as a string. + /// This contains the actual serialized content of the message. + /// + public string Data { get; } +} diff --git a/test/AWS.Messaging.IntegrationTests/EventBridgePublisherTests.cs b/test/AWS.Messaging.IntegrationTests/EventBridgePublisherTests.cs index 3e9f9196..ae625b50 100644 --- a/test/AWS.Messaging.IntegrationTests/EventBridgePublisherTests.cs +++ b/test/AWS.Messaging.IntegrationTests/EventBridgePublisherTests.cs @@ -12,6 +12,8 @@ using Amazon.SQS.Model; using Amazon.SecurityToken; using Amazon.SecurityToken.Model; +using AWS.Messaging.IntegrationTests.Handlers; +using AWS.Messaging.Serialization; namespace AWS.Messaging.IntegrationTests; @@ -105,6 +107,7 @@ await _eventBridgeClient.PutTargetsAsync(new PutTargetsRequest { builder.AddEventBridgePublisher(_eventBusArn); builder.AddMessageSource("/aws/messaging"); + builder.AddMessageHandler(); }); _serviceProvider = serviceCollection.BuildServiceProvider(); } @@ -126,23 +129,23 @@ await publisher.PublishAsync(new ChatMessage var receiveMessageResponse = await _sqsClient.ReceiveMessageAsync(_sqsQueueUrl); var message = Assert.Single(receiveMessageResponse.Messages); - // EventBridge adds an external envelope which we need to strip away - var eventBridgeEnvelope = JsonSerializer.Deserialize(message.Body); - Assert.NotNull(eventBridgeEnvelope); + var envelopeSerializer = _serviceProvider.GetRequiredService(); - Assert.NotNull(eventBridgeEnvelope.Detail); - var envelope = eventBridgeEnvelope.Detail; + // Use the EnvelopeSerializer to convert the message + var result = await envelopeSerializer.ConvertToEnvelopeAsync(message); + var envelope = result.Envelope as MessageEnvelope; + + Assert.NotNull(envelope); Assert.False(string.IsNullOrEmpty(envelope.Id)); Assert.Equal("/aws/messaging", envelope.Source.ToString()); Assert.True(envelope.TimeStamp > publishStartTime); Assert.True(envelope.TimeStamp < publishEndTime); + Assert.Equal(typeof(ChatMessage).ToString(), envelope.MessageTypeIdentifier); - var messageType = Type.GetType(eventBridgeEnvelope.Detail.MessageTypeIdentifier); - Assert.NotNull(messageType); - - var chatMessageObject = JsonSerializer.Deserialize(eventBridgeEnvelope.Detail.Message, messageType); - var chatMessage = Assert.IsType(chatMessageObject); - Assert.Equal("Test1", chatMessage.MessageDescription); + var chatMessage = envelope.Message; + Assert.NotNull(chatMessage); + Assert.IsType(chatMessage); + Assert.Equal("Test1", chatMessage.MessageDescription);; } public async Task DisposeAsync() diff --git a/test/AWS.Messaging.IntegrationTests/SNSPublisherTests.cs b/test/AWS.Messaging.IntegrationTests/SNSPublisherTests.cs index ed80dc78..df74c4f6 100644 --- a/test/AWS.Messaging.IntegrationTests/SNSPublisherTests.cs +++ b/test/AWS.Messaging.IntegrationTests/SNSPublisherTests.cs @@ -8,6 +8,8 @@ using System.Text.Json; using Amazon.SimpleNotificationService; using Amazon.SimpleNotificationService.Model; +using AWS.Messaging.IntegrationTests.Handlers; +using AWS.Messaging.Serialization; namespace AWS.Messaging.IntegrationTests; @@ -45,6 +47,7 @@ public async Task InitializeAsync() { builder.AddSNSPublisher(_snsTopicArn); builder.AddMessageSource("/aws/messaging"); + builder.AddMessageHandler(); }); _serviceProvider = serviceCollection.BuildServiceProvider(); } @@ -70,18 +73,23 @@ await publisher.PublishAsync(new ChatMessage var snsEnvelope = JsonSerializer.Deserialize(message.Body); Assert.NotNull(snsEnvelope); - var envelope = JsonSerializer.Deserialize>(snsEnvelope.Message); + // Get the EnvelopeSerializer from the service provider + var envelopeSerializer = _serviceProvider.GetRequiredService(); + + // Use the EnvelopeSerializer to convert the message + var result = await envelopeSerializer.ConvertToEnvelopeAsync(message); + var envelope = result.Envelope as MessageEnvelope; + Assert.NotNull(envelope); Assert.False(string.IsNullOrEmpty(envelope.Id)); Assert.Equal("/aws/messaging", envelope.Source.ToString()); Assert.True(envelope.TimeStamp > publishStartTime); Assert.True(envelope.TimeStamp < publishEndTime); + Assert.Equal(typeof(ChatMessage).ToString(), envelope.MessageTypeIdentifier); - var messageType = Type.GetType(envelope.MessageTypeIdentifier); - Assert.NotNull(messageType); - - var chatMessageObject = JsonSerializer.Deserialize(envelope.Message, messageType); - var chatMessage = Assert.IsType(chatMessageObject); + var chatMessage = envelope.Message; + Assert.NotNull(chatMessage); + Assert.IsType(chatMessage); Assert.Equal("Test1", chatMessage.MessageDescription); } diff --git a/test/AWS.Messaging.IntegrationTests/SQSPublisherTests.cs b/test/AWS.Messaging.IntegrationTests/SQSPublisherTests.cs index 04baade8..9792f50f 100644 --- a/test/AWS.Messaging.IntegrationTests/SQSPublisherTests.cs +++ b/test/AWS.Messaging.IntegrationTests/SQSPublisherTests.cs @@ -6,6 +6,8 @@ using Microsoft.Extensions.DependencyInjection; using AWS.Messaging.IntegrationTests.Models; using System.Text.Json; +using AWS.Messaging.IntegrationTests.Handlers; +using AWS.Messaging.Serialization; namespace AWS.Messaging.IntegrationTests; @@ -33,6 +35,8 @@ public async Task InitializeAsync() { builder.AddSQSPublisher(_sqsQueueUrl); builder.AddMessageSource("/aws/messaging"); + builder.AddMessageHandler(); + }); _serviceProvider = serviceCollection.BuildServiceProvider(); } @@ -54,19 +58,27 @@ await publisher.PublishAsync(new ChatMessage var receiveMessageResponse = await _sqsClient.ReceiveMessageAsync(_sqsQueueUrl); var message = Assert.Single(receiveMessageResponse.Messages); - var envelope = JsonSerializer.Deserialize>(message.Body); + // Get the EnvelopeSerializer from the service provider + var envelopeSerializer = _serviceProvider.GetRequiredService(); + + // Use the EnvelopeSerializer to convert the message + var result = await envelopeSerializer.ConvertToEnvelopeAsync(message); + var envelope = result.Envelope as MessageEnvelope; + Assert.NotNull(envelope); Assert.False(string.IsNullOrEmpty(envelope.Id)); Assert.Equal("/aws/messaging", envelope.Source.ToString()); - Assert.True(envelope.TimeStamp > publishStartTime); + Assert.True(envelope.TimeStamp > publishStartTime); Assert.True(envelope.TimeStamp < publishEndTime); - var messageType = Type.GetType(envelope.MessageTypeIdentifier); - Assert.NotNull(messageType); - var chatMessageObject = JsonSerializer.Deserialize(envelope.Message, messageType); - var chatMessage = Assert.IsType(chatMessageObject); + Assert.Equal(typeof(ChatMessage).ToString(), envelope.MessageTypeIdentifier); + + var chatMessage = envelope.Message; + Assert.NotNull(chatMessage); + Assert.IsType(chatMessage); Assert.Equal("Test1", chatMessage.MessageDescription); } + public async Task DisposeAsync() { try diff --git a/test/AWS.Messaging.UnitTests/MessageHandlers/Handlers.cs b/test/AWS.Messaging.UnitTests/MessageHandlers/Handlers.cs index 0984d340..ec134dff 100644 --- a/test/AWS.Messaging.UnitTests/MessageHandlers/Handlers.cs +++ b/test/AWS.Messaging.UnitTests/MessageHandlers/Handlers.cs @@ -44,6 +44,16 @@ public Task HandleAsync(MessageEnvelope messa } } +public class PlainTextHandler : IMessageHandler +{ + public Task HandleAsync(MessageEnvelope messageEnvelope, CancellationToken token = default) + { + // Simple handler implementation for test purposes + return Task.FromResult(MessageProcessStatus.Success()); + } +} + + /// /// Implements handling for mutiple message types /// diff --git a/test/AWS.Messaging.UnitTests/SerializationTests/EnvelopeSerializerTests.cs b/test/AWS.Messaging.UnitTests/SerializationTests/EnvelopeSerializerTests.cs index 22a79d7a..d65164cb 100644 --- a/test/AWS.Messaging.UnitTests/SerializationTests/EnvelopeSerializerTests.cs +++ b/test/AWS.Messaging.UnitTests/SerializationTests/EnvelopeSerializerTests.cs @@ -35,6 +35,7 @@ public EnvelopeSerializerTests() { builder.AddSQSPublisher("sqsQueueUrl", "addressInfo"); builder.AddMessageHandler("addressInfo"); + builder.AddMessageHandler("plaintext"); builder.AddMessageSource("/aws/messaging"); }); @@ -119,7 +120,7 @@ public async Task SerializeEnvelope() // ASSERT // The \u0022 corresponds to quotation mark (") - var expectedBlob = "{\"id\":\"id-123\",\"source\":\"/backend/service\",\"specversion\":\"1.0\",\"type\":\"addressInfo\",\"time\":\"2000-12-05T10:30:55+00:00\",\"data\":\"{\\u0022Unit\\u0022:123,\\u0022Street\\u0022:\\u0022Prince St\\u0022,\\u0022ZipCode\\u0022:\\u002200001\\u0022}\"}"; + var expectedBlob = "{\"id\":\"id-123\",\"source\":\"/backend/service\",\"specversion\":\"1.0\",\"type\":\"addressInfo\",\"time\":\"2000-12-05T10:30:55+00:00\",\"datacontenttype\":\"application/json\",\"data\":{\"Unit\":123,\"Street\":\"Prince St\",\"ZipCode\":\"00001\"}}"; Assert.Equal(expectedBlob, jsonBlob); } @@ -272,19 +273,19 @@ public async Task ConvertToEnvelope_With_EventBridgeOuterEnvelope_In_SQSMessageB var serviceProvider = _serviceCollection.BuildServiceProvider(); var envelopeSerializer = serviceProvider.GetRequiredService(); - var innerMessageEnvelope = new MessageEnvelope + var innerMessageEnvelope = new MessageEnvelope { Id = "66659d05-e4ff-462f-81c4-09e560e66a5c", Source = new Uri("/aws/messaging", UriKind.Relative), Version = "1.0", MessageTypeIdentifier = "addressInfo", TimeStamp = _testdate, - Message = JsonSerializer.Serialize(new AddressInfo + Message = new AddressInfo { Street = "Prince St", Unit = 123, ZipCode = "00001" - }) + } }; var outerMessageEnvelope = new Dictionary @@ -397,7 +398,7 @@ public async Task SerializationCallbacks_AreCorrectlyInvoked() var serializedMessage = await envelopeSerializer.SerializeAsync(messageEnvelope); // ASSERT - Check expected base 64 encoded string - var expectedserializedMessage = "eyJpZCI6IjEyMyIsInNvdXJjZSI6Ii9hd3MvbWVzc2FnaW5nIiwic3BlY3ZlcnNpb24iOiIxLjAiLCJ0eXBlIjoiYWRkcmVzc0luZm8iLCJ0aW1lIjoiMjAwMC0xMi0wNVQxMDozMDo1NSswMDowMCIsImRhdGEiOiJ7XHUwMDIyVW5pdFx1MDAyMjoxMjMsXHUwMDIyU3RyZWV0XHUwMDIyOlx1MDAyMlByaW5jZSBTdFx1MDAyMixcdTAwMjJaaXBDb2RlXHUwMDIyOlx1MDAyMjAwMDAxXHUwMDIyfSIsIklzLURlbGl2ZXJlZCI6ZmFsc2V9"; + var expectedserializedMessage = "eyJpZCI6IjEyMyIsInNvdXJjZSI6Ii9hd3MvbWVzc2FnaW5nIiwic3BlY3ZlcnNpb24iOiIxLjAiLCJ0eXBlIjoiYWRkcmVzc0luZm8iLCJ0aW1lIjoiMjAwMC0xMi0wNVQxMDozMDo1NSswMDowMCIsImRhdGFjb250ZW50dHlwZSI6ImFwcGxpY2F0aW9uL2pzb24iLCJkYXRhIjp7IlVuaXQiOjEyMywiU3RyZWV0IjoiUHJpbmNlIFN0IiwiWmlwQ29kZSI6IjAwMDAxIn0sIklzLURlbGl2ZXJlZCI6ZmFsc2V9"; Assert.Equal(expectedserializedMessage, serializedMessage); // ACT - Convert To Envelope from base 64 Encoded Message @@ -452,6 +453,15 @@ public async Task SerializeAsync_DataMessageLogging_NoError(bool dataMessageLogg } }; + var serializedContent = JsonSerializer.Serialize(messageEnvelope.Message); + var messageSerializeResults = new MessageSerializerResults(serializedContent, "application/json"); + + + // Mock the serializer to return a specific string + messageSerializer + .Setup(x => x.Serialize(It.IsAny())) + .Returns(messageSerializeResults); + await envelopeSerializer.SerializeAsync(messageEnvelope); if (dataMessageLogging) @@ -459,7 +469,7 @@ public async Task SerializeAsync_DataMessageLogging_NoError(bool dataMessageLogg logger.Verify(log => log.Log( It.Is(logLevel => logLevel == LogLevel.Trace), It.Is(eventId => eventId.Id == 0), - It.Is((@object, @type) => @object.ToString() == "Serialized the MessageEnvelope object as the following raw string:\n{\"id\":\"123\",\"source\":\"/aws/messaging\",\"specversion\":\"1.0\",\"type\":\"addressInfo\",\"time\":\"2000-12-05T10:30:55+00:00\",\"data\":null}"), + It.Is((@object, @type) => @object.ToString() == "Serialized the MessageEnvelope object as the following raw string:\n{\"id\":\"123\",\"source\":\"/aws/messaging\",\"specversion\":\"1.0\",\"type\":\"addressInfo\",\"time\":\"2000-12-05T10:30:55+00:00\",\"datacontenttype\":\"application/json\",\"data\":{\"Unit\":123,\"Street\":\"Prince St\",\"ZipCode\":\"00001\"}}"), null, It.IsAny>()), Times.Once); @@ -612,22 +622,30 @@ public async Task ConvertToEnvelope_WithMetadata_PreservesOnlyExpectedMetadataPr var envelopeSerializer = serviceProvider.GetRequiredService(); // Create a JSON string with both standard envelope properties and custom metadata - var jsonString = @"{ - ""id"": ""test-id-123"", - ""source"": ""/aws/messaging"", - ""specversion"": ""1.0"", - ""type"": ""addressInfo"", - ""time"": ""2000-12-05T10:30:55+00:00"", - ""data"": ""{\""Unit\"":123,\""Street\"":\""Prince St\"",\""ZipCode\"":\""00001\""}"", - ""customString"": ""test-value"", - ""customNumber"": 42, - ""customBoolean"": true, - ""customObject"": {""nestedKey"": ""nestedValue""} - }"; + var testData = new + { + id = "test-id-123", + source = "/aws/messaging", + specversion = "1.0", + type = "addressInfo", + time = "2000-12-05T10:30:55+00:00", + data = new AddressInfo + { + Unit = 123, + Street = "Prince St", + ZipCode = "00001" + }, + customString = "test-value", + customNumber = 42, + customBoolean = true, + customObject = new { nestedKey = "nestedValue" } + }; + + var json = JsonSerializer.Serialize(testData); var sqsMessage = new Message { - Body = jsonString + Body = json }; // ACT @@ -720,6 +738,147 @@ public async Task ConvertToEnvelope_NullSubscriberMapping_ThrowsException() Assert.Contains("addressInfo", innerException.Message); } + [Fact] + public async Task ConvertToEnvelope_WithNonJsonContentType() + { + // ARRANGE + var logger = new Mock>(); + var services = new ServiceCollection(); + services.AddAWSMessageBus(builder => + { + builder.AddMessageHandler("plaintext"); + }); + var serviceProvider = services.BuildServiceProvider(); + var messageConfiguration = serviceProvider.GetRequiredService(); + var messageSerializer = new Mock(); + var dateTimeHandler = new Mock(); + var messageIdGenerator = new Mock(); + var messageSourceHandler = new Mock(); + var envelopeSerializer = new EnvelopeSerializer(logger.Object, messageConfiguration, messageSerializer.Object, dateTimeHandler.Object, messageIdGenerator.Object, messageSourceHandler.Object); + var plainTextContent = "Hello, this is plain text content"; + var messageEnvelope = new MessageEnvelope + { + Id = "id-123", + Source = new Uri("/aws/messaging", UriKind.Relative), + Version = "1.0", + MessageTypeIdentifier = "plaintext", + TimeStamp = _testdate, + Message = plainTextContent + }; + + var serializedContent = JsonSerializer.Serialize(messageEnvelope.Message); + var messageSerializeResults = new MessageSerializerResults(serializedContent, "text/plain"); + + // Mock the serializer to return a specific string + messageSerializer + .Setup(x => x.Serialize(It.IsAny())) + .Returns(messageSerializeResults); + + messageSerializer + .Setup(x => x.Deserialize(It.IsAny(), typeof(string))) + .Returns("Hello, this is plain text content"); + + var sqsMessage = new Message + { + Body = await envelopeSerializer.SerializeAsync(messageEnvelope), + ReceiptHandle = "receipt-handle" + }; + + // ACT + var result = await envelopeSerializer.ConvertToEnvelopeAsync(sqsMessage); + + // ASSERT + var envelope = (MessageEnvelope)result.Envelope; + Assert.NotNull(envelope); + Assert.Equal(_testdate, envelope.TimeStamp); + Assert.Equal("1.0", envelope.Version); + Assert.Equal("/aws/messaging", envelope.Source?.ToString()); + Assert.Equal("plaintext", envelope.MessageTypeIdentifier); + Assert.Equal("text/plain", envelope.DataContentType); + Assert.Equal(plainTextContent, envelope.Message); + } + + [Fact] + public async Task ConvertToEnvelope_WithCustomJsonContentType() + { + // ARRANGE + var mockMessageSerializer = new Mock(); + _serviceCollection.RemoveAll(); + _serviceCollection.AddSingleton(mockMessageSerializer.Object); + + var serviceProvider = _serviceCollection.BuildServiceProvider(); + var envelopeSerializer = serviceProvider.GetRequiredService(); + + var message = new AddressInfo + { + Street = "Prince St", + Unit = 123, + ZipCode = "00001" + }; + + // Mock serializer behavior + var serializedMessage = JsonSerializer.Serialize(message); + mockMessageSerializer + .Setup(x => x.Serialize(It.IsAny())) + .Returns(new MessageSerializerResults(serializedMessage, "application/ld+json")); + + mockMessageSerializer + .Setup(x => x.Deserialize(It.IsAny(), typeof(AddressInfo))) + .Returns(message); + + // Create the envelope + var envelope = new MessageEnvelope + { + Id = "test-id-123", + Source = new Uri("/aws/messaging", UriKind.Relative), + Version = "1.0", + MessageTypeIdentifier = "addressInfo", + TimeStamp = _testdate, + Message = message + }; + + // Serialize the envelope to SQS message + var sqsMessage = new Message + { + Body = await envelopeSerializer.SerializeAsync(envelope), + ReceiptHandle = "receipt-handle" + }; + + // ACT + var result = await envelopeSerializer.ConvertToEnvelopeAsync(sqsMessage); + + // ASSERT + var deserializedEnvelope = (MessageEnvelope)result.Envelope; + Assert.NotNull(deserializedEnvelope); + + // Verify the content type was preserved + Assert.Equal("application/ld+json", deserializedEnvelope.DataContentType); + + // Verify the message was correctly deserialized + Assert.NotNull(deserializedEnvelope.Message); + Assert.Equal("Prince St", deserializedEnvelope.Message.Street); + Assert.Equal(123, deserializedEnvelope.Message.Unit); + Assert.Equal("00001", deserializedEnvelope.Message.ZipCode); + + // Verify other envelope properties + Assert.Equal("test-id-123", deserializedEnvelope.Id); + Assert.Equal("/aws/messaging", deserializedEnvelope.Source?.ToString()); + Assert.Equal("1.0", deserializedEnvelope.Version); + Assert.Equal("addressInfo", deserializedEnvelope.MessageTypeIdentifier); + Assert.Equal(_testdate, deserializedEnvelope.TimeStamp); + + // Verify the serializer was called with correct parameters + mockMessageSerializer.Verify( + x => x.Serialize(It.IsAny()), + Times.Once); + + mockMessageSerializer.Verify( + x => x.Deserialize(It.IsAny(), typeof(AddressInfo)), + Times.Once); + } + + + } public class MockSerializationCallback : ISerializationCallback diff --git a/test/AWS.Messaging.UnitTests/SerializationTests/MessageSerializerTests.cs b/test/AWS.Messaging.UnitTests/SerializationTests/MessageSerializerTests.cs index 8f7015ac..194f2939 100644 --- a/test/AWS.Messaging.UnitTests/SerializationTests/MessageSerializerTests.cs +++ b/test/AWS.Messaging.UnitTests/SerializationTests/MessageSerializerTests.cs @@ -47,11 +47,12 @@ public void Serialize(IMessageJsonSerializerContextContainer messageJsonSerializ }; // ACT - var jsonString = serializer.Serialize(person); + var result = serializer.Serialize(person); // ASSERT var expectedString = "{\"FirstName\":\"Bob\",\"LastName\":\"Stone\",\"Age\":30,\"Gender\":\"Male\",\"Address\":{\"Unit\":12,\"Street\":\"Prince St\",\"ZipCode\":\"00001\"}}"; - Assert.Equal(expectedString, jsonString); + Assert.Equal(expectedString, result.Data); + Assert.Equal("application/json", result.ContentType); } [Theory] @@ -75,7 +76,7 @@ public void Serialize_NoDataMessageLogging_NoError(IMessageJsonSerializerContext } }; - var jsonString = serializer.Serialize(person); + var jsonString = serializer.Serialize(person).Data; _logger.Verify(logger => logger.Log( It.Is(logLevel => logLevel == LogLevel.Trace),