From 1ddd758823186d08ea8d528c1460ba0105e5b7e2 Mon Sep 17 00:00:00 2001 From: Rafael Lillo Date: Sun, 22 Dec 2024 17:00:40 +0000 Subject: [PATCH 01/18] GH-1294 Add support to FIFO --- .../SnsPublication.cs | 42 +++- .../SnsSqsType.cs | 42 ++++ .../SqsInlineMessageCreator.cs | 46 +++- .../SqsMessageCreator.cs | 99 +++++--- .../SqsMessageProducer.cs | 6 +- .../SqsMessagePublisher.cs | 14 +- .../SqsSubscription.cs | 16 +- .../ValidateTopicByArnConvention.cs | 13 +- .../ValidateTopicByName.cs | 9 +- ...essage_consumer_reads_multiple_messages.cs | 221 +++++++++++++++--- .../When_infastructure_exists_can_assume.cs | 111 +++++++-- .../When_infastructure_exists_can_verify.cs | 116 +++++++-- ...ructure_exists_can_verify_by_convention.cs | 118 ++++++++-- ...ing_a_message_via_the_messaging_gateway.cs | 124 +++++++++- .../When_topic_missing_verify_throws.cs | 25 +- 15 files changed, 835 insertions(+), 167 deletions(-) create mode 100644 src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsSqsType.cs diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsPublication.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsPublication.cs index 319512d061..d3e591c9a6 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsPublication.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsPublication.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2022 Ian Cooper @@ -19,6 +20,7 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + #endregion namespace Paramore.Brighter.MessagingGateway.AWSSQS @@ -31,9 +33,8 @@ public class SnsPublication : Publication /// TopicFindBy.Convention -> The routing key is a name, but use convention to make an Arn for this account /// TopicFindBy.Name -> Treat the routing key as a name & use ListTopics to find it (rate limited 30/s) /// - public TopicFindBy FindTopicBy { get; set; } = TopicFindBy.Convention; - + /// /// The attributes of the topic. If TopicARNs is set we will always assume that we do not /// need to create or validate the SNS Topic @@ -45,5 +46,42 @@ public class SnsPublication : Publication /// as we use the topic from the header to dispatch to an Arn. /// public string TopicArn { get; set; } + + /// + /// The AWS SQS type. + /// + public SnsSqsType SnsType { get; set; } = SnsSqsType.Standard; + + /// + /// Amazon SNS FIFO topics and Amazon SQS FIFO queues support message deduplication, which provides + /// exactly-once message delivery and processing as long as the following conditions are met: + /// + /// + /// + /// The subscribed Amazon SQS FIFO queue exists and has permissions that allow the + /// AmazonSNS service principal to deliver messages to the queue. + /// + /// + /// + /// + /// The Amazon SQS FIFO queue consumer processes the message and deletes it from the + /// queue before the visibility timeout expires. + /// + /// + /// + /// + /// The Amazon SNS subscription topic has no message filtering. When you configure + /// message filtering, Amazon SNS FIFO topics support at-most-once delivery, as messages + /// can be filtered out based on your subscription filter policies. + /// + /// + /// + /// + /// There are no network disruptions that prevent acknowledgment of the message delivery + /// + /// + /// + /// + public bool Deduplication { get; set; } } } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsSqsType.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsSqsType.cs new file mode 100644 index 0000000000..9104a2294e --- /dev/null +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsSqsType.cs @@ -0,0 +1,42 @@ +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +/// +/// AWS offer two types of SQS. +/// For more information see +/// https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-queue-types.html +/// +public enum SnsSqsType +{ + /// + /// Standard queues support a very high, nearly unlimited number of API calls per second per + /// action (SendMessage, ReceiveMessage, or DeleteMessage). This high throughput makes them + /// ideal for use cases that require processing large volumes of messages quickly, such as + /// real-time data streaming or large-scale applications. While standard queues scale + /// automatically with demand, it's essential to monitor usage patterns to ensure optimal + /// performance, especially in regions with higher workloads. + /// + Standard, + + /// + /// FIFO (First-In-First-Out) queues have all the capabilities of the standard queues, + /// but are designed to enhance messaging between applications when the order of operations + /// and events is critical, or where duplicates can't be tolerated. + /// The most important features of FIFO queues are FIFO (First-In-First-Out) delivery and + /// exactly-once processing: + /// + /// + /// + /// The order in which messages are sent and received is strictly preserved and + /// a message is delivered once and remains unavailable until a consumer processes + /// and deletes it. + /// + /// + /// + /// + /// Duplicates aren't introduced into the queue. + /// + /// + /// + /// + Fifo +} diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs index c591a02721..dc79902668 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2022 Ian Cooper @@ -19,6 +20,7 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + #endregion using System; @@ -31,7 +33,7 @@ namespace Paramore.Brighter.MessagingGateway.AWSSQS { internal class SqsInlineMessageCreator : SqsMessageCreatorBase, ISqsMessageCreator { - private static readonly ILogger s_logger= ApplicationLogging.CreateLogger(); + private static readonly ILogger s_logger = ApplicationLogging.CreateLogger(); private Dictionary _messageAttributes = new Dictionary(); @@ -64,7 +66,9 @@ public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) replyTo = ReadReplyTo(); subject = ReadMessageSubject(jsonDocument); receiptHandle = ReadReceiptHandle(sqsMessage); - + var partitionKey = ReadPartitionKey(); + var deduplicationId = ReadMessageDeduplicationId(); + //TODO:CLOUD_EVENTS parse from headers var messageHeader = new MessageHeader( @@ -80,7 +84,9 @@ public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) handledCount: handledCount.Result, dataSchema: null, subject: subject.Result, - delayed: TimeSpan.Zero); + delayed: TimeSpan.Zero, + partitionKey: partitionKey.Result + ); message = new Message(messageHeader, ReadMessageBody(jsonDocument)); @@ -91,6 +97,11 @@ public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) message.Header.Bag.Add(key, bag[key]); } + if (deduplicationId.Success) + { + message.Header.Bag.Add("DeduplicationId", deduplicationId); + } + if (receiptHandle.Success) message.Header.Bag.Add("ReceiptHandle", sqsMessage.ReceiptHandle); } @@ -99,8 +110,8 @@ public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) s_logger.LogWarning(e, "Failed to create message from Aws Sqs message"); message = FailureMessage(topic, messageId); } - - + + return message; } @@ -149,7 +160,6 @@ private Dictionary ReadMessageBag() } catch (Exception) { - } } @@ -272,5 +282,29 @@ private static MessageBody ReadMessageBody(JsonDocument jsonDocument) return new MessageBody(string.Empty); } + + private HeaderResult ReadPartitionKey() + { + if (_messageAttributes.TryGetValue(HeaderNames.MessageGroupId, out var value)) + { + //we have an arn, and we want the topic + var messageGroupId = value.GetValueInString(); + return new HeaderResult(messageGroupId, true); + } + + return new HeaderResult(string.Empty, true); + } + + private HeaderResult ReadMessageDeduplicationId() + { + if (_messageAttributes.TryGetValue(HeaderNames.DeduplicationId, out var value)) + { + //we have an arn, and we want the topic + var deduplicationId = value.GetValueInString(); + return new HeaderResult(deduplicationId, true); + } + + return new HeaderResult(string.Empty, true); + } } } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs index 3fa51ad63a..aed2203b50 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2022 Ian Cooper @@ -19,6 +20,7 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + #endregion using System; @@ -43,10 +45,10 @@ internal enum ARNAmazonSNS TopicName = 5, SubscriptionId = 6 } - + internal class SqsMessageCreator : SqsMessageCreatorBase, ISqsMessageCreator { - private static readonly ILogger s_logger= ApplicationLogging.CreateLogger(); + private static readonly ILogger s_logger = ApplicationLogging.CreateLogger(); public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) { @@ -59,7 +61,7 @@ public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) var timeStamp = HeaderResult.Empty(); var receiptHandle = HeaderResult.Empty(); var replyTo = HeaderResult.Empty(); - + //TODO:CLOUD_EVENTS parse from headers Message message; @@ -68,12 +70,14 @@ public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) topic = ReadTopic(sqsMessage); messageId = ReadMessageId(sqsMessage); contentType = ReadContentType(sqsMessage); - correlationId = ReadCorrelationid(sqsMessage); + correlationId = ReadCorrelationId(sqsMessage); handledCount = ReadHandledCount(sqsMessage); messageType = ReadMessageType(sqsMessage); timeStamp = ReadTimestamp(sqsMessage); replyTo = ReadReplyTo(sqsMessage); receiptHandle = ReadReceiptHandle(sqsMessage); + var partitionKey = ReadPartitionKey(sqsMessage); + var deduplicationId = ReadMessageDeduplicationId(sqsMessage); var messageHeader = new MessageHeader( messageId: messageId.Result, @@ -82,17 +86,18 @@ public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) source: null, type: "", timeStamp: timeStamp.Success ? timeStamp.Result : DateTime.UtcNow, - correlationId:correlationId.Success ? correlationId.Result : "", + correlationId: correlationId.Success ? correlationId.Result : "", replyTo: replyTo.Success ? new RoutingKey(replyTo.Result) : RoutingKey.Empty, contentType: contentType.Success ? contentType.Result : "", handledCount: handledCount.Result, dataSchema: null, subject: null, - delayed: TimeSpan.Zero + delayed: TimeSpan.Zero, + partitionKey: partitionKey.Success ? partitionKey.Result : string.Empty ); message = new Message(messageHeader, ReadMessageBody(sqsMessage, messageHeader.ContentType)); - + //deserialize the bag var bag = ReadMessageBag(sqsMessage); foreach (var key in bag.Keys) @@ -100,7 +105,12 @@ public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) message.Header.Bag.Add(key, bag[key]); } - if(receiptHandle.Success) + if (deduplicationId.Success) + { + bag.Add("DeduplicationId", deduplicationId.Result); + } + + if (receiptHandle.Success) message.Header.Bag.Add("ReceiptHandle", receiptHandle.Result); } catch (Exception e) @@ -108,26 +118,29 @@ public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) s_logger.LogWarning(e, "Failed to create message from amqp message"); message = FailureMessage(topic, messageId); } - - + + return message; } private static MessageBody ReadMessageBody(Amazon.SQS.Model.Message sqsMessage, string contentType) { - if(contentType == CompressPayloadTransformerAsync.GZIP || contentType == CompressPayloadTransformerAsync.DEFLATE || contentType == CompressPayloadTransformerAsync.BROTLI) + if (contentType == CompressPayloadTransformerAsync.GZIP || + contentType == CompressPayloadTransformerAsync.DEFLATE || + contentType == CompressPayloadTransformerAsync.BROTLI) return new MessageBody(sqsMessage.Body, contentType, CharacterEncoding.Base64); - + return new MessageBody(sqsMessage.Body, contentType); } - private Dictionary ReadMessageBag(Amazon.SQS.Model.Message sqsMessage) + private static Dictionary ReadMessageBag(Amazon.SQS.Model.Message sqsMessage) { if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.Bag, out MessageAttributeValue value)) { try { - var bag = JsonSerializer.Deserialize>(value.StringValue, JsonSerialisationOptions.Options); + var bag = JsonSerializer.Deserialize>(value.StringValue, + JsonSerialisationOptions.Options); return bag; } catch (Exception) @@ -135,19 +148,21 @@ private Dictionary ReadMessageBag(Amazon.SQS.Model.Message sqsMe //we weill just suppress conversion errors, and return an empty bag } } + return new Dictionary(); } - private HeaderResult ReadReplyTo(Amazon.SQS.Model.Message sqsMessage) + private static HeaderResult ReadReplyTo(Amazon.SQS.Model.Message sqsMessage) { if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.ReplyTo, out MessageAttributeValue value)) { return new HeaderResult(value.StringValue, true); } - return new HeaderResult(String.Empty, true); + + return new HeaderResult(string.Empty, true); } - - private HeaderResult ReadTimestamp(Amazon.SQS.Model.Message sqsMessage) + + private static HeaderResult ReadTimestamp(Amazon.SQS.Model.Message sqsMessage) { if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.Timestamp, out MessageAttributeValue value)) { @@ -156,10 +171,11 @@ private HeaderResult ReadTimestamp(Amazon.SQS.Model.Message sqsMessage return new HeaderResult(timestamp, true); } } + return new HeaderResult(DateTime.UtcNow, true); } - private HeaderResult ReadMessageType(Amazon.SQS.Model.Message sqsMessage) + private static HeaderResult ReadMessageType(Amazon.SQS.Model.Message sqsMessage) { if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.MessageType, out MessageAttributeValue value)) { @@ -168,10 +184,11 @@ private HeaderResult ReadMessageType(Amazon.SQS.Model.Message sqsMe return new HeaderResult(messageType, true); } } + return new HeaderResult(MessageType.MT_EVENT, true); } - private HeaderResult ReadHandledCount(Amazon.SQS.Model.Message sqsMessage) + private static HeaderResult ReadHandledCount(Amazon.SQS.Model.Message sqsMessage) { if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.HandledCount, out MessageAttributeValue value)) { @@ -180,37 +197,42 @@ private HeaderResult ReadHandledCount(Amazon.SQS.Model.Message sqsMessage) return new HeaderResult(handledCount, true); } } + return new HeaderResult(0, true); } - private HeaderResult ReadCorrelationid(Amazon.SQS.Model.Message sqsMessage) + private static HeaderResult ReadCorrelationId(Amazon.SQS.Model.Message sqsMessage) { - if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.CorrelationId, out MessageAttributeValue correlationId)) + if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.CorrelationId, + out MessageAttributeValue correlationId)) { return new HeaderResult(correlationId.StringValue, true); } + return new HeaderResult(string.Empty, true); - } + } - private HeaderResult ReadContentType(Amazon.SQS.Model.Message sqsMessage) + private static HeaderResult ReadContentType(Amazon.SQS.Model.Message sqsMessage) { if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.ContentType, out MessageAttributeValue value)) { return new HeaderResult(value.StringValue, true); } + return new HeaderResult(String.Empty, true); } - private HeaderResult ReadMessageId(Amazon.SQS.Model.Message sqsMessage) + private static HeaderResult ReadMessageId(Amazon.SQS.Model.Message sqsMessage) { if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.Id, out MessageAttributeValue value)) { return new HeaderResult(value.StringValue, true); } + return new HeaderResult(string.Empty, true); } - private HeaderResult ReadTopic(Amazon.SQS.Model.Message sqsMessage) + private static HeaderResult ReadTopic(Amazon.SQS.Model.Message sqsMessage) { if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.Topic, out MessageAttributeValue value)) { @@ -219,7 +241,32 @@ private HeaderResult ReadTopic(Amazon.SQS.Model.Message sqsMessage) var topic = arnElements[(int)ARNAmazonSNS.TopicName]; return new HeaderResult(new RoutingKey(topic), true); } + return new HeaderResult(RoutingKey.Empty, true); } + + private static HeaderResult ReadPartitionKey(Amazon.SQS.Model.Message sqsMessage) + { + if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.MessageGroupId, out MessageAttributeValue value)) + { + //we have an arn, and we want the topic + var messageGroupId = value.StringValue; + return new HeaderResult(messageGroupId, true); + } + + return new HeaderResult(string.Empty, true); + } + + private static HeaderResult ReadMessageDeduplicationId(Amazon.SQS.Model.Message sqsMessage) + { + if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.DeduplicationId , out MessageAttributeValue value)) + { + //we have an arn, and we want the topic + var deduplicationId = value.StringValue; + return new HeaderResult(deduplicationId, true); + } + + return new HeaderResult(string.Empty, true); + } } } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs index 4f9a01a8e4..381a379c92 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs @@ -72,7 +72,9 @@ await EnsureTopicAsync( topic != null ? new RoutingKey(topic) : _publication.Topic, _publication.SnsAttributes, _publication.FindTopicBy, - _publication.MakeChannels); + _publication.MakeChannels, + _publication.SnsType, + _publication.Deduplication); } return !string.IsNullOrEmpty(ChannelTopicArn); @@ -90,7 +92,7 @@ public async Task SendAsync(Message message) await ConfirmTopicExistsAsync(message.Header.Topic); using var client = _clientFactory.CreateSnsClient(); - var publisher = new SqsMessagePublisher(ChannelTopicArn, client); + var publisher = new SqsMessagePublisher(ChannelTopicArn, client, _publication.SnsType, _publication.Deduplication); var messageId = await publisher.PublishAsync(message); if (messageId != null) { diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessagePublisher.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessagePublisher.cs index 96a6a6f742..be3b8a3ba2 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessagePublisher.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessagePublisher.cs @@ -34,17 +34,29 @@ public class SqsMessagePublisher { private readonly string _topicArn; private readonly AmazonSimpleNotificationServiceClient _client; + private readonly SnsSqsType _snsSqsType; + private readonly bool _deduplication; - public SqsMessagePublisher(string topicArn, AmazonSimpleNotificationServiceClient client) + public SqsMessagePublisher(string topicArn, AmazonSimpleNotificationServiceClient client, SnsSqsType snsSqsType, bool deduplication) { _topicArn = topicArn; _client = client; + _snsSqsType = snsSqsType; + _deduplication = deduplication; } public async Task PublishAsync(Message message) { var messageString = message.Body.Value; var publishRequest = new PublishRequest(_topicArn, messageString, message.Header.Subject); + if (_snsSqsType == SnsSqsType.Fifo) + { + publishRequest.MessageGroupId = message.Header.PartitionKey; + if (_deduplication && message.Header.Bag.TryGetValue(HeaderNames.DeduplicationId, out var deduplicationId)) + { + publishRequest.MessageDeduplicationId = (string)deduplicationId; + } + } var messageAttributes = new Dictionary(); messageAttributes.Add(HeaderNames.Id, new MessageAttributeValue{StringValue = Convert.ToString(message.Header.MessageId), DataType = "String"}); diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs index be40e01034..87f24c39fe 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs @@ -86,6 +86,11 @@ public class SqsSubscription : Subscription /// A list of resource tags to use when creating the queue /// public Dictionary Tags { get; } + + /// + /// The AWS SQS type. + /// + public SnsSqsType SqsType { get; } /// /// Initializes a new instance of the class. @@ -114,6 +119,7 @@ public class SqsSubscription : Subscription /// The indication of Raw Message Delivery setting is enabled or disabled /// How long to pause when a channel is empty in milliseconds /// How long to pause when there is a channel failure in milliseconds + /// The SQS Type public SqsSubscription(Type dataType, SubscriptionName name = null, ChannelName channelName = null, @@ -137,7 +143,8 @@ public SqsSubscription(Type dataType, OnMissingChannel makeChannels = OnMissingChannel.Create, bool rawMessageDelivery = true, int emptyChannelDelay = 500, - int channelFailureDelay = 1000 + int channelFailureDelay = 1000, + SnsSqsType sqsType = SnsSqsType.Standard ) : base(dataType, name, channelName, routingKey, bufferSize, noOfPerformers, timeOut, requeueCount, requeueDelay, unacceptableMessageLimit, runAsync, channelFactory, makeChannels, emptyChannelDelay, channelFailureDelay) @@ -151,6 +158,7 @@ public SqsSubscription(Type dataType, RedrivePolicy = redrivePolicy; SnsAttributes = snsAttributes; Tags = tags; + SqsType = sqsType; } } @@ -189,6 +197,7 @@ public class SqsSubscription : SqsSubscription where T : IRequest /// The indication of Raw Message Delivery setting is enabled or disabled /// How long to pause when a channel is empty in milliseconds /// How long to pause when there is a channel failure in milliseconds + /// The SQS Type public SqsSubscription(SubscriptionName name = null, ChannelName channelName = null, RoutingKey routingKey = null, @@ -211,11 +220,12 @@ public SqsSubscription(SubscriptionName name = null, OnMissingChannel makeChannels = OnMissingChannel.Create, bool rawMessageDelivery = true, int emptyChannelDelay = 500, - int channelFailureDelay = 1000 + int channelFailureDelay = 1000, + SnsSqsType sqsType = SnsSqsType.Standard ) : base(typeof(T), name, channelName, routingKey, bufferSize, noOfPerformers, timeOut, requeueCount, requeueDelay, unacceptableMessageLimit, runAsync, channelFactory, lockTimeout, delaySeconds, messageRetentionPeriod,findTopicBy, - iAmPolicy,redrivePolicy, snsAttributes, tags, makeChannels, rawMessageDelivery, emptyChannelDelay, channelFailureDelay) + iAmPolicy,redrivePolicy, snsAttributes, tags, makeChannels, rawMessageDelivery, emptyChannelDelay, channelFailureDelay, sqsType) { } } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByArnConvention.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByArnConvention.cs index 698d3a412c..65d5a99baa 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByArnConvention.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByArnConvention.cs @@ -34,12 +34,14 @@ namespace Paramore.Brighter.MessagingGateway.AWSSQS public class ValidateTopicByArnConvention : ValidateTopicByArn, IValidateTopic { private readonly RegionEndpoint _region; - private AmazonSecurityTokenServiceClient _stsClient; + private readonly AmazonSecurityTokenServiceClient _stsClient; + private readonly SnsSqsType _type; - public ValidateTopicByArnConvention(AWSCredentials credentials, RegionEndpoint region, Action clientConfigAction = null) + public ValidateTopicByArnConvention(AWSCredentials credentials, RegionEndpoint region, Action clientConfigAction = null, SnsSqsType type = SnsSqsType.Standard) : base(credentials, region, clientConfigAction) { _region = region; + _type = type; var clientFactory = new AWSClientFactory(credentials, region, clientConfigAction); _stsClient = clientFactory.CreateStsClient(); @@ -58,7 +60,12 @@ private string GetArnFromTopic(string topicName) ) .GetAwaiter().GetResult(); - if (callerIdentityResponse.HttpStatusCode != HttpStatusCode.OK) throw new InvalidOperationException("Could not find identity of AWS account"); + if (callerIdentityResponse.HttpStatusCode != HttpStatusCode.OK) throw new InvalidOperationException("Could not find identity of AWS account"); + + if (_type == SnsSqsType.Fifo && !topicName.EndsWith(".fifo")) + { + topicName += ".fifo"; + } return new Arn { diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByName.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByName.cs index 74c2b40b58..3cb8e9d347 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByName.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByName.cs @@ -32,11 +32,13 @@ namespace Paramore.Brighter.MessagingGateway.AWSSQS internal class ValidateTopicByName : IValidateTopic { private readonly AmazonSimpleNotificationServiceClient _snsClient; + private readonly SnsSqsType _type; - public ValidateTopicByName(AWSCredentials credentials, RegionEndpoint region, Action clientConfigAction = null) + public ValidateTopicByName(AWSCredentials credentials, RegionEndpoint region, Action clientConfigAction = null, SnsSqsType type = SnsSqsType.Standard) { var clientFactory = new AWSClientFactory(credentials, region, clientConfigAction); _snsClient = clientFactory.CreateSnsClient(); + _type = type; } public ValidateTopicByName(AmazonSimpleNotificationServiceClient snsClient) @@ -49,6 +51,11 @@ public ValidateTopicByName(AmazonSimpleNotificationServiceClient snsClient) //you may run into issues public async Task<(bool, string TopicArn)> ValidateAsync(string topicName) { + if (_type == SnsSqsType.Fifo && !topicName.EndsWith(".fifo")) + { + topicName += ".fifo"; + } + var topic = await _snsClient.FindTopicAsync(topicName); return (topic != null, topic?.TopicArn); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_a_message_consumer_reads_multiple_messages.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_a_message_consumer_reads_multiple_messages.cs index 33ce5108a2..7c8b99c9eb 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_a_message_consumer_reads_multiple_messages.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_a_message_consumer_reads_multiple_messages.cs @@ -18,8 +18,15 @@ public class SQSBufferedConsumerTests : IDisposable { private readonly SqsMessageProducer _messageProducer; private readonly SqsMessageConsumer _consumer; - private readonly string _topicName; + private readonly string _topicName; private readonly ChannelFactory _channelFactory; + + + private readonly SqsMessageProducer _fifoMessageProducer; + private readonly SqsMessageConsumer _fifoConsumer; + private readonly string _fifoTopicName; + private readonly ChannelFactory _fifoChannelFactory; + private const string _contentType = "text\\plain"; private const int _bufferSize = 3; private const int _messageCount = 4; @@ -30,59 +37,87 @@ public SQSBufferedConsumerTests() var awsConnection = new AWSMessagingGatewayConnection(credentials, region); _channelFactory = new ChannelFactory(awsConnection); + var channelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); _topicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - + //we need the channel to create the queues and notifications var routingKey = new RoutingKey(_topicName); - + var channel = _channelFactory.CreateChannel(new SqsSubscription( name: new SubscriptionName(channelName), - channelName:new ChannelName(channelName), - routingKey:routingKey, + channelName: new ChannelName(channelName), + routingKey: routingKey, bufferSize: _bufferSize, makeChannels: OnMissingChannel.Create - )); - + )); + //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel //just for the tests, so create a new consumer from the properties - _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), routingKey, _bufferSize); - _messageProducer = new SqsMessageProducer(awsConnection, + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), routingKey, + _bufferSize); + _messageProducer = new SqsMessageProducer(awsConnection, + new SnsPublication { MakeChannels = OnMissingChannel.Create }); + + // Because fifo can modify the topic name we need to run the same test twice, one for standard queue and another to FIFO + _fifoChannelFactory = new ChannelFactory(awsConnection); + var fifoChannelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _fifoTopicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + + //we need the channel to create the queues and notifications + var fifoRoutingKey = new RoutingKey(_fifoTopicName); + + var fifoChannel = _fifoChannelFactory.CreateChannel(new SqsSubscription( + name: new SubscriptionName(fifoChannelName), + channelName: new ChannelName(fifoChannelName), + routingKey: fifoRoutingKey, + bufferSize: _bufferSize, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo + )); + + //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel + //just for the tests, so create a new consumer from the properties + _fifoConsumer = new SqsMessageConsumer(awsConnection, fifoChannel.Name.ToValidSQSQueueName(), fifoRoutingKey, + _bufferSize); + _fifoMessageProducer = new SqsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo, + Deduplication = true }); } - + [Fact] public async Task When_a_message_consumer_reads_multiple_messages() { var routingKey = new RoutingKey(_topicName); - + var messageOne = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, correlationId: Guid.NewGuid().ToString(), contentType: _contentType), new MessageBody("test content one") - ); - - var messageTwo= new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + ); + + var messageTwo = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, correlationId: Guid.NewGuid().ToString(), contentType: _contentType), new MessageBody("test content two") - ); - - var messageThree= new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + ); + + var messageThree = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, correlationId: Guid.NewGuid().ToString(), contentType: _contentType), new MessageBody("test content three") - ); - - var messageFour= new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + ); + + var messageFour = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, correlationId: Guid.NewGuid().ToString(), contentType: _contentType), new MessageBody("test content four") - ); - + ); + //send MESSAGE_COUNT messages _messageProducer.Send(messageOne); _messageProducer.Send(messageTwo); @@ -100,9 +135,9 @@ public async Task When_a_message_consumer_reads_multiple_messages() //retrieve messages var messages = _consumer.Receive(TimeSpan.FromMilliseconds(10000)); - + messages.Length.Should().BeLessOrEqualTo(outstandingMessageCount); - + //should not receive more than buffer in one hit messages.Length.Should().BeLessOrEqualTo(_bufferSize); @@ -110,27 +145,139 @@ public async Task When_a_message_consumer_reads_multiple_messages() foreach (var message in moreMessages) { messagesReceived.Add(message); - _consumer.Acknowledge(message); + _consumer.Acknowledge(message); } - + messagesReceivedCount = messagesReceived.Count; - + await Task.Delay(1000); + } while ((iteration <= 5) && (messagesReceivedCount < _messageCount)); - } while ((iteration <= 5) && (messagesReceivedCount < _messageCount)); - messagesReceivedCount.Should().Be(4); + } + + [Fact] + public async Task When_a_message_consumer_reads_multiple_messages_per_group() + { + var routingKey = new RoutingKey(_fifoTopicName); + var messageGroupIdOne = "123"; + var messageOne = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType, + partitionKey: messageGroupIdOne), + new MessageBody("test content one") + ); + + var messageTwo = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType, + partitionKey: messageGroupIdOne), + new MessageBody("test content two") + ); + + var messageThree = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType, + partitionKey: messageGroupIdOne), + new MessageBody("test content three") + ); + + var messageFour = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType, + partitionKey: messageGroupIdOne), + new MessageBody("test content four") + ); + var messageGroupIdTwo = "1234"; + var messageFive = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType, + partitionKey: messageGroupIdTwo), + new MessageBody("test content five") + ); + + var messageSix = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType, + partitionKey: messageGroupIdTwo), + new MessageBody("test content six") + ); + + var messageSeven = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType, + partitionKey: messageGroupIdTwo) + { + Bag = + { + [HeaderNames.DeduplicationId] = "123" + } + }, + new MessageBody("test content seven") + ); + + var messageEight = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType, + partitionKey: messageGroupIdTwo) + { + Bag = + { + [HeaderNames.DeduplicationId] = "123" + } + }, + new MessageBody("test content eight") + ); + + //send MESSAGE_COUNT messages + _fifoMessageProducer.Send(messageOne); + _fifoMessageProducer.Send(messageTwo); + _fifoMessageProducer.Send(messageThree); + _fifoMessageProducer.Send(messageFour); + _fifoMessageProducer.Send(messageFive); + _fifoMessageProducer.Send(messageSix); + _fifoMessageProducer.Send(messageSeven); + _fifoMessageProducer.Send(messageEight); + + int iteration = 0; + var messagesReceived = new List(); + var messagesReceivedCount = messagesReceived.Count; + do + { + iteration++; + + //retrieve messages + var messages = _fifoConsumer.Receive(TimeSpan.FromMilliseconds(10000)); + + // should not receive more number of message group + messages.Length.Should().BeLessOrEqualTo(2); + + var moreMessages = messages.Where(m => m.Header.MessageType == MessageType.MT_COMMAND); + foreach (var message in moreMessages) + { + messagesReceived.Add(message); + _fifoConsumer.Acknowledge(message); + } + + messagesReceivedCount = messagesReceived.Count; + + await Task.Delay(1000); + } while ((iteration <= 7) && (messagesReceivedCount < _messageCount)); + + + messagesReceivedCount.Should().Be(7); } - + public void Dispose() { //Clean up resources that we have created _channelFactory.DeleteTopic(); _channelFactory.DeleteQueue(); + + _fifoChannelFactory.DeleteTopic(); + _fifoChannelFactory.DeleteQueue(); } } } - - diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_assume.cs index 0ad39dc054..fdf16b805c 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_assume.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_assume.cs @@ -13,46 +13,54 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway { [Trait("Category", "AWS")] [Trait("Fragile", "CI")] - public class AWSAssumeInfrastructureTests : IDisposable - { private readonly Message _message; + public class AWSAssumeInfrastructureTests : IDisposable + { + private readonly Message _message; + private readonly MyCommand _myCommand; private readonly SqsMessageConsumer _consumer; private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; - private readonly MyCommand _myCommand; + + private readonly Message _fifoMessage; + private readonly SqsMessageConsumer _fifoConsumer; + private readonly SqsMessageProducer _fifoMessageProducer; + private readonly ChannelFactory _fifoChannelFactory; + public AWSAssumeInfrastructureTests() { - _myCommand = new MyCommand{Value = "Test"}; + _myCommand = new MyCommand { Value = "Test" }; string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - + SqsSubscription subscription = new( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, makeChannels: OnMissingChannel.Create ); - + _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - + //We need to do this manually in a test - will create the channel from subscriber parameters //This doesn't look that different from our create tests - this is because we create using the channel factory in //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) _channelFactory = new ChannelFactory(awsConnection); var channel = _channelFactory.CreateChannel(subscription); - + //Now change the subscription to validate, just check what we made subscription = new( name: new SubscriptionName(channelName), @@ -60,10 +68,52 @@ public AWSAssumeInfrastructureTests() routingKey: routingKey, makeChannels: OnMissingChannel.Assume ); - - _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Assume}); + + _messageProducer = new SqsMessageProducer(awsConnection, + new SnsPublication { MakeChannels = OnMissingChannel.Assume }); _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), routingKey); + + // Because fifo can modify the topic name we need to run the same test twice, one for standard queue and another to FIFO + var fifoChannelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string fifoTopicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var fifoRoutingKey = new RoutingKey(fifoTopicName); + + SqsSubscription fifoSubscription = new( + name: new SubscriptionName(fifoChannelName), + channelName: new ChannelName(fifoChannelName), + routingKey: fifoRoutingKey, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo + ); + + string fifoMessageGroupId = Guid.NewGuid().ToString(); + _fifoMessage = new Message( + new MessageHeader(_myCommand.Id, fifoRoutingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: fifoMessageGroupId), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _fifoChannelFactory = new ChannelFactory(awsConnection); + var fifoChannel = _fifoChannelFactory.CreateChannel(fifoSubscription); + + //Now change the subscription to validate, just check what we made + fifoSubscription = new( + name: new SubscriptionName(fifoChannelName), + channelName: fifoChannel.Name, + routingKey: fifoRoutingKey, + makeChannels: OnMissingChannel.Assume, + sqsType: SnsSqsType.Fifo + ); + + _fifoMessageProducer = new SqsMessageProducer(awsConnection, + new SnsPublication { MakeChannels = OnMissingChannel.Assume, SnsType = SnsSqsType.Fifo }); + + _fifoConsumer = + new SqsMessageConsumer(awsConnection, fifoChannel.Name.ToValidSQSQueueName(), fifoRoutingKey); } [Fact] @@ -71,9 +121,9 @@ public void When_infastructure_exists_can_assume() { //arrange _messageProducer.Send(_message); - + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); - + //Assert var message = messages.First(); message.Id.Should().Be(_myCommand.Id); @@ -81,15 +131,36 @@ public void When_infastructure_exists_can_assume() //clear the queue _consumer.Acknowledge(message); } - + + [Fact] + public void When_infastructure_exists_can_assume_for_fifo() + { + //arrange + _fifoMessageProducer.Send(_fifoMessage); + + var messages = _fifoConsumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _fifoConsumer.Acknowledge(message); + } + + public void Dispose() { _channelFactory.DeleteTopic(); _channelFactory.DeleteQueue(); _consumer.Dispose(); _messageProducer.Dispose(); + + + _fifoChannelFactory.DeleteTopic(); + _fifoChannelFactory.DeleteQueue(); + _fifoConsumer.Dispose(); + _fifoMessageProducer.Dispose(); } - - - } + } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify.cs index e4d1cc602d..98a5ad6659 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify.cs @@ -12,48 +12,54 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway { - [Trait("Category", "AWS")] + [Trait("Category", "AWS")] [Trait("Fragile", "CI")] - public class AWSValidateInfrastructureTests : IDisposable - { private readonly Message _message; + public class AWSValidateInfrastructureTests : IDisposable + { + private readonly Message _message; private readonly IAmAMessageConsumer _consumer; private readonly SqsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; + private readonly ChannelFactory _channelFactory; + + private readonly Message _fifoMessage; + private readonly IAmAMessageConsumer _fifoConsumer; + private readonly SqsMessageProducer _fifoMessageProducer; + private readonly ChannelFactory _fifoChannelFactory; public AWSValidateInfrastructureTests() { - _myCommand = new MyCommand{Value = "Test"}; + _myCommand = new MyCommand { Value = "Test" }; string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - + SqsSubscription subscription = new( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, makeChannels: OnMissingChannel.Create ); - + _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - + //We need to do this manually in a test - will create the channel from subscriber parameters //This doesn't look that different from our create tests - this is because we create using the channel factory in //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) _channelFactory = new ChannelFactory(awsConnection); var channel = _channelFactory.CreateChannel(subscription); - + //Now change the subscription to validate, just check what we made subscription = new( name: new SubscriptionName(channelName), @@ -62,18 +68,65 @@ public AWSValidateInfrastructureTests() findTopicBy: TopicFindBy.Name, makeChannels: OnMissingChannel.Validate ); - + _messageProducer = new SqsMessageProducer( - awsConnection, + awsConnection, new SnsPublication { FindTopicBy = TopicFindBy.Name, MakeChannels = OnMissingChannel.Validate, Topic = new RoutingKey(topicName) } - ); + ); _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + + // Because fifo can modify the topic name we need to run the same test twice, one for standard queue and another to FIFO + var fifoChannelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string fifoTopicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var fifoRoutingKey = new RoutingKey(fifoTopicName); + + SqsSubscription fifoSubscription = new( + name: new SubscriptionName(fifoChannelName), + channelName: new ChannelName(fifoChannelName), + routingKey: fifoRoutingKey, + makeChannels: OnMissingChannel.Create + ); + + var fifoMessageGroupId = Guid.NewGuid().ToString(); + _fifoMessage = new Message( + new MessageHeader(_myCommand.Id, fifoRoutingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: fifoMessageGroupId), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _fifoChannelFactory = new ChannelFactory(awsConnection); + var fifoChannel = _fifoChannelFactory.CreateChannel(fifoSubscription); + + //Now change the subscription to validate, just check what we made + fifoSubscription = new( + name: new SubscriptionName(fifoChannelName), + channelName: fifoChannel.Name, + routingKey: fifoRoutingKey, + findTopicBy: TopicFindBy.Name, + makeChannels: OnMissingChannel.Validate + ); + + _fifoMessageProducer = new SqsMessageProducer( + awsConnection, + new SnsPublication + { + FindTopicBy = TopicFindBy.Name, + MakeChannels = OnMissingChannel.Validate, + Topic = new RoutingKey(fifoTopicName), + SnsType = SnsSqsType.Fifo + } + ); + + _fifoConsumer = new SqsMessageConsumerFactory(awsConnection).Create(fifoSubscription); } [Fact] @@ -83,9 +136,9 @@ public async Task When_infrastructure_exists_can_verify() _messageProducer.Send(_message); await Task.Delay(1000); - + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); - + //Assert var message = messages.First(); message.Id.Should().Be(_myCommand.Id); @@ -93,13 +146,36 @@ public async Task When_infrastructure_exists_can_verify() //clear the queue _consumer.Acknowledge(message); } - + + [Fact] + public async Task When_infrastructure_exists_can_verify_for_fifo() + { + //arrange + _fifoMessageProducer.Send(_fifoMessage); + + await Task.Delay(1000); + + var messages = _fifoConsumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _fifoConsumer.Acknowledge(message); + } + public void Dispose() { _channelFactory.DeleteTopic(); _channelFactory.DeleteQueue(); _consumer.Dispose(); _messageProducer.Dispose(); + + _fifoChannelFactory.DeleteTopic(); + _fifoChannelFactory.DeleteQueue(); + _fifoConsumer.Dispose(); + _fifoMessageProducer.Dispose(); } - } + } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify_by_convention.cs index 22b003d0fe..1c81a7eee5 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify_by_convention.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify_by_convention.cs @@ -14,46 +14,53 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway { [Trait("Category", "AWS")] [Trait("Fragile", "CI")] - public class AWSValidateInfrastructureByConventionTests : IDisposable - { private readonly Message _message; + public class AWSValidateInfrastructureByConventionTests : IDisposable + { + private readonly Message _message; private readonly IAmAMessageConsumer _consumer; private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; + + private readonly Message _fifoMessage; + private readonly IAmAMessageConsumer _fifoConsumer; + private readonly SqsMessageProducer _fifoMessageProducer; + private readonly ChannelFactory _fifoChannelFactory; + private readonly MyCommand _myCommand; public AWSValidateInfrastructureByConventionTests() { - _myCommand = new MyCommand{Value = "Test"}; + _myCommand = new MyCommand { Value = "Test" }; string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - + SqsSubscription subscription = new( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, makeChannels: OnMissingChannel.Create ); - + _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - + //We need to do this manually in a test - will create the channel from subscriber parameters //This doesn't look that different from our create tests - this is because we create using the channel factory in //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) _channelFactory = new ChannelFactory(awsConnection); var channel = _channelFactory.CreateChannel(subscription); - + //Now change the subscription to validate, just check what we made - will make the SNS Arn to prevent ListTopics call subscription = new( name: new SubscriptionName(channelName), @@ -62,16 +69,60 @@ public AWSValidateInfrastructureByConventionTests() findTopicBy: TopicFindBy.Convention, makeChannels: OnMissingChannel.Validate ); - + _messageProducer = new SqsMessageProducer( - awsConnection, - new SnsPublication{ - FindTopicBy = TopicFindBy.Convention, - MakeChannels = OnMissingChannel.Validate - } - ); + awsConnection, + new SnsPublication { FindTopicBy = TopicFindBy.Convention, MakeChannels = OnMissingChannel.Validate } + ); _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + + // Because fifo can modify the topic name we need to run the same test twice, one for standard queue and another to FIFO + var fifoChannelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string fifoTopicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var fifoRoutingKey = new RoutingKey(fifoTopicName); + + SqsSubscription fifoSubscription = new( + name: new SubscriptionName(fifoChannelName), + channelName: new ChannelName(fifoChannelName), + routingKey: fifoRoutingKey, + makeChannels: OnMissingChannel.Create + ); + + _fifoMessage = new Message( + new MessageHeader(_myCommand.Id, fifoRoutingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, + partitionKey: Guid.NewGuid().ToString()), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _fifoChannelFactory = new ChannelFactory(awsConnection); + var fifoChannel = _fifoChannelFactory.CreateChannel(fifoSubscription); + + //Now change the subscription to validate, just check what we made - will make the SNS Arn to prevent ListTopics call + fifoSubscription = new( + name: new SubscriptionName(fifoChannelName), + channelName: fifoChannel.Name, + routingKey: fifoRoutingKey, + findTopicBy: TopicFindBy.Convention, + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo + ); + + _messageProducer = new SqsMessageProducer( + awsConnection, + new SnsPublication + { + FindTopicBy = TopicFindBy.Convention, + MakeChannels = OnMissingChannel.Validate, + SnsType = SnsSqsType.Fifo + } + ); + + _fifoConsumer = new SqsMessageConsumerFactory(awsConnection).Create(fifoSubscription); } [Fact] @@ -81,9 +132,9 @@ public async Task When_infrastructure_exists_can_verify() _messageProducer.Send(_message); await Task.Delay(1000); - + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); - + //Assert var message = messages.First(); message.Id.Should().Be(_myCommand.Id); @@ -91,15 +142,36 @@ public async Task When_infrastructure_exists_can_verify() //clear the queue _consumer.Acknowledge(message); } - + + [Fact] + public async Task When_infrastructure_exists_can_verify_for_fico() + { + //arrange + _fifoMessageProducer.Send(_fifoMessage); + + await Task.Delay(1000); + + var messages = _fifoConsumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _fifoConsumer.Acknowledge(message); + } + public void Dispose() { _channelFactory.DeleteTopic(); _channelFactory.DeleteQueue(); _consumer.Dispose(); _messageProducer.Dispose(); + + _fifoChannelFactory.DeleteTopic(); + _fifoChannelFactory.DeleteQueue(); + _fifoConsumer.Dispose(); + _fifoMessageProducer.Dispose(); } - - - } + } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_posting_a_message_via_the_messaging_gateway.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_posting_a_message_via_the_messaging_gateway.cs index 7e103c136a..f8643af2de 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_posting_a_message_via_the_messaging_gateway.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_posting_a_message_via_the_messaging_gateway.cs @@ -19,44 +19,93 @@ public class SqsMessageProducerSendTests : IDisposable private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; + private readonly string _topicName; + + private readonly Message _fifoMessage; + private readonly IAmAChannel _fifoChannel; + private readonly SqsMessageProducer _fifoMessageProducer; + private readonly ChannelFactory _fifoChannelFactory; + private readonly string _fifoTopicName; + private readonly string _fifoMessageGroupId; + private readonly string _fifoDeduplicationId; + private readonly string _correlationId; private readonly string _replyTo; private readonly string _contentType; - private readonly string _topicName; public SqsMessageProducerSendTests() { - _myCommand = new MyCommand{Value = "Test"}; + _myCommand = new MyCommand { Value = "Test" }; _correlationId = Guid.NewGuid().ToString(); _replyTo = "http:\\queueUrl"; _contentType = "text\\plain"; var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); _topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(_topicName); - + SqsSubscription subscription = new( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, rawMessageDelivery: false ); - + _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: _correlationId, replyTo: new RoutingKey(_replyTo), contentType: _contentType), - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - + _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateChannel(subscription); + + _messageProducer = new SqsMessageProducer(awsConnection, + new SnsPublication { Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create }); + + // FIFO + var fifoChannelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _fifoTopicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var fifoRoutingKey = new RoutingKey(_fifoTopicName); + + SqsSubscription fifoSubscription = new( + name: new SubscriptionName(fifoChannelName), + channelName: new ChannelName(channelName), + routingKey: fifoRoutingKey, + rawMessageDelivery: false, + sqsType: SnsSqsType.Fifo + ); - _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication{Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create}); - } + _fifoMessageGroupId = Guid.NewGuid().ToString(); + _fifoDeduplicationId = Guid.NewGuid().ToString(); + _fifoMessage = new Message( + new MessageHeader(_myCommand.Id, fifoRoutingKey, MessageType.MT_COMMAND, correlationId: _correlationId, + replyTo: new RoutingKey(_replyTo), contentType: _contentType, + partitionKey: _fifoMessageGroupId) + { + Bag = + { + [HeaderNames.DeduplicationId] = _fifoDeduplicationId + } + }, + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + _fifoChannelFactory = new ChannelFactory(awsConnection); + _fifoChannel = _fifoChannelFactory.CreateChannel(fifoSubscription); + _fifoMessageProducer = new SqsMessageProducer(awsConnection, + new SnsPublication + { + Topic = new RoutingKey(_fifoTopicName), + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo, + Deduplication = true + }); + } [Theory] @@ -78,9 +127,9 @@ public async Task When_posting_a_message_via_the_producer(string subject, bool s } await Task.Delay(1000); - + var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - + //clear the queue _channel.Acknowledge(message); @@ -103,17 +152,68 @@ public async Task When_posting_a_message_via_the_producer(string subject, bool s message.Body.Value.Should().Be(_message.Body.Value); } + [Theory] + [InlineData("test subject", true)] + [InlineData(null, true)] + [InlineData("test subject", false)] + [InlineData(null, false)] + public async Task When_posting_a_message_via_the_producer_for_fifo(string subject, bool sendAsync) + { + //arrange + _fifoMessage.Header.Subject = subject; + if (sendAsync) + { + await _fifoMessageProducer.SendAsync(_fifoMessage); + } + else + { + _fifoMessageProducer.Send(_fifoMessage); + } + + await Task.Delay(1000); + + var message = _fifoChannel.Receive(TimeSpan.FromMilliseconds(5000)); + + //clear the queue + _fifoChannel.Acknowledge(message); + + //should_send_the_message_to_aws_sqs + message.Header.MessageType.Should().Be(MessageType.MT_COMMAND); + + message.Id.Should().Be(_myCommand.Id); + message.Redelivered.Should().BeFalse(); + message.Header.MessageId.Should().Be(_myCommand.Id); + message.Header.Topic.Value.Should().Contain(_fifoTopicName); + message.Header.CorrelationId.Should().Be(_correlationId); + message.Header.ReplyTo.Should().Be(_replyTo); + message.Header.ContentType.Should().Be(_contentType); + message.Header.HandledCount.Should().Be(0); + message.Header.Subject.Should().Be(subject); + //allow for clock drift in the following test, more important to have a contemporary timestamp than anything + message.Header.TimeStamp.Should().BeAfter(RoundToSeconds(DateTime.UtcNow.AddMinutes(-1))); + message.Header.Delayed.Should().Be(TimeSpan.Zero); + //{"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} + message.Body.Value.Should().Be(_message.Body.Value); + message.Header.PartitionKey.Should().Be(_fifoMessageGroupId); + message.Header.Bag.Should().ContainKey(HeaderNames.DeduplicationId); + message.Header.Bag[HeaderNames.DeduplicationId].Should().Be(_fifoDeduplicationId); + } + + public void Dispose() { _channelFactory?.DeleteTopic(); _channelFactory?.DeleteQueue(); _messageProducer?.Dispose(); + + _fifoChannelFactory?.DeleteTopic(); + _fifoChannelFactory?.DeleteQueue(); + _fifoMessageProducer?.Dispose(); } - + private static DateTime RoundToSeconds(DateTime dateTime) { return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind); } - } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_topic_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_topic_missing_verify_throws.cs index 8d663b20ed..4934a31dab 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_topic_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_topic_missing_verify_throws.cs @@ -7,8 +7,8 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway { - [Trait("Category", "AWS")] - public class AWSValidateMissingTopicTests + [Trait("Category", "AWS")] + public class AWSValidateMissingTopicTests { private readonly AWSMessagingGatewayConnection _awsConnection; private readonly RoutingKey _routingKey; @@ -17,27 +17,30 @@ public AWSValidateMissingTopicTests() { string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); _routingKey = new RoutingKey(topicName); - + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); _awsConnection = new AWSMessagingGatewayConnection(credentials, region); //Because we don't use channel factory to create the infrastructure -it won't exist - } + } - [Fact] - public void When_topic_missing_verify_throws() + [Theory] + [InlineData(SnsSqsType.Standard, null)] + [InlineData(SnsSqsType.Fifo, "123")] + public void When_topic_missing_verify_throws(SnsSqsType type, string partitionKey) { //arrange - var producer = new SqsMessageProducer(_awsConnection, + var producer = new SqsMessageProducer(_awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Validate + MakeChannels = OnMissingChannel.Validate, + SnsType = type }); - + //act && assert Assert.Throws(() => producer.Send(new Message( - new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text"), + new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text") { PartitionKey = partitionKey}, new MessageBody("Test")))); } - } + } } From 582d57c1af653eb02a7c7c3e49a060a92f071b17 Mon Sep 17 00:00:00 2001 From: Rafael Lillo Date: Sun, 22 Dec 2024 17:12:57 +0000 Subject: [PATCH 02/18] GH-1294 fixes build --- .../AWSMessagingGateway.cs | 57 +++++++++++++------ .../ChannelFactory.cs | 16 +++++- .../HeaderNames.cs | 2 + 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs index 27f6624254..e2ce531824 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2022 Ian Cooper @@ -25,6 +26,7 @@ THE SOFTWARE. */ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Amazon.Runtime.Internal.Transform; using Amazon.SimpleNotificationService; using Amazon.SimpleNotificationService.Model; using Microsoft.Extensions.Logging; @@ -46,43 +48,59 @@ public AWSMessagingGateway(AWSMessagingGatewayConnection awsConnection) _awsClientFactory = new AWSClientFactory(awsConnection); } - protected async Task EnsureTopicAsync(RoutingKey topic, SnsAttributes attributes, TopicFindBy topicFindBy, OnMissingChannel makeTopic) + protected async Task EnsureTopicAsync(RoutingKey topic, SnsAttributes attributes, + TopicFindBy topicFindBy, OnMissingChannel makeTopic, SnsSqsType snsSqsType, bool deduplication) { //on validate or assume, turn a routing key into a topicARN - if ((makeTopic == OnMissingChannel.Assume) || (makeTopic == OnMissingChannel.Validate)) - await ValidateTopicAsync(topic, topicFindBy, makeTopic); - else if (makeTopic == OnMissingChannel.Create) CreateTopic(topic, attributes); + if ((makeTopic == OnMissingChannel.Assume) || (makeTopic == OnMissingChannel.Validate)) + await ValidateTopicAsync(topic, topicFindBy, makeTopic, snsSqsType); + else if (makeTopic == OnMissingChannel.Create) CreateTopic(topic, attributes, snsSqsType, deduplication); return ChannelTopicArn; } - private void CreateTopic(RoutingKey topicName, SnsAttributes snsAttributes) + private void CreateTopic(RoutingKey topicName, SnsAttributes snsAttributes, SnsSqsType snsSqsType, bool deduplication) { using var snsClient = _awsClientFactory.CreateSnsClient(); var attributes = new Dictionary(); if (snsAttributes != null) { - if (!string.IsNullOrEmpty(snsAttributes.DeliveryPolicy)) attributes.Add("DeliveryPolicy", snsAttributes.DeliveryPolicy); + if (!string.IsNullOrEmpty(snsAttributes.DeliveryPolicy)) + attributes.Add("DeliveryPolicy", snsAttributes.DeliveryPolicy); if (!string.IsNullOrEmpty(snsAttributes.Policy)) attributes.Add("Policy", snsAttributes.Policy); } - var createTopicRequest = new CreateTopicRequest(topicName) + string name = topicName; + if (snsSqsType == SnsSqsType.Fifo) { - Attributes = attributes, - Tags = new List {new Tag {Key = "Source", Value = "Brighter"}} - }; + name += ".fifo"; + attributes.Add("FifoTopic", "true"); + if (deduplication) + { + attributes.Add("ContentBasedDeduplication", "true"); + } + } + + var createTopicRequest = new CreateTopicRequest(name) + { + Attributes = attributes, Tags = new List { new Tag { Key = "Source", Value = "Brighter" } } + }; + + //create topic is idempotent, so safe to call even if topic already exists var createTopic = snsClient.CreateTopicAsync(createTopicRequest).Result; - + if (!string.IsNullOrEmpty(createTopic.TopicArn)) ChannelTopicArn = createTopic.TopicArn; else - throw new InvalidOperationException($"Could not create Topic topic: {topicName} on {_awsConnection.Region}"); + throw new InvalidOperationException( + $"Could not create Topic topic: {name} on {_awsConnection.Region}"); } - private async Task ValidateTopicAsync(RoutingKey topic, TopicFindBy findTopicBy, OnMissingChannel onMissingChannel) + private async Task ValidateTopicAsync(RoutingKey topic, TopicFindBy findTopicBy, + OnMissingChannel onMissingChannel, SnsSqsType snsSqsType) { - IValidateTopic topicValidationStrategy = GetTopicValidationStrategy(findTopicBy); + IValidateTopic topicValidationStrategy = GetTopicValidationStrategy(findTopicBy, snsSqsType); (bool exists, string topicArn) = await topicValidationStrategy.ValidateAsync(topic); if (exists) ChannelTopicArn = topicArn; @@ -91,16 +109,19 @@ private async Task ValidateTopicAsync(RoutingKey topic, TopicFindBy findTopicBy, $"Topic validation error: could not find topic {topic}. Did you want Brighter to create infrastructure?"); } - private IValidateTopic GetTopicValidationStrategy(TopicFindBy findTopicBy) + private IValidateTopic GetTopicValidationStrategy(TopicFindBy findTopicBy, SnsSqsType type) { switch (findTopicBy) { case TopicFindBy.Arn: - return new ValidateTopicByArn(_awsConnection.Credentials, _awsConnection.Region, _awsConnection.ClientConfigAction); + return new ValidateTopicByArn(_awsConnection.Credentials, _awsConnection.Region, + _awsConnection.ClientConfigAction); case TopicFindBy.Convention: - return new ValidateTopicByArnConvention(_awsConnection.Credentials, _awsConnection.Region, _awsConnection.ClientConfigAction); + return new ValidateTopicByArnConvention(_awsConnection.Credentials, _awsConnection.Region, + _awsConnection.ClientConfigAction, type); case TopicFindBy.Name: - return new ValidateTopicByName(_awsConnection.Credentials, _awsConnection.Region, _awsConnection.ClientConfigAction); + return new ValidateTopicByName(_awsConnection.Credentials, _awsConnection.Region, + _awsConnection.ClientConfigAction, type); default: throw new ConfigurationException("Unknown TopicFindBy used to determine how to read RoutingKey"); } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs index e267b850da..67125f5b1c 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs @@ -81,7 +81,7 @@ public IAmAChannel CreateChannel(Subscription subscription) SqsSubscription sqsSubscription = subscription as SqsSubscription; _subscription = sqsSubscription ?? throw new ConfigurationException("We expect an SqsSubscription or SqsSubscription as a parameter"); - EnsureTopicAsync(_subscription.RoutingKey, _subscription.SnsAttributes, _subscription.FindTopicBy, _subscription.MakeChannels).Wait(); + EnsureTopicAsync(_subscription.RoutingKey, _subscription.SnsAttributes, _subscription.FindTopicBy, _subscription.MakeChannels, _subscription.SqsType, false).Wait(); EnsureQueue(); return new Channel( @@ -164,7 +164,19 @@ private void CreateQueue(AmazonSQSClient sqsClient) } } - var request = new CreateQueueRequest(_subscription.ChannelName.Value) + + var queueName = _subscription.ChannelName.Value; + if (_subscription.SqsType == SnsSqsType.Fifo) + { + if (!queueName.EndsWith(".fifo")) + { + queueName += ".fifo"; + } + + attributes.Add(QueueAttributeName.FifoQueue, "true"); + } + + var request = new CreateQueueRequest(queueName) { Attributes = attributes, Tags = tags diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/HeaderNames.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/HeaderNames.cs index 851d3cd701..71488f567a 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/HeaderNames.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/HeaderNames.cs @@ -34,5 +34,7 @@ public static class HeaderNames public static readonly string Timestamp = "timestamp"; public static readonly string ReplyTo = "reply-to"; public static string Bag = "bag"; + public const string MessageGroupId = "MessageGroupId"; + public const string DeduplicationId = "MessageDeduplicationId"; } } From c9e30332dbaba055e8263dc5eb5a191881314d91 Mon Sep 17 00:00:00 2001 From: Rafael Lillo Date: Sun, 29 Dec 2024 22:27:32 +0000 Subject: [PATCH 03/18] GH-1294 Add Fifo test --- docker-compose-localstack.yaml | 13 + .../AWSMessagingGateway.cs | 26 +- .../ChannelFactory.cs | 163 ++++--- .../DeduplicationScope.cs | 18 + .../SnsPublication.cs | 2 +- .../SqsSubscription.cs | 421 ++++++++++-------- ...essage_consumer_reads_multiple_messages.cs | 168 +++++++ .../When_customising_aws_client_config.cs | 97 ++++ .../When_infastructure_exists_can_assume.cs | 98 ++++ .../When_infastructure_exists_can_verify.cs | 111 +++++ ..._infastructure_exists_can_verify_by_arn.cs | 119 +++++ ...ructure_exists_can_verify_by_convention.cs | 108 +++++ ...ing_a_message_via_the_messaging_gateway.cs | 133 ++++++ .../Fifo/When_queues_missing_assume_throws.cs | 64 +++ .../Fifo/When_queues_missing_verify_throws.cs | 60 +++ .../When_raw_message_delivery_disabled.cs | 107 +++++ ..._a_message_through_gateway_with_requeue.cs | 90 ++++ .../Fifo/When_requeueing_a_message.cs | 84 ++++ .../When_requeueing_redrives_to_the_dlq.cs | 113 +++++ ...n_throwing_defer_action_respect_redrive.cs | 161 +++++++ .../Fifo/When_topic_missing_verify_throws.cs | 45 ++ ...essage_consumer_reads_multiple_messages.cs | 131 ++++++ .../When_customising_aws_client_config.cs | 2 +- .../When_infastructure_exists_can_assume.cs | 72 +-- .../When_infastructure_exists_can_verify.cs | 77 +--- ..._infastructure_exists_can_verify_by_arn.cs | 2 +- ...ructure_exists_can_verify_by_convention.cs | 78 +--- ...ing_a_message_via_the_messaging_gateway.cs | 103 +---- .../When_queues_missing_assume_throws.cs | 2 +- .../When_queues_missing_verify_throws.cs | 2 +- .../When_raw_message_delivery_disabled.cs | 2 +- ..._a_message_through_gateway_with_requeue.cs | 2 +- .../When_requeueing_a_message.cs | 2 +- .../When_requeueing_redrives_to_the_dlq.cs | 2 +- ...n_throwing_defer_action_respect_redrive.cs | 2 +- .../When_topic_missing_verify_throws.cs | 2 +- ...essage_consumer_reads_multiple_messages.cs | 283 ------------ 37 files changed, 2089 insertions(+), 876 deletions(-) create mode 100644 docker-compose-localstack.yaml create mode 100644 src/Paramore.Brighter.MessagingGateway.AWSSQS/DeduplicationScope.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_customising_aws_client_config.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages.cs rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{ => Standard}/When_customising_aws_client_config.cs (98%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{ => Standard}/When_infastructure_exists_can_assume.cs (52%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{ => Standard}/When_infastructure_exists_can_verify.cs (53%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{ => Standard}/When_infastructure_exists_can_verify_by_arn.cs (98%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{ => Standard}/When_infastructure_exists_can_verify_by_convention.cs (53%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{ => Standard}/When_posting_a_message_via_the_messaging_gateway.cs (50%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{ => Standard}/When_queues_missing_assume_throws.cs (97%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{ => Standard}/When_queues_missing_verify_throws.cs (97%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{ => Standard}/When_raw_message_delivery_disabled.cs (98%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{ => Standard}/When_rejecting_a_message_through_gateway_with_requeue.cs (98%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{ => Standard}/When_requeueing_a_message.cs (98%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{ => Standard}/When_requeueing_redrives_to_the_dlq.cs (98%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{ => Standard}/When_throwing_defer_action_respect_redrive.cs (99%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{ => Standard}/When_topic_missing_verify_throws.cs (96%) delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_a_message_consumer_reads_multiple_messages.cs diff --git a/docker-compose-localstack.yaml b/docker-compose-localstack.yaml new file mode 100644 index 0000000000..04b60b0578 --- /dev/null +++ b/docker-compose-localstack.yaml @@ -0,0 +1,13 @@ +version: '3' + +services: + localstack: + image: localstack/localstack + environment: + # LocalStack configuration: https://docs.localstack.cloud/references/configuration/ + - "SERVICES=s3,sqs,sns,dynamodb" + ports: + - "4566:4566" # LocalStack Gateway + - "4510-4559:4510-4559" # External services port range + volumes: + - "/var/run/docker.sock:/var/run/docker.sock" \ No newline at end of file diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs index e2ce531824..5afbef71b0 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs @@ -52,21 +52,35 @@ protected async Task EnsureTopicAsync(RoutingKey topic, SnsAttributes at TopicFindBy topicFindBy, OnMissingChannel makeTopic, SnsSqsType snsSqsType, bool deduplication) { //on validate or assume, turn a routing key into a topicARN - if ((makeTopic == OnMissingChannel.Assume) || (makeTopic == OnMissingChannel.Validate)) + if (makeTopic is OnMissingChannel.Assume or OnMissingChannel.Validate) + { await ValidateTopicAsync(topic, topicFindBy, makeTopic, snsSqsType); - else if (makeTopic == OnMissingChannel.Create) CreateTopic(topic, attributes, snsSqsType, deduplication); + } + else if (makeTopic == OnMissingChannel.Create) + { + CreateTopic(topic, attributes, snsSqsType, deduplication); + } return ChannelTopicArn; } - private void CreateTopic(RoutingKey topicName, SnsAttributes snsAttributes, SnsSqsType snsSqsType, bool deduplication) + private void CreateTopic(RoutingKey topicName, + SnsAttributes snsAttributes, + SnsSqsType snsSqsType, + bool deduplication) { using var snsClient = _awsClientFactory.CreateSnsClient(); var attributes = new Dictionary(); if (snsAttributes != null) { if (!string.IsNullOrEmpty(snsAttributes.DeliveryPolicy)) + { attributes.Add("DeliveryPolicy", snsAttributes.DeliveryPolicy); - if (!string.IsNullOrEmpty(snsAttributes.Policy)) attributes.Add("Policy", snsAttributes.Policy); + } + + if (!string.IsNullOrEmpty(snsAttributes.Policy)) + { + attributes.Add("Policy", snsAttributes.Policy); + } } string name = topicName; @@ -83,10 +97,10 @@ private void CreateTopic(RoutingKey topicName, SnsAttributes snsAttributes, SnsS var createTopicRequest = new CreateTopicRequest(name) { - Attributes = attributes, Tags = new List { new Tag { Key = "Source", Value = "Brighter" } } + Attributes = attributes, + Tags = [new Tag { Key = "Source", Value = "Brighter" }] }; - //create topic is idempotent, so safe to call even if topic already exists var createTopic = snsClient.CreateTopicAsync(createTopicRequest).Result; diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs index 67125f5b1c..51d6605543 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2022 Ian Cooper @@ -19,6 +20,7 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + #endregion using System; @@ -46,6 +48,7 @@ public class ChannelFactory : AWSMessagingGateway, IAmAChannelFactory private string _queueUrl; private string _dlqARN; private readonly RetryPolicy _retryPolicy; + /// /// Initializes a new instance of the class. /// @@ -58,12 +61,7 @@ public ChannelFactory( var delay = Backoff.LinearBackoff(TimeSpan.FromSeconds(2), retryCount: 3, factor: 2.0, fastFirst: true); _retryPolicy = Policy .Handle() - .WaitAndRetry(new[] - { - TimeSpan.FromSeconds(1), - TimeSpan.FromSeconds(5), - TimeSpan.FromSeconds(10) - }); + .WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) }); } /// @@ -79,13 +77,16 @@ public IAmAChannel CreateChannel(Subscription subscription) var channel = _retryPolicy.Execute(() => { SqsSubscription sqsSubscription = subscription as SqsSubscription; - _subscription = sqsSubscription ?? throw new ConfigurationException("We expect an SqsSubscription or SqsSubscription as a parameter"); + _subscription = sqsSubscription ?? + throw new ConfigurationException( + "We expect an SqsSubscription or SqsSubscription as a parameter"); - EnsureTopicAsync(_subscription.RoutingKey, _subscription.SnsAttributes, _subscription.FindTopicBy, _subscription.MakeChannels, _subscription.SqsType, false).Wait(); + EnsureTopicAsync(_subscription.RoutingKey, _subscription.SnsAttributes, _subscription.FindTopicBy, + _subscription.MakeChannels, _subscription.SqsType, false).Wait(); EnsureQueue(); return new Channel( - subscription.ChannelName.ToValidSQSQueueName(), + subscription.ChannelName.ToValidSQSQueueName(), subscription.RoutingKey.ToValidSNSTopicName(), _messageConsumerFactory.Create(subscription), subscription.BufferSize @@ -114,9 +115,8 @@ private void EnsureQueue() { CreateDLQ(sqsClient); } - + CreateQueue(sqsClient); - } else if (_subscription.MakeChannels == OnMissingChannel.Validate) { @@ -144,18 +144,28 @@ private void CreateQueue(AmazonSQSClient sqsClient) var attributes = new Dictionary(); if (_subscription.RedrivePolicy != null && _dlqARN != null) { - var policy = new {maxReceiveCount = _subscription.RedrivePolicy.MaxReceiveCount, deadLetterTargetArn = _dlqARN}; - attributes.Add("RedrivePolicy", JsonSerializer.Serialize(policy, JsonSerialisationOptions.Options)); + var policy = new + { + maxReceiveCount = _subscription.RedrivePolicy.MaxReceiveCount, deadLetterTargetArn = _dlqARN + }; + attributes.Add(QueueAttributeName.RedrivePolicy, + JsonSerializer.Serialize(policy, JsonSerialisationOptions.Options)); } - - attributes.Add("DelaySeconds", _subscription.DelaySeconds.ToString()); - attributes.Add("MessageRetentionPeriod", _subscription.MessageRetentionPeriod.ToString()); - if (_subscription.IAMPolicy != null )attributes.Add("Policy", _subscription.IAMPolicy); - attributes.Add("ReceiveMessageWaitTimeSeconds", _subscription.TimeOut.Seconds.ToString()); - attributes.Add("VisibilityTimeout", _subscription.LockTimeout.ToString()); - - var tags = new Dictionary(); - tags.Add("Source","Brighter"); + + attributes.Add(QueueAttributeName.DelaySeconds, _subscription.DelaySeconds.ToString()); + attributes.Add(QueueAttributeName.MessageRetentionPeriod, + _subscription.MessageRetentionPeriod.ToString()); + + if (_subscription.IAMPolicy != null) + { + attributes.Add(QueueAttributeName.Policy, _subscription.IAMPolicy); + } + + attributes.Add(QueueAttributeName.ReceiveMessageWaitTimeSeconds, + _subscription.TimeOut.Seconds.ToString()); + attributes.Add(QueueAttributeName.VisibilityTimeout, _subscription.LockTimeout.ToString()); + + var tags = new Dictionary { ["Source"] = "Brighter" }; if (_subscription.Tags != null) { foreach (var tag in _subscription.Tags) @@ -164,7 +174,6 @@ private void CreateQueue(AmazonSQSClient sqsClient) } } - var queueName = _subscription.ChannelName.Value; if (_subscription.SqsType == SnsSqsType.Fifo) { @@ -172,27 +181,41 @@ private void CreateQueue(AmazonSQSClient sqsClient) { queueName += ".fifo"; } - + attributes.Add(QueueAttributeName.FifoQueue, "true"); + + if (_subscription.ContentBasedDeduplication) + { + attributes.Add(QueueAttributeName.ContentBasedDeduplication, "true"); + } + + if (_subscription.DeduplicationScope != null && _subscription.FifoThroughputLimit != null) + { + attributes.Add(QueueAttributeName.FifoThroughputLimit, + _subscription.FifoThroughputLimit.ToString()); + attributes.Add(QueueAttributeName.DeduplicationScope, _subscription.DeduplicationScope switch + { + DeduplicationScope.MessageGroup => "messageGroup", + _ => "queue" + }); + } } - - var request = new CreateQueueRequest(queueName) - { - Attributes = attributes, - Tags = tags - }; + + var request = new CreateQueueRequest(queueName) { Attributes = attributes, Tags = tags }; var response = sqsClient.CreateQueueAsync(request).GetAwaiter().GetResult(); _queueUrl = response.QueueUrl; if (!string.IsNullOrEmpty(_queueUrl)) { s_logger.LogDebug("Queue created: {URL}", _queueUrl); - using var snsClient = new AmazonSimpleNotificationServiceClient(_awsConnection.Credentials, _awsConnection.Region); + using var snsClient = + new AmazonSimpleNotificationServiceClient(_awsConnection.Credentials, _awsConnection.Region); CheckSubscription(_subscription.MakeChannels, sqsClient, snsClient); } else { - throw new InvalidOperationException($"Could not create queue: {_subscription.ChannelName.Value} subscribed to {ChannelTopicArn} on {_awsConnection.Region}"); + throw new InvalidOperationException( + $"Could not create queue: {_subscription.ChannelName.Value} subscribed to {ChannelTopicArn} on {_awsConnection.Region}"); } } catch (QueueDeletedRecentlyException ex) @@ -200,14 +223,18 @@ private void CreateQueue(AmazonSQSClient sqsClient) //QueueDeletedRecentlyException - wait 30 seconds then retry //Although timeout is 60s, we could be partway through that, so apply Copernican Principle //and assume we are halfway through - var error = $"Could not create queue {_subscription.ChannelName.Value} because {ex.Message} waiting 60s to retry"; - s_logger.LogError(ex, "Could not create queue {ChannelName} because {ErrorMessage} waiting 60s to retry", _subscription.ChannelName.Value, ex.Message); + var error = + $"Could not create queue {_subscription.ChannelName.Value} because {ex.Message} waiting 60s to retry"; + s_logger.LogError(ex, + "Could not create queue {ChannelName} because {ErrorMessage} waiting 60s to retry", + _subscription.ChannelName.Value, ex.Message); Thread.Sleep(TimeSpan.FromSeconds(30)); throw new ChannelFailureException(error, ex); } catch (AmazonSQSException ex) { - var error = $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {_awsConnection.Region.DisplayName} because {ex.Message}"; + var error = + $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {_awsConnection.Region.DisplayName} because {ex.Message}"; s_logger.LogError(ex, "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", _queueUrl, _subscription.RoutingKey.Value, _awsConnection.Region.DisplayName, ex.Message); @@ -215,7 +242,8 @@ private void CreateQueue(AmazonSQSClient sqsClient) } catch (HttpErrorResponseException ex) { - var error = $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {_awsConnection.Region.DisplayName} because {ex.Message}"; + var error = + $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {_awsConnection.Region.DisplayName} because {ex.Message}"; s_logger.LogError(ex, "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", _queueUrl, _subscription.RoutingKey.Value, _awsConnection.Region.DisplayName, ex.Message); @@ -239,24 +267,28 @@ private void CreateDLQ(AmazonSQSClient sqsClient) var attributesRequest = new GetQueueAttributesRequest { QueueUrl = queueUrl, - AttributeNames = new List {"QueueArn"} + AttributeNames = ["QueueArn"] }; - var attributesResponse = sqsClient.GetQueueAttributesAsync(attributesRequest).GetAwaiter().GetResult(); + var attributesResponse = + sqsClient.GetQueueAttributesAsync(attributesRequest).GetAwaiter().GetResult(); if (attributesResponse.HttpStatusCode != HttpStatusCode.OK) - throw new InvalidOperationException($"Could not find ARN of DLQ, status: {attributesResponse.HttpStatusCode}"); + throw new InvalidOperationException( + $"Could not find ARN of DLQ, status: {attributesResponse.HttpStatusCode}"); _dlqARN = attributesResponse.QueueARN; } - else - throw new InvalidOperationException($"Could not find create DLQ, status: {createDeadLetterQueueResponse.HttpStatusCode}"); + else + throw new InvalidOperationException( + $"Could not find create DLQ, status: {createDeadLetterQueueResponse.HttpStatusCode}"); } catch (QueueDeletedRecentlyException ex) { //QueueDeletedRecentlyException - wait 30 seconds then retry //Although timeout is 60s, we could be partway through that, so apply Copernican Principle //and assume we are halfway through - var error = $"Could not create queue {_subscription.ChannelName.Value} because {ex.Message} waiting 60s to retry"; + var error = + $"Could not create queue {_subscription.ChannelName.Value} because {ex.Message} waiting 60s to retry"; s_logger.LogError(ex, "Could not create queue {ChannelName} because {ErrorMessage} waiting 60s to retry", _subscription.ChannelName.Value, ex.Message); @@ -265,7 +297,8 @@ private void CreateDLQ(AmazonSQSClient sqsClient) } catch (AmazonSQSException ex) { - var error = $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {_awsConnection.Region.DisplayName} because {ex.Message}"; + var error = + $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {_awsConnection.Region.DisplayName} because {ex.Message}"; s_logger.LogError(ex, "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", _queueUrl, _subscription.RoutingKey.Value, _awsConnection.Region.DisplayName, ex.Message); @@ -273,14 +306,17 @@ private void CreateDLQ(AmazonSQSClient sqsClient) } catch (HttpErrorResponseException ex) { - var error = $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {_awsConnection.Region.DisplayName} because {ex.Message}"; - s_logger.LogError(ex, "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", + var error = + $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {_awsConnection.Region.DisplayName} because {ex.Message}"; + s_logger.LogError(ex, + "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", _queueUrl, _subscription.RoutingKey.Value, _awsConnection.Region.DisplayName, ex.Message); throw new InvalidOperationException(error, ex); } } - private void CheckSubscription(OnMissingChannel makeSubscriptions, AmazonSQSClient sqsClient, AmazonSimpleNotificationServiceClient snsClient) + private void CheckSubscription(OnMissingChannel makeSubscriptions, AmazonSQSClient sqsClient, + AmazonSimpleNotificationServiceClient snsClient) { if (makeSubscriptions == OnMissingChannel.Assume) return; @@ -289,7 +325,8 @@ private void CheckSubscription(OnMissingChannel makeSubscriptions, AmazonSQSClie { if (makeSubscriptions == OnMissingChannel.Validate) { - throw new BrokerUnreachableException($"Subscription validation error: could not find subscription for {_queueUrl}"); + throw new BrokerUnreachableException( + $"Subscription validation error: could not find subscription for {_queueUrl}"); } else if (makeSubscriptions == OnMissingChannel.Create) { @@ -311,7 +348,8 @@ private void SubscribeToTopic(AmazonSQSClient sqsClient, AmazonSimpleNotificatio .Result; if (response.HttpStatusCode != HttpStatusCode.OK) { - throw new InvalidOperationException("Unable to set subscription attribute for raw message delivery"); + throw new InvalidOperationException( + "Unable to set subscription attribute for raw message delivery"); } } else @@ -365,8 +403,11 @@ private bool SubscriptionExists(AmazonSQSClient sqsClient, AmazonSimpleNotificat ListSubscriptionsByTopicResponse response; do { - response = snsClient.ListSubscriptionsByTopicAsync(new ListSubscriptionsByTopicRequest {TopicArn = ChannelTopicArn}).GetAwaiter().GetResult(); - exists = response.Subscriptions.Any(sub => (sub.Protocol.ToLower() == "sqs") && (sub.Endpoint == queueArn)); + response = snsClient + .ListSubscriptionsByTopicAsync(new ListSubscriptionsByTopicRequest { TopicArn = ChannelTopicArn }) + .GetAwaiter().GetResult(); + exists = response.Subscriptions.Any(sub => + (sub.Protocol.ToLower() == "sqs") && (sub.Endpoint == queueArn)); } while (!exists && response.NextToken != null); return exists; @@ -379,7 +420,8 @@ public void DeleteQueue() using var sqsClient = new AmazonSQSClient(_awsConnection.Credentials, _awsConnection.Region); //Does the queue exist - this is an HTTP call, we should cache the results for a period of time - (bool exists, string name) queueExists = QueueExists(sqsClient, _subscription.ChannelName.ToValidSQSQueueName()); + (bool exists, string name) queueExists = + QueueExists(sqsClient, _subscription.ChannelName.ToValidSQSQueueName()); if (queueExists.exists) { @@ -400,8 +442,10 @@ public void DeleteTopic() if (_subscription == null) return; - using var snsClient = new AmazonSimpleNotificationServiceClient(_awsConnection.Credentials, _awsConnection.Region); - (bool exists, string topicArn) = new ValidateTopicByArn(snsClient).ValidateAsync(ChannelTopicArn).GetAwaiter().GetResult(); + using var snsClient = + new AmazonSimpleNotificationServiceClient(_awsConnection.Credentials, _awsConnection.Region); + (bool exists, string topicArn) = new ValidateTopicByArn(snsClient).ValidateAsync(ChannelTopicArn) + .GetAwaiter().GetResult(); if (exists) { try @@ -427,7 +471,7 @@ private void DeleteTopic(AmazonSimpleNotificationServiceClient snsClient) private string GetQueueARNForChannel(AmazonSQSClient sqsClient) { var result = sqsClient.GetQueueAttributesAsync( - new GetQueueAttributesRequest {QueueUrl = _queueUrl, AttributeNames = new List {"QueueArn"}} + new GetQueueAttributesRequest { QueueUrl = _queueUrl, AttributeNames = new List { "QueueArn" } } ).GetAwaiter().GetResult(); if (result.HttpStatusCode == HttpStatusCode.OK) @@ -443,13 +487,18 @@ private void UnsubscribeFromTopic(AmazonSimpleNotificationServiceClient snsClien ListSubscriptionsByTopicResponse response; do { - response = snsClient.ListSubscriptionsByTopicAsync(new ListSubscriptionsByTopicRequest {TopicArn = ChannelTopicArn}).GetAwaiter().GetResult(); + response = snsClient + .ListSubscriptionsByTopicAsync(new ListSubscriptionsByTopicRequest { TopicArn = ChannelTopicArn }) + .GetAwaiter().GetResult(); foreach (var sub in response.Subscriptions) { - var unsubscribe = snsClient.UnsubscribeAsync(new UnsubscribeRequest {SubscriptionArn = sub.SubscriptionArn}).GetAwaiter().GetResult(); + var unsubscribe = snsClient + .UnsubscribeAsync(new UnsubscribeRequest { SubscriptionArn = sub.SubscriptionArn }).GetAwaiter() + .GetResult(); if (unsubscribe.HttpStatusCode != HttpStatusCode.OK) { - s_logger.LogError("Error unsubscribing from {TopicResourceName} for sub {ChannelResourceName}", ChannelTopicArn, sub.SubscriptionArn); + s_logger.LogError("Error unsubscribing from {TopicResourceName} for sub {ChannelResourceName}", + ChannelTopicArn, sub.SubscriptionArn); } } } while (response.NextToken != null); diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/DeduplicationScope.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/DeduplicationScope.cs new file mode 100644 index 0000000000..a55c164431 --- /dev/null +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/DeduplicationScope.cs @@ -0,0 +1,18 @@ +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +/// +/// For High throughput for FIFO queues +/// See: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/high-throughput-fifo.html +/// +public enum DeduplicationScope +{ + /// + /// The throughput configuration to be applied to message group + /// + MessageGroup, + + /// + /// The throughput configuration to be applied to the queue + /// + Queue +} diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsPublication.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsPublication.cs index d3e591c9a6..e51aa8d005 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsPublication.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsPublication.cs @@ -53,7 +53,7 @@ public class SnsPublication : Publication public SnsSqsType SnsType { get; set; } = SnsSqsType.Standard; /// - /// Amazon SNS FIFO topics and Amazon SQS FIFO queues support message deduplication, which provides + /// Amazon SNS FIFO topics support message deduplication, which provides /// exactly-once message delivery and processing as long as the following conditions are met: /// /// diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs index 87f24c39fe..e72fd68da7 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs @@ -26,207 +26,242 @@ THE SOFTWARE. */ using System; using System.Collections.Generic; -namespace Paramore.Brighter.MessagingGateway.AWSSQS +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +/// +/// A subscription for an SQS Consumer. +/// We will create infrastructure on the basis of Make Channels +/// Create = topic using routing key name, queue using channel name +/// Validate = look for topic using routing key name, queue using channel name +/// Assume = Assume Routing Key is Topic ARN, queue exists via channel name +/// +public class SqsSubscription : Subscription { /// - /// A subscription for an SQS Consumer. - /// We will create infrastructure on the basis of Make Channels - /// Create = topic using routing key name, queue using channel name - /// Validate = look for topic using routing key name, queue using channel name - /// Assume = Assume Routing Key is Topic ARN, queue exists via channel name + /// This governs how long, in seconds, a 'lock' is held on a message for one consumer + /// to process. SQS calls this the VisibilityTimeout + /// + public int LockTimeout { get; } + + /// + /// The length of time, in seconds, for which the delivery of all messages in the queue is delayed. + /// + public int DelaySeconds { get; } + + /// + /// The length of time, in seconds, for which Amazon SQS retains a message + /// + public int MessageRetentionPeriod { get; } + + /// + /// Indicates how we should treat the routing key + /// TopicFindBy.Arn -> the routing key is an Arn + /// TopicFindBy.Convention -> The routing key is a name, but use convention to make an Arn for this account + /// TopicFindBy.Name -> Treat the routing key as a name & use ListTopics to find it (rate limited 30/s) + /// + public TopicFindBy FindTopicBy { get; } + + /// + /// The JSON serialization of the queue's access control policy. + /// + public string IAMPolicy { get; } + + /// + /// Indicate that the Raw Message Delivery setting is enabled or disabled + /// + public bool RawMessageDelivery { get; } + + /// + /// The policy that controls when we send messages to a DLQ after too many requeue attempts + /// + public RedrivePolicy RedrivePolicy { get; } + + /// + /// The attributes of the topic. If TopicARN is set we will always assume that we do not + /// need to create or validate the SNS Topic + /// + public SnsAttributes SnsAttributes { get; } + + /// + /// A list of resource tags to use when creating the queue /// - public class SqsSubscription : Subscription + public Dictionary Tags { get; } + + /// + /// The AWS SQS type. + /// + public SnsSqsType SqsType { get; } + + /// + /// Enables or disable content-based deduplication, for Fifo queues. + /// + public bool ContentBasedDeduplication { get; } + + /// + /// Specifies whether message deduplication occurs at the message group or queue level. + /// This configuration is used for high throughput for FIFO queues configuration + /// + public DeduplicationScope? DeduplicationScope { get; } + + /// + /// Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group + /// This configuration is used for high throughput for FIFO queues configuration + /// + public int? FifoThroughputLimit { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Type of the data. + /// The name. Defaults to the data type's full name. + /// The channel name. Defaults to the data type's full name. + /// The routing key. Defaults to the data type's full name. + /// The no of threads reading this channel. + /// The number of messages to buffer at any one time, also the number of messages to retrieve at once. Min of 1 Max of 10 + /// The timeout in milliseconds. + /// The number of times you want to requeue a message before dropping it. + /// The number of milliseconds to delay the delivery of a requeue message for. + /// The number of unacceptable messages to handle, before stopping reading from the channel. + /// Is this channel read asynchronously + /// The channel factory to create channels for Consumer. + /// What is the visibility timeout for the queue + /// The length of time, in seconds, for which the delivery of all messages in the queue is delayed. + /// The length of time, in seconds, for which Amazon SQS retains a message + /// Is the Topic an Arn, should be treated as an Arn by convention, or a name + /// The queue's policy. A valid AWS policy. + /// The policy that controls when and where requeued messages are sent to the DLQ + /// The attributes of the Topic, either ARN if created, or attributes for creation + /// Resource tags to be added to the queue + /// Should we make channels if they don't exist, defaults to creating + /// The indication of Raw Message Delivery setting is enabled or disabled + /// How long to pause when a channel is empty in milliseconds + /// How long to pause when there is a channel failure in milliseconds + /// The SQS Type + /// Enables or disable content-based deduplication + /// Specifies whether message deduplication occurs at the message group or queue level + /// Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group + public SqsSubscription(Type dataType, + SubscriptionName name = null, + ChannelName channelName = null, + RoutingKey routingKey = null, + int bufferSize = 1, + int noOfPerformers = 1, + TimeSpan? timeOut = null, + int requeueCount = -1, + TimeSpan? requeueDelay = null, + int unacceptableMessageLimit = 0, + bool runAsync = false, + IAmAChannelFactory channelFactory = null, + int lockTimeout = 10, + int delaySeconds = 0, + int messageRetentionPeriod = 345600, + TopicFindBy findTopicBy = TopicFindBy.Name, + string iAmPolicy = null, + RedrivePolicy redrivePolicy = null, + SnsAttributes snsAttributes = null, + Dictionary tags = null, + OnMissingChannel makeChannels = OnMissingChannel.Create, + bool rawMessageDelivery = true, + int emptyChannelDelay = 500, + int channelFailureDelay = 1000, + SnsSqsType sqsType = SnsSqsType.Standard, + bool contentBasedDeduplication = false, + DeduplicationScope? deduplicationScope = null, + int? fifoThroughputLimit = null + ) + : base(dataType, name, channelName, routingKey, bufferSize, noOfPerformers, timeOut, requeueCount, + requeueDelay, unacceptableMessageLimit, runAsync, channelFactory, makeChannels, emptyChannelDelay, + channelFailureDelay) { - /// - /// This governs how long, in seconds, a 'lock' is held on a message for one consumer - /// to process. SQS calls this the VisibilityTimeout - /// - public int LockTimeout { get; } - - /// - /// The length of time, in seconds, for which the delivery of all messages in the queue is delayed. - /// - public int DelaySeconds { get; } - - /// - /// The length of time, in seconds, for which Amazon SQS retains a message - /// - public int MessageRetentionPeriod { get; } - - /// - /// Indicates how we should treat the routing key - /// TopicFindBy.Arn -> the routing key is an Arn - /// TopicFindBy.Convention -> The routing key is a name, but use convention to make an Arn for this account - /// TopicFindBy.Name -> Treat the routing key as a name & use ListTopics to find it (rate limited 30/s) - /// - public TopicFindBy FindTopicBy { get; } - - /// - /// The JSON serialization of the queue's access control policy. - /// - public string IAMPolicy { get; } - - /// - /// Indicate that the Raw Message Delivery setting is enabled or disabled - /// - public bool RawMessageDelivery { get; } - - /// - /// The policy that controls when we send messages to a DLQ after too many requeue attempts - /// - public RedrivePolicy RedrivePolicy { get; } - - /// - /// The attributes of the topic. If TopicARN is set we will always assume that we do not - /// need to create or validate the SNS Topic - /// - public SnsAttributes SnsAttributes { get; } - - /// - /// A list of resource tags to use when creating the queue - /// - public Dictionary Tags { get; } - - /// - /// The AWS SQS type. - /// - public SnsSqsType SqsType { get; } - - /// - /// Initializes a new instance of the class. - /// - /// Type of the data. - /// The name. Defaults to the data type's full name. - /// The channel name. Defaults to the data type's full name. - /// The routing key. Defaults to the data type's full name. - /// The no of threads reading this channel. - /// The number of messages to buffer at any one time, also the number of messages to retrieve at once. Min of 1 Max of 10 - /// The timeout in milliseconds. - /// The number of times you want to requeue a message before dropping it. - /// The number of milliseconds to delay the delivery of a requeue message for. - /// The number of unacceptable messages to handle, before stopping reading from the channel. - /// Is this channel read asynchronously - /// The channel factory to create channels for Consumer. - /// What is the visibility timeout for the queue - /// The length of time, in seconds, for which the delivery of all messages in the queue is delayed. - /// The length of time, in seconds, for which Amazon SQS retains a message - /// Is the Topic an Arn, should be treated as an Arn by convention, or a name - /// The queue's policy. A valid AWS policy. - /// The policy that controls when and where requeued messages are sent to the DLQ - /// The attributes of the Topic, either ARN if created, or attributes for creation - /// Resource tags to be added to the queue - /// Should we make channels if they don't exist, defaults to creating - /// The indication of Raw Message Delivery setting is enabled or disabled - /// How long to pause when a channel is empty in milliseconds - /// How long to pause when there is a channel failure in milliseconds - /// The SQS Type - public SqsSubscription(Type dataType, - SubscriptionName name = null, - ChannelName channelName = null, - RoutingKey routingKey = null, - int bufferSize = 1, - int noOfPerformers = 1, - TimeSpan? timeOut = null, - int requeueCount = -1, - TimeSpan? requeueDelay = null, - int unacceptableMessageLimit = 0, - bool runAsync = false, - IAmAChannelFactory channelFactory = null, - int lockTimeout = 10, - int delaySeconds = 0, - int messageRetentionPeriod = 345600, - TopicFindBy findTopicBy = TopicFindBy.Name, - string iAmPolicy = null, - RedrivePolicy redrivePolicy = null, - SnsAttributes snsAttributes = null, - Dictionary tags = null, - OnMissingChannel makeChannels = OnMissingChannel.Create, - bool rawMessageDelivery = true, - int emptyChannelDelay = 500, - int channelFailureDelay = 1000, - SnsSqsType sqsType = SnsSqsType.Standard - ) - : base(dataType, name, channelName, routingKey, bufferSize, noOfPerformers, timeOut, requeueCount, - requeueDelay, unacceptableMessageLimit, runAsync, channelFactory, makeChannels, emptyChannelDelay, channelFailureDelay) - { - LockTimeout = lockTimeout; - DelaySeconds = delaySeconds; - MessageRetentionPeriod = messageRetentionPeriod; - FindTopicBy = findTopicBy; - IAMPolicy = iAmPolicy; - RawMessageDelivery = rawMessageDelivery; - RedrivePolicy = redrivePolicy; - SnsAttributes = snsAttributes; - Tags = tags; - SqsType = sqsType; - } + LockTimeout = lockTimeout; + DelaySeconds = delaySeconds; + MessageRetentionPeriod = messageRetentionPeriod; + FindTopicBy = findTopicBy; + IAMPolicy = iAmPolicy; + RawMessageDelivery = rawMessageDelivery; + RedrivePolicy = redrivePolicy; + SnsAttributes = snsAttributes; + Tags = tags; + SqsType = sqsType; + ContentBasedDeduplication = contentBasedDeduplication; + DeduplicationScope = deduplicationScope; + FifoThroughputLimit = fifoThroughputLimit; } +} +/// +/// A subscription for an SQS Consumer. +/// We will create infrastructure on the basis of Make Channels +/// Create = topic using routing key name, queue using channel name +/// Validate = look for topic using routing key name, queue using channel name +/// Assume = Assume Routing Key is Topic ARN, queue exists via channel name +/// +public class SqsSubscription : SqsSubscription where T : IRequest +{ /// - /// A subscription for an SQS Consumer. - /// We will create infrastructure on the basis of Make Channels - /// Create = topic using routing key name, queue using channel name - /// Validate = look for topic using routing key name, queue using channel name - /// Assume = Assume Routing Key is Topic ARN, queue exists via channel name + /// Initializes a new instance of the class. /// - public class SqsSubscription : SqsSubscription where T : IRequest + /// The name. Defaults to the data type's full name. + /// The channel name. Defaults to the data type's full name. + /// The routing key. Defaults to the data type's full name. + /// The no of threads reading this channel. + /// The number of messages to buffer at any one time, also the number of messages to retrieve at once. Min of 1 Max of 10 + /// The timeout. Defaults to 300 milliseconds. + /// The number of times you want to requeue a message before dropping it. + /// The number of milliseconds to delay the delivery of a requeue message for. + /// The number of unacceptable messages to handle, before stopping reading from the channel. + /// Is this channel read asynchronously + /// The channel factory to create channels for Consumer. + /// What is the visibility timeout for the queue + /// The length of time, in seconds, for which the delivery of all messages in the queue is delayed. + /// The length of time, in seconds, for which Amazon SQS retains a message + /// Is the Topic an Arn, should be treated as an Arn by convention, or a name + /// The queue's policy. A valid AWS policy. + /// The policy that controls when and where requeued messages are sent to the DLQ + /// The attributes of the Topic, either ARN if created, or attributes for creation + /// Resource tags to be added to the queue + /// Should we make channels if they don't exist, defaults to creating + /// The indication of Raw Message Delivery setting is enabled or disabled + /// How long to pause when a channel is empty in milliseconds + /// How long to pause when there is a channel failure in milliseconds + /// The SQS Type + /// Enables or disable content-based deduplication + /// Specifies whether message deduplication occurs at the message group or queue level + /// Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group + public SqsSubscription(SubscriptionName name = null, + ChannelName channelName = null, + RoutingKey routingKey = null, + int bufferSize = 1, + int noOfPerformers = 1, + TimeSpan? timeOut = null, + int requeueCount = -1, + TimeSpan? requeueDelay = null, + int unacceptableMessageLimit = 0, + bool runAsync = false, + IAmAChannelFactory channelFactory = null, + int lockTimeout = 10, + int delaySeconds = 0, + int messageRetentionPeriod = 345600, + TopicFindBy findTopicBy = TopicFindBy.Name, + string iAmPolicy = null, + RedrivePolicy redrivePolicy = null, + SnsAttributes snsAttributes = null, + Dictionary tags = null, + OnMissingChannel makeChannels = OnMissingChannel.Create, + bool rawMessageDelivery = true, + int emptyChannelDelay = 500, + int channelFailureDelay = 1000, + SnsSqsType sqsType = SnsSqsType.Standard, + bool contentBasedDeduplication = false, + DeduplicationScope? deduplicationScope = null, + int? fifoThroughputLimit = null) + : base(typeof(T), name, channelName, routingKey, bufferSize, noOfPerformers, timeOut, requeueCount, + requeueDelay, + unacceptableMessageLimit, runAsync, channelFactory, lockTimeout, delaySeconds, messageRetentionPeriod, + findTopicBy, + iAmPolicy, redrivePolicy, snsAttributes, tags, makeChannels, rawMessageDelivery, emptyChannelDelay, + channelFailureDelay, + sqsType, contentBasedDeduplication, deduplicationScope, fifoThroughputLimit) { - /// - /// Initializes a new instance of the class. - /// - /// The name. Defaults to the data type's full name. - /// The channel name. Defaults to the data type's full name. - /// The routing key. Defaults to the data type's full name. - /// The no of threads reading this channel. - /// The number of messages to buffer at any one time, also the number of messages to retrieve at once. Min of 1 Max of 10 - /// The timeout. Defaults to 300 milliseconds. - /// The number of times you want to requeue a message before dropping it. - /// The number of milliseconds to delay the delivery of a requeue message for. - /// The number of unacceptable messages to handle, before stopping reading from the channel. - /// Is this channel read asynchronously - /// The channel factory to create channels for Consumer. - /// What is the visibility timeout for the queue - /// The length of time, in seconds, for which the delivery of all messages in the queue is delayed. - /// The length of time, in seconds, for which Amazon SQS retains a message - /// Is the Topic an Arn, should be treated as an Arn by convention, or a name - /// The queue's policy. A valid AWS policy. - /// The policy that controls when and where requeued messages are sent to the DLQ - /// The attributes of the Topic, either ARN if created, or attributes for creation - /// Resource tags to be added to the queue - /// Should we make channels if they don't exist, defaults to creating - /// The indication of Raw Message Delivery setting is enabled or disabled - /// How long to pause when a channel is empty in milliseconds - /// How long to pause when there is a channel failure in milliseconds - /// The SQS Type - public SqsSubscription(SubscriptionName name = null, - ChannelName channelName = null, - RoutingKey routingKey = null, - int bufferSize = 1, - int noOfPerformers = 1, - TimeSpan? timeOut = null, - int requeueCount = -1, - TimeSpan? requeueDelay = null, - int unacceptableMessageLimit = 0, - bool runAsync = false, - IAmAChannelFactory channelFactory = null, - int lockTimeout = 10, - int delaySeconds = 0, - int messageRetentionPeriod = 345600, - TopicFindBy findTopicBy = TopicFindBy.Name, - string iAmPolicy = null, - RedrivePolicy redrivePolicy = null, - SnsAttributes snsAttributes = null, - Dictionary tags = null, - OnMissingChannel makeChannels = OnMissingChannel.Create, - bool rawMessageDelivery = true, - int emptyChannelDelay = 500, - int channelFailureDelay = 1000, - SnsSqsType sqsType = SnsSqsType.Standard - ) - : base(typeof(T), name, channelName, routingKey, bufferSize, noOfPerformers, timeOut, requeueCount, requeueDelay, - unacceptableMessageLimit, runAsync, channelFactory, lockTimeout, delaySeconds, messageRetentionPeriod,findTopicBy, - iAmPolicy,redrivePolicy, snsAttributes, tags, makeChannels, rawMessageDelivery, emptyChannelDelay, channelFailureDelay, sqsType) - { - } } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs new file mode 100644 index 0000000000..ba961c5c25 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SQSBufferedConsumerTests : IDisposable +{ + private readonly SqsMessageProducer _messageProducer; + private readonly SqsMessageConsumer _consumer; + private readonly string _topicName; + private readonly ChannelFactory _channelFactory; + private const string _contentType = "text\\plain"; + private const int _bufferSize = 3; + private const int _messageCount = 4; + + public SQSBufferedConsumerTests() + { + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + + _channelFactory = new ChannelFactory(awsConnection); + + var channelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _topicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + + //we need the channel to create the queues and notifications + var routingKey = new RoutingKey(_topicName); + + var channel = _channelFactory.CreateChannel(new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + bufferSize: _bufferSize, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + contentBasedDeduplication: true + )); + + //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel + //just for the tests, so create a new consumer from the properties + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), routingKey, + _bufferSize); + _messageProducer = new SqsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo, Deduplication = true + }); + } + + [Fact] + public async Task When_a_message_consumer_reads_multiple_messages_per_group() + { + var routingKey = new RoutingKey(_topicName); + var messageGroupIdOne = "123"; + + var messageOne = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType, + partitionKey: messageGroupIdOne), + new MessageBody("test content one") + ); + + var messageTwo = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType, + partitionKey: messageGroupIdOne), + new MessageBody("test content two") + ); + + var messageThree = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType, + partitionKey: messageGroupIdOne), + new MessageBody("test content three") + ); + + var messageFour = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType, + partitionKey: messageGroupIdOne), + new MessageBody("test content four") + ); + var messageGroupIdTwo = "1234"; + var messageFive = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType, + partitionKey: messageGroupIdTwo), + new MessageBody("test content five") + ); + + var messageSix = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType, + partitionKey: messageGroupIdTwo), + new MessageBody("test content six") + ); + + var messageSeven = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType, + partitionKey: messageGroupIdTwo) { Bag = { [HeaderNames.DeduplicationId] = "123" } }, + new MessageBody("test content seven") + ); + + var messageEight = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType, + partitionKey: messageGroupIdTwo) { Bag = { [HeaderNames.DeduplicationId] = "123" } }, + new MessageBody("test content eight") + ); + + //send MESSAGE_COUNT messages + _messageProducer.Send(messageOne); + _messageProducer.Send(messageTwo); + _messageProducer.Send(messageThree); + _messageProducer.Send(messageFour); + _messageProducer.Send(messageFive); + _messageProducer.Send(messageSix); + _messageProducer.Send(messageSeven); + _messageProducer.Send(messageEight); + + int iteration = 0; + var messagesReceived = new List(); + var messagesReceivedCount = messagesReceived.Count; + do + { + iteration++; + + //retrieve messages + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(10000)); + + // should not receive more number of message group + messages.Length.Should().BeLessOrEqualTo(2); + + var moreMessages = messages.Where(m => m.Header.MessageType == MessageType.MT_COMMAND); + foreach (var message in moreMessages) + { + messagesReceived.Add(message); + _consumer.Acknowledge(message); + } + + messagesReceivedCount = messagesReceived.Count; + + await Task.Delay(1000); + } while ((iteration <= 7) && (messagesReceivedCount < _messageCount)); + + + messagesReceivedCount.Should().Be(7); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopic(); + _channelFactory.DeleteQueue(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_customising_aws_client_config.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_customising_aws_client_config.cs new file mode 100644 index 0000000000..78b8bde8d2 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_customising_aws_client_config.cs @@ -0,0 +1,97 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +public class CustomisingAwsClientConfigTests : IDisposable +{ + private readonly Message _message; + private readonly IAmAChannel _channel; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + + private readonly InterceptingDelegatingHandler _publishHttpHandler = new(); + private readonly InterceptingDelegatingHandler _subscribeHttpHandler = new(); + + public CustomisingAwsClientConfigTests() + { + MyCommand myCommand = new() { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var partitionKey = $"Partition-Key-{Guid.NewGuid().ToString()}".Truncate(45); + + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + sqsType: SnsSqsType.Fifo + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var subscribeAwsConnection = new AWSMessagingGatewayConnection(credentials, region, config => + { + config.HttpClientFactory = new InterceptingHttpClientFactory(_subscribeHttpHandler); + }); + + _channelFactory = new ChannelFactory(subscribeAwsConnection); + _channel = _channelFactory.CreateChannel(subscription); + + var publishAwsConnection = new AWSMessagingGatewayConnection(credentials, region, config => + { + config.HttpClientFactory = new InterceptingHttpClientFactory(_publishHttpHandler); + }); + + _messageProducer = new SqsMessageProducer(publishAwsConnection, + new SnsPublication + { + Topic = new RoutingKey(topicName), + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo + }); + } + + [Fact] + public async Task When_customising_aws_client_config() + { + //arrange + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + //clear the queue + _channel.Acknowledge(message); + + //publish_and_subscribe_should_use_custom_http_client_factory + _publishHttpHandler.RequestCount.Should().BeGreaterThan(0); + _subscribeHttpHandler.RequestCount.Should().BeGreaterThan(0); + } + + public void Dispose() + { + _channelFactory?.DeleteTopic(); + _channelFactory?.DeleteQueue(); + _messageProducer?.Dispose(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs new file mode 100644 index 0000000000..e28ee3cf45 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs @@ -0,0 +1,98 @@ +using System; +using System.Linq; +using System.Text.Json; +using Amazon; +using Amazon.Runtime; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSAssumeInfrastructureTests : IDisposable +{ + private readonly Message _message; + private readonly MyCommand _myCommand; + private readonly SqsMessageConsumer _consumer; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + + public AWSAssumeInfrastructureTests() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var partitionKey = $"Partition-Key-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateChannel(subscription); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + makeChannels: OnMissingChannel.Assume, + sqsType: SnsSqsType.Fifo + ); + + _messageProducer = new SqsMessageProducer(awsConnection, + new SnsPublication { MakeChannels = OnMissingChannel.Assume, SnsType = SnsSqsType.Fifo }); + + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), routingKey); + } + + [Fact] + public void When_infastructure_exists_can_assume() + { + //arrange + _messageProducer.Send(_message); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + _channelFactory.DeleteTopic(); + _channelFactory.DeleteQueue(); + _consumer.Dispose(); + _messageProducer.Dispose(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs new file mode 100644 index 0000000000..9b3c5a90a1 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs @@ -0,0 +1,111 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureTests : IDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumer _consumer; + private readonly SqsMessageProducer _messageProducer; + private readonly MyCommand _myCommand; + private readonly ChannelFactory _channelFactory; + + public AWSValidateInfrastructureTests() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var partitionKey = $"Partition-Key-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateChannel(subscription); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + findTopicBy: TopicFindBy.Name, + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo, + contentBasedDeduplication: true + ); + + _messageProducer = new SqsMessageProducer( + awsConnection, + new SnsPublication + { + FindTopicBy = TopicFindBy.Name, + MakeChannels = OnMissingChannel.Validate, + Topic = new RoutingKey(topicName), + SnsType = SnsSqsType.Fifo, + Deduplication = true + } + ); + + _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify_for_fifo() + { + //arrange + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + _channelFactory.DeleteTopic(); + _channelFactory.DeleteQueue(); + _consumer.Dispose(); + _messageProducer.Dispose(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs new file mode 100644 index 0000000000..b4ecc20209 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs @@ -0,0 +1,119 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using Amazon.SimpleNotificationService; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureByArnTests : IDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumer _consumer; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureByArnTests() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var partitionKey = $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey($"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateChannel(subscription); + + var topicArn = FindTopicArn(credentials, region, routingKey.Value); + var routingKeyArn = new RoutingKey(topicArn); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKeyArn, + findTopicBy: TopicFindBy.Arn, + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo + ); + + _messageProducer = new SqsMessageProducer( + awsConnection, + new SnsPublication + { + Topic = routingKey, + TopicArn = topicArn, + FindTopicBy = TopicFindBy.Arn, + MakeChannels = OnMissingChannel.Validate, + SnsType = SnsSqsType.Fifo + }); + + _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify() + { + //arrange + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + _channelFactory.DeleteTopic(); + _channelFactory.DeleteQueue(); + _consumer.Dispose(); + _messageProducer.Dispose(); + } + + private static string FindTopicArn(AWSCredentials credentials, RegionEndpoint region, string topicName) + { + var snsClient = new AmazonSimpleNotificationServiceClient(credentials, region); + var topicResponse = snsClient.FindTopicAsync(topicName).GetAwaiter().GetResult(); + return topicResponse.TopicArn; + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs new file mode 100644 index 0000000000..9f5434cdea --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs @@ -0,0 +1,108 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureByConventionTests : IDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumer _consumer; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureByConventionTests() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var partitionKey = $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateChannel(subscription); + + //Now change the subscription to validate, just check what we made - will make the SNS Arn to prevent ListTopics call + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + findTopicBy: TopicFindBy.Convention, + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo + ); + + _messageProducer = new SqsMessageProducer( + awsConnection, + new SnsPublication + { + FindTopicBy = TopicFindBy.Convention, + MakeChannels = OnMissingChannel.Validate, + SnsType = SnsSqsType.Fifo + } + ); + + _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify() + { + //arrange + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + _channelFactory.DeleteTopic(); + _channelFactory.DeleteQueue(); + _consumer.Dispose(); + _messageProducer.Dispose(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs new file mode 100644 index 0000000000..a3ef2062eb --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs @@ -0,0 +1,133 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +public class SqsMessageProducerSendTests : IDisposable +{ + private readonly Message _message; + private readonly IAmAChannel _channel; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + private readonly string _topicName; + private readonly string _partitionKey; + private readonly string _deduplicationId; + + private readonly string _correlationId; + private readonly string _replyTo; + private readonly string _contentType; + + public SqsMessageProducerSendTests() + { + _myCommand = new MyCommand { Value = "Test" }; + _correlationId = Guid.NewGuid().ToString(); + _replyTo = "http:\\queueUrl"; + _contentType = "text\\plain"; + _topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _partitionKey = $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _deduplicationId = $"Deduplication-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(_topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + rawMessageDelivery: false, + sqsType: SnsSqsType.Fifo, + contentBasedDeduplication: true + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: _correlationId, + replyTo: new RoutingKey(_replyTo), contentType: _contentType, partitionKey: _partitionKey), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateChannel(subscription); + + _messageProducer = new SqsMessageProducer(awsConnection, + new SnsPublication + { + Topic = new RoutingKey(_topicName), + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo, + Deduplication = true + }); + } + + [Theory] + [InlineData("test subject", true)] + [InlineData(null, true)] + [InlineData("test subject", false)] + [InlineData(null, false)] + public async Task When_posting_a_message_via_the_producer_for_fifo(string subject, bool sendAsync) + { + //arrange + _message.Header.Subject = subject; + if (sendAsync) + { + await _messageProducer.SendAsync(_message); + } + else + { + _messageProducer.Send(_message); + } + + await Task.Delay(1000); + + var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + //clear the queue + _channel.Acknowledge(message); + + //should_send_the_message_to_aws_sqs + message.Header.MessageType.Should().Be(MessageType.MT_COMMAND); + + message.Id.Should().Be(_myCommand.Id); + message.Redelivered.Should().BeFalse(); + message.Header.MessageId.Should().Be(_myCommand.Id); + message.Header.Topic.Value.Should().Contain(_topicName); + message.Header.CorrelationId.Should().Be(_correlationId); + message.Header.ReplyTo.Should().Be(_replyTo); + message.Header.ContentType.Should().Be(_contentType); + message.Header.HandledCount.Should().Be(0); + message.Header.Subject.Should().Be(subject); + //allow for clock drift in the following test, more important to have a contemporary timestamp than anything + message.Header.TimeStamp.Should().BeAfter(RoundToSeconds(DateTime.UtcNow.AddMinutes(-1))); + message.Header.Delayed.Should().Be(TimeSpan.Zero); + //{"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} + message.Body.Value.Should().Be(_message.Body.Value); + message.Header.Bag.Should().ContainKey(HeaderNames.DeduplicationId); + message.Header.Bag[HeaderNames.DeduplicationId].Should().Be(_deduplicationId); + message.Header.PartitionKey.Should().Be(_partitionKey); + message.Header.Bag.Should().ContainKey(HeaderNames.MessageGroupId); + message.Header.Bag[HeaderNames.MessageGroupId].Should().Be(_partitionKey); + } + + public void Dispose() + { + _channelFactory?.DeleteTopic(); + _channelFactory?.DeleteQueue(); + _messageProducer?.Dispose(); + } + + private static DateTime RoundToSeconds(DateTime dateTime) + { + return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs new file mode 100644 index 0000000000..d825fd1f13 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs @@ -0,0 +1,64 @@ +using System; +using Amazon; +using Amazon.Runtime; +using Amazon.SQS.Model; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +public class AWSAssumeQueuesTests : IDisposable +{ + private readonly ChannelFactory _channelFactory; + private readonly SqsMessageConsumer _consumer; + + public AWSAssumeQueuesTests() + { + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + makeChannels: OnMissingChannel.Assume, + sqsType: SnsSqsType.Fifo + ); + + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + + //create the topic, we want the queue to be the issue + //We need to create the topic at least, to check the queues + var producer = new SqsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo + }); + + producer.ConfirmTopicExistsAsync(topicName).Wait(); + + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateChannel(subscription); + + //We need to create the topic at least, to check the queues + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), routingKey); + } + + [Fact] + public void When_queues_missing_assume_throws() + { + //we will try to get the queue url, and fail because it does not exist + Assert.Throws(() => _consumer.Receive(TimeSpan.FromMilliseconds(1000))); + } + + public void Dispose() + { + _channelFactory.DeleteTopic(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs new file mode 100644 index 0000000000..e1669fc54b --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs @@ -0,0 +1,60 @@ +using System; +using Amazon; +using Amazon.Runtime; +using Amazon.SQS.Model; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +public class AWSValidateQueuesTests : IDisposable +{ + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; + private ChannelFactory _channelFactory; + + public AWSValidateQueuesTests() + { + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + _subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo + ); + + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + _awsConnection = new AWSMessagingGatewayConnection(credentials, region); + + //We need to create the topic at least, to check the queues + var producer = new SqsMessageProducer(_awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo + }); + producer.ConfirmTopicExistsAsync(topicName).Wait(); + + } + + [Fact] + public void When_queues_missing_verify_throws() + { + //We have no queues so we should throw + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(_awsConnection); + Assert.Throws(() => _channelFactory.CreateChannel(_subscription)); + } + + public void Dispose() + { + _channelFactory.DeleteTopic(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs new file mode 100644 index 0000000000..04e7257081 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using Amazon; +using Amazon.Runtime; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsRawMessageDeliveryTests : IDisposable +{ + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly IAmAChannel _channel; + private readonly RoutingKey _routingKey; + + public SqsRawMessageDeliveryTests() + { + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + + _channelFactory = new ChannelFactory(awsConnection); + var channelName = $"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey($"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); + + var bufferSize = 10; + + //Set rawMessageDelivery to false + _channel = _channelFactory.CreateChannel(new SqsSubscription( + name: new SubscriptionName(channelName), + channelName:new ChannelName(channelName), + routingKey:_routingKey, + bufferSize: bufferSize, + makeChannels: OnMissingChannel.Create, + rawMessageDelivery: false, + sqsType: SnsSqsType.Fifo, + contentBasedDeduplication: true)); + + _messageProducer = new SqsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo, + Deduplication = true + }); + } + + [Fact] + public void When_raw_message_delivery_disabled() + { + //arrange + var partitionKey = Guid.NewGuid().ToString(); + var deduplicationId = Guid.NewGuid().ToString(); + + var messageHeader = new MessageHeader( + Guid.NewGuid().ToString(), + _routingKey, + MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), + replyTo: RoutingKey.Empty, + contentType: "text\\plain", + partitionKey: partitionKey) + { + Bag = + { + [HeaderNames.DeduplicationId] = deduplicationId + } + }; + + var customHeaderItem = new KeyValuePair("custom-header-item", "custom-header-item-value"); + messageHeader.Bag.Add(customHeaderItem.Key, customHeaderItem.Value); + + var messageToSent = new Message(messageHeader, new MessageBody("test content one")); + + //act + _messageProducer.Send(messageToSent); + + var messageReceived = _channel.Receive(TimeSpan.FromMilliseconds(10000)); + + _channel.Acknowledge(messageReceived); + + //assert + messageReceived.Id.Should().Be(messageToSent.Id); + messageReceived.Header.Topic.Should().Be(messageToSent.Header.Topic); + messageReceived.Header.MessageType.Should().Be(messageToSent.Header.MessageType); + messageReceived.Header.CorrelationId.Should().Be(messageToSent.Header.CorrelationId); + messageReceived.Header.ReplyTo.Should().Be(messageToSent.Header.ReplyTo); + messageReceived.Header.ContentType.Should().Be(messageToSent.Header.ContentType); + messageReceived.Header.PartitionKey.Should().Be(partitionKey); + messageReceived.Header.Bag.Should().ContainKey(customHeaderItem.Key).And.ContainValue(customHeaderItem.Value); + messageReceived.Header.Bag.Should().ContainKey(HeaderNames.DeduplicationId).And.ContainValue(deduplicationId); + messageReceived.Header.Bag.Should().ContainKey(HeaderNames.MessageGroupId).And.ContainValue(partitionKey); + messageReceived.Body.Value.Should().Be(messageToSent.Body.Value); + + } + + public void Dispose() + { + _channelFactory.DeleteTopic(); + _channelFactory.DeleteQueue(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs new file mode 100644 index 0000000000..66f2ecc07f --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs @@ -0,0 +1,90 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageConsumerRequeueTests : IDisposable +{ + private readonly Message _message; + private readonly IAmAChannel _channel; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public SqsMessageConsumerRequeueTests() + { + _myCommand = new MyCommand{Value = "Test"}; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var channelName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var partitionKey = $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + sqsType: SnsSqsType.Fifo + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), + new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + ); + + //Must have credentials stored in the SDK Credentials store or shared credentials file + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateChannel(subscription); + + _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication + { + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo + }); + } + + [Fact] + public void When_rejecting_a_message_through_gateway_with_requeue() + { + _messageProducer.Send(_message); + + var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + _channel.Reject(message); + + //Let the timeout change + Task.Delay(TimeSpan.FromMilliseconds(3000)); + + //should requeue_the_message + message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + //clear the queue + _channel.Acknowledge(message); + + message.Id.Should().Be(_myCommand.Id); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopic(); + _channelFactory.DeleteQueue(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs new file mode 100644 index 0000000000..13f1c0e9b2 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs @@ -0,0 +1,84 @@ +using System; +using System.Text.Json; +using Amazon; +using Amazon.Runtime; +using Amazon.Runtime.CredentialManagement; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +public class SqsMessageProducerRequeueTests : IDisposable +{ + private Message _requeuedMessage; + private Message _receivedMessage; + private readonly IAmAMessageProducerSync _sender; + private readonly IAmAChannel _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; + + public SqsMessageProducerRequeueTests() + { + MyCommand myCommand = new() { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var channelName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var partitionKey= $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + //Must have credentials stored in the SDK Credentials store or shared credentials file + new CredentialProfileStoreChain(); + + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + + _sender = new SqsMessageProducer(awsConnection, new SnsPublication + { + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo + }); + + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateChannel(subscription); + } + + [Fact] + public void When_requeueing_a_message() + { + _sender.Send(_message); + _receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(_receivedMessage); + + _requeuedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + //clear the queue + _channel.Acknowledge(_requeuedMessage); + + _requeuedMessage.Body.Value.Should().Be(_receivedMessage.Body.Value); + } + + public void Dispose() + { + _channelFactory.DeleteTopic(); + _channelFactory.DeleteQueue(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs new file mode 100644 index 0000000000..50146f45f2 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs @@ -0,0 +1,113 @@ +using System; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using Amazon.SQS; +using Amazon.SQS.Model; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageProducerDlqTests : IDisposable +{ + private readonly SqsMessageProducer _sender; + private readonly IAmAChannel _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly string _dlqChannelName; + + public SqsMessageProducerDlqTests() + { + var myCommand = new MyCommand { Value = "Test" }; + var correlationId = Guid.NewGuid().ToString(); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var partitionKey = $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _dlqChannelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + redrivePolicy: new RedrivePolicy(_dlqChannelName, 2), + sqsType: SnsSqsType.Fifo + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + //Must have credentials stored in the SDK Credentials store or shared credentials file + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + _awsConnection = new AWSMessagingGatewayConnection(credentials, region); + + _sender = new SqsMessageProducer(_awsConnection, + new SnsPublication { MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo }); + + _sender.ConfirmTopicExistsAsync(topicName).Wait(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateChannel(subscription); + } + + [Fact] + public void When_requeueing_redrives_to_the_queue() + { + _sender.Send(_message); + var receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(receivedMessage); + + receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(receivedMessage); + + //should force us into the dlq + receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(receivedMessage); + + Task.Delay(5000); + + //inspect the dlq + GetDLQCount(_dlqChannelName).Should().Be(1); + } + + public void Dispose() + { + _channelFactory.DeleteTopic(); + _channelFactory.DeleteQueue(); + } + + private int GetDLQCount(string queueName) + { + using var sqsClient = new AmazonSQSClient(_awsConnection.Credentials, _awsConnection.Region); + var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); + var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest + { + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageAttributeNames = ["All", "ApproximateReceiveCount"] + }).GetAwaiter().GetResult(); + + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new AmazonSQSException( + $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + } + + return response.Messages.Count; + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs new file mode 100644 index 0000000000..57de059a70 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using Amazon.SQS; +using Amazon.SQS.Model; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Paramore.Brighter.ServiceActivator; +using Polly.Registry; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SnsReDrivePolicySDlqTests +{ + private readonly IAmAMessagePump _messagePump; + private readonly Message _message; + private readonly string _dlqChannelName; + private readonly IAmAChannel _channel; + private readonly SqsMessageProducer _sender; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; + + public SnsReDrivePolicySDlqTests() + { + string correlationId = Guid.NewGuid().ToString(); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var channelName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _dlqChannelName = $"Redrive-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var partitionKey = $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + //how are we consuming + _subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + //don't block the redrive policy from owning retry management + requeueCount: -1, + //delay before requeuing + requeueDelay: TimeSpan.FromMilliseconds(50), + sqsType: SnsSqsType.Fifo, + //we want our SNS subscription to manage requeue limits using the DLQ for 'too many requeues' + redrivePolicy: new RedrivePolicy + ( + deadLetterQueueName: new ChannelName(_dlqChannelName), + maxReceiveCount: 2 + )); + + //what do we send + var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + //Must have credentials stored in the SDK Credentials store or shared credentials file + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + _awsConnection = new AWSMessagingGatewayConnection(credentials, region); + + //how do we send to the queue + _sender = new SqsMessageProducer( + _awsConnection, + new SnsPublication + { + Topic = routingKey, + RequestType = typeof(MyDeferredCommand), + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo + } + ); + + //We need to do this manually in a test - will create the channel from subscriber parameters + ChannelFactory channelFactory = new(_awsConnection); + _channel = channelFactory.CreateChannel(_subscription); + + //how do we handle a command + IHandleRequests handler = new MyDeferredCommandHandler(); + + //hook up routing for the command processor + var subscriberRegistry = new SubscriberRegistry(); + subscriberRegistry.Register(); + + //once we read, how do we dispatch to a handler. N.B. we don't use this for reading here + IAmACommandProcessor commandProcessor = new CommandProcessor( + subscriberRegistry: subscriberRegistry, + handlerFactory: new QuickHandlerFactory(() => handler), + requestContextFactory: new InMemoryRequestContextFactory(), + policyRegistry: new PolicyRegistry() + ); + var provider = new CommandProcessorProvider(commandProcessor); + + var messageMapperRegistry = new MessageMapperRegistry( + new SimpleMessageMapperFactory(_ => new MyDeferredCommandMessageMapper()), + null + ); + messageMapperRegistry.Register(); + + //pump messages from a channel to a handler - in essence we are building our own dispatcher in this test + _messagePump = new MessagePumpBlocking(provider, messageMapperRegistry, + null, new InMemoryRequestContextFactory(), _channel) + { + Channel = _channel, TimeOut = TimeSpan.FromMilliseconds(5000), RequeueCount = 3 + }; + } + + public int GetDLQCount(string queueName) + { + using var sqsClient = new AmazonSQSClient(_awsConnection.Credentials, _awsConnection.Region); + var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); + var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest + { + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageSystemAttributeNames = ["ApproximateReceiveCount"], + MessageAttributeNames = new List { "All" } + }).GetAwaiter().GetResult(); + + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new AmazonSQSException($"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + } + + return response.Messages.Count; + } + + + [Fact] + public async Task When_throwing_defer_action_respect_redrive() + { + //put something on an SNS topic, which will be delivered to our SQS queue + _sender.Send(_message); + + //start a message pump, let it process messages + var task = Task.Factory.StartNew(() => _messagePump.Run(), TaskCreationOptions.LongRunning); + await Task.Delay(5000); + + //send a quit message to the pump to terminate it + var quitMessage = MessageFactory.CreateQuitMessage(_subscription.RoutingKey); + _channel.Enqueue(quitMessage); + + //wait for the pump to stop once it gets a quit message + await Task.WhenAll(task); + + await Task.Delay(5000); + + //inspect the dlq + GetDLQCount(_dlqChannelName).Should().Be(1); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs new file mode 100644 index 0000000000..1ed01318c0 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs @@ -0,0 +1,45 @@ +using System; +using Amazon; +using Amazon.Runtime; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +public class AWSValidateMissingTopicTests +{ + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly RoutingKey _routingKey; + + public AWSValidateMissingTopicTests() + { + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey(topicName); + + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + _awsConnection = new AWSMessagingGatewayConnection(credentials, region); + + //Because we don't use channel factory to create the infrastructure -it won't exist + } + + [Theory] + [InlineData(SnsSqsType.Standard, null)] + [InlineData(SnsSqsType.Fifo, "123")] + public void When_topic_missing_verify_throws(SnsSqsType type, string partitionKey) + { + //arrange + var producer = new SqsMessageProducer(_awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Validate, + SnsType = type + }); + + //act && assert + Assert.Throws(() => producer.Send(new Message( + new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text") { PartitionKey = partitionKey}, + new MessageBody("Test")))); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages.cs new file mode 100644 index 0000000000..90e240c10c --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard +{ + [Trait("Category", "AWS")] + [Trait("Fragile", "CI")] + public class SQSBufferedConsumerTests : IDisposable + { + private readonly SqsMessageProducer _messageProducer; + private readonly SqsMessageConsumer _consumer; + private readonly string _topicName; + private readonly ChannelFactory _channelFactory; + private const string _contentType = "text\\plain"; + private const int _bufferSize = 3; + private const int _messageCount = 4; + + public SQSBufferedConsumerTests() + { + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + + _channelFactory = new ChannelFactory(awsConnection); + + var channelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _topicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + + //we need the channel to create the queues and notifications + var routingKey = new RoutingKey(_topicName); + + var channel = _channelFactory.CreateChannel(new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + bufferSize: _bufferSize, + makeChannels: OnMissingChannel.Create + )); + + //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel + //just for the tests, so create a new consumer from the properties + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), routingKey, + _bufferSize); + _messageProducer = new SqsMessageProducer(awsConnection, + new SnsPublication { MakeChannels = OnMissingChannel.Create }); + } + + [Fact] + public async Task When_a_message_consumer_reads_multiple_messages() + { + var routingKey = new RoutingKey(_topicName); + + var messageOne = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType), + new MessageBody("test content one") + ); + + var messageTwo = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType), + new MessageBody("test content two") + ); + + var messageThree = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType), + new MessageBody("test content three") + ); + + var messageFour = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: _contentType), + new MessageBody("test content four") + ); + + //send MESSAGE_COUNT messages + _messageProducer.Send(messageOne); + _messageProducer.Send(messageTwo); + _messageProducer.Send(messageThree); + _messageProducer.Send(messageFour); + + + int iteration = 0; + var messagesReceived = new List(); + var messagesReceivedCount = messagesReceived.Count; + do + { + iteration++; + var outstandingMessageCount = _messageCount - messagesReceivedCount; + + //retrieve messages + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(10000)); + + messages.Length.Should().BeLessOrEqualTo(outstandingMessageCount); + + //should not receive more than buffer in one hit + messages.Length.Should().BeLessOrEqualTo(_bufferSize); + + var moreMessages = messages.Where(m => m.Header.MessageType == MessageType.MT_COMMAND); + foreach (var message in moreMessages) + { + messagesReceived.Add(message); + _consumer.Acknowledge(message); + } + + messagesReceivedCount = messagesReceived.Count; + + await Task.Delay(1000); + } while ((iteration <= 5) && (messagesReceivedCount < _messageCount)); + + + messagesReceivedCount.Should().Be(4); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopic(); + _channelFactory.DeleteQueue(); + } + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_customising_aws_client_config.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs similarity index 98% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_customising_aws_client_config.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs index 83daa8c6d7..e33f972dad 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_customising_aws_client_config.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs @@ -9,7 +9,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] public class CustomisingAwsClientConfigTests : IDisposable diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs similarity index 52% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_assume.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs index fdf16b805c..19ed493b23 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_assume.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs @@ -9,7 +9,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -21,12 +21,6 @@ public class AWSAssumeInfrastructureTests : IDisposable private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; - private readonly Message _fifoMessage; - private readonly SqsMessageConsumer _fifoConsumer; - private readonly SqsMessageProducer _fifoMessageProducer; - private readonly ChannelFactory _fifoChannelFactory; - - public AWSAssumeInfrastructureTests() { _myCommand = new MyCommand { Value = "Test" }; @@ -73,47 +67,6 @@ public AWSAssumeInfrastructureTests() new SnsPublication { MakeChannels = OnMissingChannel.Assume }); _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), routingKey); - - // Because fifo can modify the topic name we need to run the same test twice, one for standard queue and another to FIFO - var fifoChannelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string fifoTopicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var fifoRoutingKey = new RoutingKey(fifoTopicName); - - SqsSubscription fifoSubscription = new( - name: new SubscriptionName(fifoChannelName), - channelName: new ChannelName(fifoChannelName), - routingKey: fifoRoutingKey, - makeChannels: OnMissingChannel.Create, - sqsType: SnsSqsType.Fifo - ); - - string fifoMessageGroupId = Guid.NewGuid().ToString(); - _fifoMessage = new Message( - new MessageHeader(_myCommand.Id, fifoRoutingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: fifoMessageGroupId), - new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) - ); - - //We need to do this manually in a test - will create the channel from subscriber parameters - //This doesn't look that different from our create tests - this is because we create using the channel factory in - //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) - _fifoChannelFactory = new ChannelFactory(awsConnection); - var fifoChannel = _fifoChannelFactory.CreateChannel(fifoSubscription); - - //Now change the subscription to validate, just check what we made - fifoSubscription = new( - name: new SubscriptionName(fifoChannelName), - channelName: fifoChannel.Name, - routingKey: fifoRoutingKey, - makeChannels: OnMissingChannel.Assume, - sqsType: SnsSqsType.Fifo - ); - - _fifoMessageProducer = new SqsMessageProducer(awsConnection, - new SnsPublication { MakeChannels = OnMissingChannel.Assume, SnsType = SnsSqsType.Fifo }); - - _fifoConsumer = - new SqsMessageConsumer(awsConnection, fifoChannel.Name.ToValidSQSQueueName(), fifoRoutingKey); } [Fact] @@ -132,35 +85,12 @@ public void When_infastructure_exists_can_assume() _consumer.Acknowledge(message); } - [Fact] - public void When_infastructure_exists_can_assume_for_fifo() - { - //arrange - _fifoMessageProducer.Send(_fifoMessage); - - var messages = _fifoConsumer.Receive(TimeSpan.FromMilliseconds(5000)); - - //Assert - var message = messages.First(); - message.Id.Should().Be(_myCommand.Id); - - //clear the queue - _fifoConsumer.Acknowledge(message); - } - - public void Dispose() { _channelFactory.DeleteTopic(); _channelFactory.DeleteQueue(); _consumer.Dispose(); _messageProducer.Dispose(); - - - _fifoChannelFactory.DeleteTopic(); - _fifoChannelFactory.DeleteQueue(); - _fifoConsumer.Dispose(); - _fifoMessageProducer.Dispose(); } } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify.cs similarity index 53% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify.cs index 98a5ad6659..429dee9ee8 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify.cs @@ -10,7 +10,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -22,11 +22,6 @@ public class AWSValidateInfrastructureTests : IDisposable private readonly MyCommand _myCommand; private readonly ChannelFactory _channelFactory; - private readonly Message _fifoMessage; - private readonly IAmAMessageConsumer _fifoConsumer; - private readonly SqsMessageProducer _fifoMessageProducer; - private readonly ChannelFactory _fifoChannelFactory; - public AWSValidateInfrastructureTests() { _myCommand = new MyCommand { Value = "Test" }; @@ -80,53 +75,6 @@ public AWSValidateInfrastructureTests() ); _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); - - // Because fifo can modify the topic name we need to run the same test twice, one for standard queue and another to FIFO - var fifoChannelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string fifoTopicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var fifoRoutingKey = new RoutingKey(fifoTopicName); - - SqsSubscription fifoSubscription = new( - name: new SubscriptionName(fifoChannelName), - channelName: new ChannelName(fifoChannelName), - routingKey: fifoRoutingKey, - makeChannels: OnMissingChannel.Create - ); - - var fifoMessageGroupId = Guid.NewGuid().ToString(); - _fifoMessage = new Message( - new MessageHeader(_myCommand.Id, fifoRoutingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: fifoMessageGroupId), - new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) - ); - - //We need to do this manually in a test - will create the channel from subscriber parameters - //This doesn't look that different from our create tests - this is because we create using the channel factory in - //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) - _fifoChannelFactory = new ChannelFactory(awsConnection); - var fifoChannel = _fifoChannelFactory.CreateChannel(fifoSubscription); - - //Now change the subscription to validate, just check what we made - fifoSubscription = new( - name: new SubscriptionName(fifoChannelName), - channelName: fifoChannel.Name, - routingKey: fifoRoutingKey, - findTopicBy: TopicFindBy.Name, - makeChannels: OnMissingChannel.Validate - ); - - _fifoMessageProducer = new SqsMessageProducer( - awsConnection, - new SnsPublication - { - FindTopicBy = TopicFindBy.Name, - MakeChannels = OnMissingChannel.Validate, - Topic = new RoutingKey(fifoTopicName), - SnsType = SnsSqsType.Fifo - } - ); - - _fifoConsumer = new SqsMessageConsumerFactory(awsConnection).Create(fifoSubscription); } [Fact] @@ -147,35 +95,12 @@ public async Task When_infrastructure_exists_can_verify() _consumer.Acknowledge(message); } - [Fact] - public async Task When_infrastructure_exists_can_verify_for_fifo() - { - //arrange - _fifoMessageProducer.Send(_fifoMessage); - - await Task.Delay(1000); - - var messages = _fifoConsumer.Receive(TimeSpan.FromMilliseconds(5000)); - - //Assert - var message = messages.First(); - message.Id.Should().Be(_myCommand.Id); - - //clear the queue - _fifoConsumer.Acknowledge(message); - } - public void Dispose() { _channelFactory.DeleteTopic(); _channelFactory.DeleteQueue(); _consumer.Dispose(); _messageProducer.Dispose(); - - _fifoChannelFactory.DeleteTopic(); - _fifoChannelFactory.DeleteQueue(); - _fifoConsumer.Dispose(); - _fifoMessageProducer.Dispose(); } } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify_by_arn.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_arn.cs similarity index 98% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify_by_arn.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_arn.cs index b27521204a..9278fa1941 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify_by_arn.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_arn.cs @@ -11,7 +11,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_convention.cs similarity index 53% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify_by_convention.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_convention.cs index 1c81a7eee5..8f29b8dca7 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_verify_by_convention.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_convention.cs @@ -10,7 +10,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -20,12 +20,6 @@ public class AWSValidateInfrastructureByConventionTests : IDisposable private readonly IAmAMessageConsumer _consumer; private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; - - private readonly Message _fifoMessage; - private readonly IAmAMessageConsumer _fifoConsumer; - private readonly SqsMessageProducer _fifoMessageProducer; - private readonly ChannelFactory _fifoChannelFactory; - private readonly MyCommand _myCommand; public AWSValidateInfrastructureByConventionTests() @@ -76,53 +70,6 @@ public AWSValidateInfrastructureByConventionTests() ); _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); - - // Because fifo can modify the topic name we need to run the same test twice, one for standard queue and another to FIFO - var fifoChannelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string fifoTopicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var fifoRoutingKey = new RoutingKey(fifoTopicName); - - SqsSubscription fifoSubscription = new( - name: new SubscriptionName(fifoChannelName), - channelName: new ChannelName(fifoChannelName), - routingKey: fifoRoutingKey, - makeChannels: OnMissingChannel.Create - ); - - _fifoMessage = new Message( - new MessageHeader(_myCommand.Id, fifoRoutingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType, - partitionKey: Guid.NewGuid().ToString()), - new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) - ); - - //We need to do this manually in a test - will create the channel from subscriber parameters - //This doesn't look that different from our create tests - this is because we create using the channel factory in - //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) - _fifoChannelFactory = new ChannelFactory(awsConnection); - var fifoChannel = _fifoChannelFactory.CreateChannel(fifoSubscription); - - //Now change the subscription to validate, just check what we made - will make the SNS Arn to prevent ListTopics call - fifoSubscription = new( - name: new SubscriptionName(fifoChannelName), - channelName: fifoChannel.Name, - routingKey: fifoRoutingKey, - findTopicBy: TopicFindBy.Convention, - makeChannels: OnMissingChannel.Validate, - sqsType: SnsSqsType.Fifo - ); - - _messageProducer = new SqsMessageProducer( - awsConnection, - new SnsPublication - { - FindTopicBy = TopicFindBy.Convention, - MakeChannels = OnMissingChannel.Validate, - SnsType = SnsSqsType.Fifo - } - ); - - _fifoConsumer = new SqsMessageConsumerFactory(awsConnection).Create(fifoSubscription); } [Fact] @@ -143,35 +90,12 @@ public async Task When_infrastructure_exists_can_verify() _consumer.Acknowledge(message); } - [Fact] - public async Task When_infrastructure_exists_can_verify_for_fico() - { - //arrange - _fifoMessageProducer.Send(_fifoMessage); - - await Task.Delay(1000); - - var messages = _fifoConsumer.Receive(TimeSpan.FromMilliseconds(5000)); - - //Assert - var message = messages.First(); - message.Id.Should().Be(_myCommand.Id); - - //clear the queue - _fifoConsumer.Acknowledge(message); - } - public void Dispose() { _channelFactory.DeleteTopic(); _channelFactory.DeleteQueue(); _consumer.Dispose(); _messageProducer.Dispose(); - - _fifoChannelFactory.DeleteTopic(); - _fifoChannelFactory.DeleteQueue(); - _fifoConsumer.Dispose(); - _fifoMessageProducer.Dispose(); } } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_posting_a_message_via_the_messaging_gateway.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway.cs similarity index 50% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_posting_a_message_via_the_messaging_gateway.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway.cs index f8643af2de..6dd1fda4da 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_posting_a_message_via_the_messaging_gateway.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway.cs @@ -9,7 +9,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] public class SqsMessageProducerSendTests : IDisposable @@ -20,15 +20,6 @@ public class SqsMessageProducerSendTests : IDisposable private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; private readonly string _topicName; - - private readonly Message _fifoMessage; - private readonly IAmAChannel _fifoChannel; - private readonly SqsMessageProducer _fifoMessageProducer; - private readonly ChannelFactory _fifoChannelFactory; - private readonly string _fifoTopicName; - private readonly string _fifoMessageGroupId; - private readonly string _fifoDeduplicationId; - private readonly string _correlationId; private readonly string _replyTo; private readonly string _contentType; @@ -65,46 +56,6 @@ public SqsMessageProducerSendTests() _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication { Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create }); - - // FIFO - var fifoChannelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _fifoTopicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var fifoRoutingKey = new RoutingKey(_fifoTopicName); - - SqsSubscription fifoSubscription = new( - name: new SubscriptionName(fifoChannelName), - channelName: new ChannelName(channelName), - routingKey: fifoRoutingKey, - rawMessageDelivery: false, - sqsType: SnsSqsType.Fifo - ); - - _fifoMessageGroupId = Guid.NewGuid().ToString(); - _fifoDeduplicationId = Guid.NewGuid().ToString(); - _fifoMessage = new Message( - new MessageHeader(_myCommand.Id, fifoRoutingKey, MessageType.MT_COMMAND, correlationId: _correlationId, - replyTo: new RoutingKey(_replyTo), contentType: _contentType, - partitionKey: _fifoMessageGroupId) - { - Bag = - { - [HeaderNames.DeduplicationId] = _fifoDeduplicationId - } - }, - new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) - ); - - _fifoChannelFactory = new ChannelFactory(awsConnection); - _fifoChannel = _fifoChannelFactory.CreateChannel(fifoSubscription); - - _fifoMessageProducer = new SqsMessageProducer(awsConnection, - new SnsPublication - { - Topic = new RoutingKey(_fifoTopicName), - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo, - Deduplication = true - }); } @@ -152,63 +103,11 @@ public async Task When_posting_a_message_via_the_producer(string subject, bool s message.Body.Value.Should().Be(_message.Body.Value); } - [Theory] - [InlineData("test subject", true)] - [InlineData(null, true)] - [InlineData("test subject", false)] - [InlineData(null, false)] - public async Task When_posting_a_message_via_the_producer_for_fifo(string subject, bool sendAsync) - { - //arrange - _fifoMessage.Header.Subject = subject; - if (sendAsync) - { - await _fifoMessageProducer.SendAsync(_fifoMessage); - } - else - { - _fifoMessageProducer.Send(_fifoMessage); - } - - await Task.Delay(1000); - - var message = _fifoChannel.Receive(TimeSpan.FromMilliseconds(5000)); - - //clear the queue - _fifoChannel.Acknowledge(message); - - //should_send_the_message_to_aws_sqs - message.Header.MessageType.Should().Be(MessageType.MT_COMMAND); - - message.Id.Should().Be(_myCommand.Id); - message.Redelivered.Should().BeFalse(); - message.Header.MessageId.Should().Be(_myCommand.Id); - message.Header.Topic.Value.Should().Contain(_fifoTopicName); - message.Header.CorrelationId.Should().Be(_correlationId); - message.Header.ReplyTo.Should().Be(_replyTo); - message.Header.ContentType.Should().Be(_contentType); - message.Header.HandledCount.Should().Be(0); - message.Header.Subject.Should().Be(subject); - //allow for clock drift in the following test, more important to have a contemporary timestamp than anything - message.Header.TimeStamp.Should().BeAfter(RoundToSeconds(DateTime.UtcNow.AddMinutes(-1))); - message.Header.Delayed.Should().Be(TimeSpan.Zero); - //{"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} - message.Body.Value.Should().Be(_message.Body.Value); - message.Header.PartitionKey.Should().Be(_fifoMessageGroupId); - message.Header.Bag.Should().ContainKey(HeaderNames.DeduplicationId); - message.Header.Bag[HeaderNames.DeduplicationId].Should().Be(_fifoDeduplicationId); - } - - public void Dispose() { _channelFactory?.DeleteTopic(); _channelFactory?.DeleteQueue(); _messageProducer?.Dispose(); - - _fifoChannelFactory?.DeleteTopic(); - _fifoChannelFactory?.DeleteQueue(); - _fifoMessageProducer?.Dispose(); } private static DateTime RoundToSeconds(DateTime dateTime) diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_queues_missing_assume_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws.cs similarity index 97% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_queues_missing_assume_throws.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws.cs index 250108d383..948ddd1b9c 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_queues_missing_assume_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws.cs @@ -7,7 +7,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] public class AWSAssumeQueuesTests : IDisposable diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_queues_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws.cs similarity index 97% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_queues_missing_verify_throws.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws.cs index 3b92e20561..5f3d945500 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_queues_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws.cs @@ -7,7 +7,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] public class AWSValidateQueuesTests : IDisposable diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_raw_message_delivery_disabled.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled.cs similarity index 98% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_raw_message_delivery_disabled.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled.cs index f254812e89..1d1cdc33ca 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_raw_message_delivery_disabled.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_rejecting_a_message_through_gateway_with_requeue.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue.cs similarity index 98% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_rejecting_a_message_through_gateway_with_requeue.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue.cs index 1296cd28f5..84361fc3ca 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_rejecting_a_message_through_gateway_with_requeue.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue.cs @@ -9,7 +9,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_requeueing_a_message.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message.cs similarity index 98% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_requeueing_a_message.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message.cs index 1c4b3c540e..02daf33915 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_requeueing_a_message.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message.cs @@ -9,7 +9,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] public class SqsMessageProducerRequeueTests : IDisposable diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_requeueing_redrives_to_the_dlq.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq.cs similarity index 98% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_requeueing_redrives_to_the_dlq.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq.cs index e15b3cd29d..e49da7529f 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_requeueing_redrives_to_the_dlq.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq.cs @@ -13,7 +13,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs similarity index 99% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_throwing_defer_action_respect_redrive.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs index c1895016db..90b9d17275 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_throwing_defer_action_respect_redrive.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs @@ -15,7 +15,7 @@ using Polly.Registry; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_topic_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs similarity index 96% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_topic_missing_verify_throws.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs index 4934a31dab..53bd529068 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_topic_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs @@ -5,7 +5,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] public class AWSValidateMissingTopicTests diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_a_message_consumer_reads_multiple_messages.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_a_message_consumer_reads_multiple_messages.cs deleted file mode 100644 index 7c8b99c9eb..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_a_message_consumer_reads_multiple_messages.cs +++ /dev/null @@ -1,283 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway -{ - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class SQSBufferedConsumerTests : IDisposable - { - private readonly SqsMessageProducer _messageProducer; - private readonly SqsMessageConsumer _consumer; - private readonly string _topicName; - private readonly ChannelFactory _channelFactory; - - - private readonly SqsMessageProducer _fifoMessageProducer; - private readonly SqsMessageConsumer _fifoConsumer; - private readonly string _fifoTopicName; - private readonly ChannelFactory _fifoChannelFactory; - - private const string _contentType = "text\\plain"; - private const int _bufferSize = 3; - private const int _messageCount = 4; - - public SQSBufferedConsumerTests() - { - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - _channelFactory = new ChannelFactory(awsConnection); - - var channelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _topicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - - //we need the channel to create the queues and notifications - var routingKey = new RoutingKey(_topicName); - - var channel = _channelFactory.CreateChannel(new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - bufferSize: _bufferSize, - makeChannels: OnMissingChannel.Create - )); - - //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel - //just for the tests, so create a new consumer from the properties - _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), routingKey, - _bufferSize); - _messageProducer = new SqsMessageProducer(awsConnection, - new SnsPublication { MakeChannels = OnMissingChannel.Create }); - - // Because fifo can modify the topic name we need to run the same test twice, one for standard queue and another to FIFO - _fifoChannelFactory = new ChannelFactory(awsConnection); - var fifoChannelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _fifoTopicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - - //we need the channel to create the queues and notifications - var fifoRoutingKey = new RoutingKey(_fifoTopicName); - - var fifoChannel = _fifoChannelFactory.CreateChannel(new SqsSubscription( - name: new SubscriptionName(fifoChannelName), - channelName: new ChannelName(fifoChannelName), - routingKey: fifoRoutingKey, - bufferSize: _bufferSize, - makeChannels: OnMissingChannel.Create, - sqsType: SnsSqsType.Fifo - )); - - //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel - //just for the tests, so create a new consumer from the properties - _fifoConsumer = new SqsMessageConsumer(awsConnection, fifoChannel.Name.ToValidSQSQueueName(), fifoRoutingKey, - _bufferSize); - _fifoMessageProducer = new SqsMessageProducer(awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo, - Deduplication = true - }); - } - - [Fact] - public async Task When_a_message_consumer_reads_multiple_messages() - { - var routingKey = new RoutingKey(_topicName); - - var messageOne = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType), - new MessageBody("test content one") - ); - - var messageTwo = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType), - new MessageBody("test content two") - ); - - var messageThree = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType), - new MessageBody("test content three") - ); - - var messageFour = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType), - new MessageBody("test content four") - ); - - //send MESSAGE_COUNT messages - _messageProducer.Send(messageOne); - _messageProducer.Send(messageTwo); - _messageProducer.Send(messageThree); - _messageProducer.Send(messageFour); - - - int iteration = 0; - var messagesReceived = new List(); - var messagesReceivedCount = messagesReceived.Count; - do - { - iteration++; - var outstandingMessageCount = _messageCount - messagesReceivedCount; - - //retrieve messages - var messages = _consumer.Receive(TimeSpan.FromMilliseconds(10000)); - - messages.Length.Should().BeLessOrEqualTo(outstandingMessageCount); - - //should not receive more than buffer in one hit - messages.Length.Should().BeLessOrEqualTo(_bufferSize); - - var moreMessages = messages.Where(m => m.Header.MessageType == MessageType.MT_COMMAND); - foreach (var message in moreMessages) - { - messagesReceived.Add(message); - _consumer.Acknowledge(message); - } - - messagesReceivedCount = messagesReceived.Count; - - await Task.Delay(1000); - } while ((iteration <= 5) && (messagesReceivedCount < _messageCount)); - - - messagesReceivedCount.Should().Be(4); - } - - [Fact] - public async Task When_a_message_consumer_reads_multiple_messages_per_group() - { - var routingKey = new RoutingKey(_fifoTopicName); - var messageGroupIdOne = "123"; - - var messageOne = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType, - partitionKey: messageGroupIdOne), - new MessageBody("test content one") - ); - - var messageTwo = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType, - partitionKey: messageGroupIdOne), - new MessageBody("test content two") - ); - - var messageThree = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType, - partitionKey: messageGroupIdOne), - new MessageBody("test content three") - ); - - var messageFour = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType, - partitionKey: messageGroupIdOne), - new MessageBody("test content four") - ); - var messageGroupIdTwo = "1234"; - var messageFive = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType, - partitionKey: messageGroupIdTwo), - new MessageBody("test content five") - ); - - var messageSix = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType, - partitionKey: messageGroupIdTwo), - new MessageBody("test content six") - ); - - var messageSeven = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType, - partitionKey: messageGroupIdTwo) - { - Bag = - { - [HeaderNames.DeduplicationId] = "123" - } - }, - new MessageBody("test content seven") - ); - - var messageEight = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType, - partitionKey: messageGroupIdTwo) - { - Bag = - { - [HeaderNames.DeduplicationId] = "123" - } - }, - new MessageBody("test content eight") - ); - - //send MESSAGE_COUNT messages - _fifoMessageProducer.Send(messageOne); - _fifoMessageProducer.Send(messageTwo); - _fifoMessageProducer.Send(messageThree); - _fifoMessageProducer.Send(messageFour); - _fifoMessageProducer.Send(messageFive); - _fifoMessageProducer.Send(messageSix); - _fifoMessageProducer.Send(messageSeven); - _fifoMessageProducer.Send(messageEight); - - int iteration = 0; - var messagesReceived = new List(); - var messagesReceivedCount = messagesReceived.Count; - do - { - iteration++; - - //retrieve messages - var messages = _fifoConsumer.Receive(TimeSpan.FromMilliseconds(10000)); - - // should not receive more number of message group - messages.Length.Should().BeLessOrEqualTo(2); - - var moreMessages = messages.Where(m => m.Header.MessageType == MessageType.MT_COMMAND); - foreach (var message in moreMessages) - { - messagesReceived.Add(message); - _fifoConsumer.Acknowledge(message); - } - - messagesReceivedCount = messagesReceived.Count; - - await Task.Delay(1000); - } while ((iteration <= 7) && (messagesReceivedCount < _messageCount)); - - - messagesReceivedCount.Should().Be(7); - } - - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopic(); - _channelFactory.DeleteQueue(); - - _fifoChannelFactory.DeleteTopic(); - _fifoChannelFactory.DeleteQueue(); - } - } -} From ab9d3e9909229cccabbf0322367e8191176b4eb7 Mon Sep 17 00:00:00 2001 From: Rafael Lillo Date: Mon, 30 Dec 2024 17:31:51 +0000 Subject: [PATCH 04/18] GH-1294 fixes unit test --- .../AWSMessagingGateway.cs | 7 ++- .../SqsInlineMessageCreator.cs | 3 +- .../SqsMessageCreator.cs | 5 +- ...essage_consumer_reads_multiple_messages.cs | 18 +++++-- .../When_customising_aws_client_config.cs | 22 ++++---- .../When_infastructure_exists_can_assume.cs | 22 +++++--- .../When_infastructure_exists_can_verify.cs | 21 +++++--- ..._infastructure_exists_can_verify_by_arn.cs | 19 +++++-- ...ructure_exists_can_verify_by_convention.cs | 21 +++++--- ...ing_a_message_via_the_messaging_gateway.cs | 24 ++++++--- .../Fifo/When_queues_missing_assume_throws.cs | 40 ++++++++------- .../Fifo/When_queues_missing_verify_throws.cs | 33 ++++++------ .../When_raw_message_delivery_disabled.cs | 50 +++++++++---------- ..._a_message_through_gateway_with_requeue.cs | 44 ++++++++-------- .../Fifo/When_requeueing_a_message.cs | 26 ++++++---- .../When_requeueing_redrives_to_the_dlq.cs | 16 ++++-- ...n_throwing_defer_action_respect_redrive.cs | 45 +++++++++++------ .../When_infastructure_exists_can_assume.cs | 33 ++++++------ ...n_throwing_defer_action_respect_redrive.cs | 2 +- 19 files changed, 268 insertions(+), 183 deletions(-) diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs index f0e74c4a91..10a6e6c67b 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs @@ -28,6 +28,7 @@ THE SOFTWARE. */ using System.Threading; using System.Threading.Tasks; using Amazon.SimpleNotificationService.Model; +using Amazon.SQS; using Microsoft.Extensions.Logging; using Paramore.Brighter.Logging; @@ -75,9 +76,11 @@ private async Task CreateTopicAsync(RoutingKey topicName, string name = topicName; if (snsSqsType == SnsSqsType.Fifo) { - name += ".fifo"; + if (!name.EndsWith(".fifo")) + { + name += ".fifo"; + } - // TODO: Remove hard code attributes.Add("FifoTopic", "true"); if (deduplication) { diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs index d18c727167..528326c67e 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs @@ -97,8 +97,7 @@ public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) if (deduplicationId.Success) { - // TODO: Remove hard code - message.Header.Bag.Add("DeduplicationId", deduplicationId); + message.Header.Bag.Add(HeaderNames.DeduplicationId, deduplicationId); } if (receiptHandle.Success) diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs index e924360ea1..e91a2502ed 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs @@ -109,12 +109,11 @@ public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) if (deduplicationId.Success) { - // TODO: Remove hard code - bag.Add("DeduplicationId", deduplicationId.Result!); + bag.Add(HeaderNames.DeduplicationId, deduplicationId.Result); } if (receiptHandle.Success) - message.Header.Bag.Add("ReceiptHandle", receiptHandle.Result!); + message.Header.Bag.Add("ReceiptHandle", receiptHandle.Result); } catch (Exception e) { diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs index ba961c5c25..27edda7244 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs @@ -14,7 +14,7 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class SQSBufferedConsumerTests : IDisposable +public class SQSBufferedConsumerTests : IDisposable, IAsyncDisposable { private readonly SqsMessageProducer _messageProducer; private readonly SqsMessageConsumer _consumer; @@ -37,7 +37,7 @@ public SQSBufferedConsumerTests() //we need the channel to create the queues and notifications var routingKey = new RoutingKey(_topicName); - var channel = _channelFactory.CreateChannel(new SqsSubscription( + var channel = _channelFactory.CreateSyncChannel(new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, @@ -49,7 +49,7 @@ public SQSBufferedConsumerTests() //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel //just for the tests, so create a new consumer from the properties - _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), routingKey, + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), _bufferSize); _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication @@ -162,7 +162,15 @@ public async Task When_a_message_consumer_reads_multiple_messages_per_group() public void Dispose() { //Clean up resources that we have created - _channelFactory.DeleteTopic(); - _channelFactory.DeleteQueue(); + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageProducerAsync)_messageProducer).DisposeAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_customising_aws_client_config.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_customising_aws_client_config.cs index 78b8bde8d2..5ab95554d9 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_customising_aws_client_config.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_customising_aws_client_config.cs @@ -12,10 +12,10 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; [Trait("Category", "AWS")] -public class CustomisingAwsClientConfigTests : IDisposable +public class CustomisingAwsClientConfigTests : IDisposable, IAsyncDisposable { private readonly Message _message; - private readonly IAmAChannel _channel; + private readonly IAmAChannelSync _channel; private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; @@ -54,7 +54,7 @@ public CustomisingAwsClientConfigTests() }); _channelFactory = new ChannelFactory(subscribeAwsConnection); - _channel = _channelFactory.CreateChannel(subscription); + _channel = _channelFactory.CreateSyncChannel(subscription); var publishAwsConnection = new AWSMessagingGatewayConnection(credentials, region, config => { @@ -64,9 +64,7 @@ public CustomisingAwsClientConfigTests() _messageProducer = new SqsMessageProducer(publishAwsConnection, new SnsPublication { - Topic = new RoutingKey(topicName), - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo + Topic = new RoutingKey(topicName), MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo }); } @@ -90,8 +88,14 @@ public async Task When_customising_aws_client_config() public void Dispose() { - _channelFactory?.DeleteTopic(); - _channelFactory?.DeleteQueue(); - _messageProducer?.Dispose(); + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs index e28ee3cf45..4d9376e529 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Text.Json; +using System.Threading.Tasks; using Amazon; using Amazon.Runtime; using FluentAssertions; @@ -13,7 +14,7 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class AWSAssumeInfrastructureTests : IDisposable +public class AWSAssumeInfrastructureTests : IDisposable, IAsyncDisposable { private readonly Message _message; private readonly MyCommand _myCommand; @@ -33,7 +34,7 @@ public AWSAssumeInfrastructureTests() var partitionKey = $"Partition-Key-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - SqsSubscription subscription = new( + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, @@ -55,7 +56,7 @@ public AWSAssumeInfrastructureTests() //This doesn't look that different from our create tests - this is because we create using the channel factory in //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateChannel(subscription); + var channel = _channelFactory.CreateSyncChannel(subscription); //Now change the subscription to validate, just check what we made subscription = new( @@ -69,7 +70,7 @@ public AWSAssumeInfrastructureTests() _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Assume, SnsType = SnsSqsType.Fifo }); - _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), routingKey); + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); } [Fact] @@ -90,9 +91,14 @@ public void When_infastructure_exists_can_assume() public void Dispose() { - _channelFactory.DeleteTopic(); - _channelFactory.DeleteQueue(); - _consumer.Dispose(); - _messageProducer.Dispose(); + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs index 9b3c5a90a1..73eafb7097 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs @@ -14,10 +14,10 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class AWSValidateInfrastructureTests : IDisposable +public class AWSValidateInfrastructureTests : IDisposable, IAsyncDisposable { private readonly Message _message; - private readonly IAmAMessageConsumer _consumer; + private readonly IAmAMessageConsumerSync _consumer; private readonly SqsMessageProducer _messageProducer; private readonly MyCommand _myCommand; private readonly ChannelFactory _channelFactory; @@ -33,7 +33,7 @@ public AWSValidateInfrastructureTests() var partitionKey = $"Partition-Key-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - SqsSubscription subscription = new( + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, @@ -55,7 +55,7 @@ public AWSValidateInfrastructureTests() //This doesn't look that different from our create tests - this is because we create using the channel factory in //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateChannel(subscription); + var channel = _channelFactory.CreateSyncChannel(subscription); //Now change the subscription to validate, just check what we made subscription = new( @@ -103,9 +103,18 @@ public async Task When_infrastructure_exists_can_verify_for_fifo() public void Dispose() { - _channelFactory.DeleteTopic(); - _channelFactory.DeleteQueue(); + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); _consumer.Dispose(); _messageProducer.Dispose(); } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); + await _messageProducer.DisposeAsync(); + } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs index b4ecc20209..2148f1b340 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs @@ -15,10 +15,10 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class AWSValidateInfrastructureByArnTests : IDisposable +public class AWSValidateInfrastructureByArnTests : IDisposable, IAsyncDisposable { private readonly Message _message; - private readonly IAmAMessageConsumer _consumer; + private readonly IAmAMessageConsumerSync _consumer; private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; @@ -55,7 +55,7 @@ public AWSValidateInfrastructureByArnTests() //This doesn't look that different from our create tests - this is because we create using the channel factory in //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateChannel(subscription); + var channel = _channelFactory.CreateSyncChannel(subscription); var topicArn = FindTopicArn(credentials, region, routingKey.Value); var routingKeyArn = new RoutingKey(topicArn); @@ -104,12 +104,21 @@ public async Task When_infrastructure_exists_can_verify() public void Dispose() { - _channelFactory.DeleteTopic(); - _channelFactory.DeleteQueue(); + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); _consumer.Dispose(); _messageProducer.Dispose(); } + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); + await _messageProducer.DisposeAsync(); + } + private static string FindTopicArn(AWSCredentials credentials, RegionEndpoint region, string topicName) { var snsClient = new AmazonSimpleNotificationServiceClient(credentials, region); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs index 9f5434cdea..f8d3e649ee 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs @@ -14,10 +14,10 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class AWSValidateInfrastructureByConventionTests : IDisposable +public class AWSValidateInfrastructureByConventionTests : IDisposable, IAsyncDisposable { private readonly Message _message; - private readonly IAmAMessageConsumer _consumer; + private readonly IAmAMessageConsumerSync _consumer; private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; @@ -33,7 +33,7 @@ public AWSValidateInfrastructureByConventionTests() var partitionKey = $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - SqsSubscription subscription = new( + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, @@ -55,7 +55,7 @@ public AWSValidateInfrastructureByConventionTests() //This doesn't look that different from our create tests - this is because we create using the channel factory in //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateChannel(subscription); + var channel = _channelFactory.CreateSyncChannel(subscription); //Now change the subscription to validate, just check what we made - will make the SNS Arn to prevent ListTopics call subscription = new( @@ -100,9 +100,18 @@ public async Task When_infrastructure_exists_can_verify() public void Dispose() { - _channelFactory.DeleteTopic(); - _channelFactory.DeleteQueue(); + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); _consumer.Dispose(); _messageProducer.Dispose(); } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); + await _messageProducer.DisposeAsync(); + } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs index a3ef2062eb..aa9c32f714 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs @@ -12,10 +12,10 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; [Trait("Category", "AWS")] -public class SqsMessageProducerSendTests : IDisposable +public class SqsMessageProducerSendTests : IDisposable, IAsyncDisposable { private readonly Message _message; - private readonly IAmAChannel _channel; + private readonly IAmAChannelSync _channel; private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; @@ -39,7 +39,7 @@ public SqsMessageProducerSendTests() var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(_topicName); - SqsSubscription subscription = new( + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, @@ -58,12 +58,12 @@ public SqsMessageProducerSendTests() var awsConnection = new AWSMessagingGatewayConnection(credentials, region); _channelFactory = new ChannelFactory(awsConnection); - _channel = _channelFactory.CreateChannel(subscription); + _channel = _channelFactory.CreateSyncChannel(subscription); _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication { - Topic = new RoutingKey(_topicName), + Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo, Deduplication = true @@ -121,9 +121,17 @@ public async Task When_posting_a_message_via_the_producer_for_fifo(string subjec public void Dispose() { - _channelFactory?.DeleteTopic(); - _channelFactory?.DeleteQueue(); - _messageProducer?.Dispose(); + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _messageProducer.DisposeAsync(); } private static DateTime RoundToSeconds(DateTime dateTime) diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs index d825fd1f13..11cea2ebca 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Amazon; using Amazon.Runtime; using Amazon.SQS.Model; @@ -9,8 +10,8 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; -[Trait("Category", "AWS")] -public class AWSAssumeQueuesTests : IDisposable +[Trait("Category", "AWS")] +public class AWSAssumeQueuesTests : IDisposable, IAsyncDisposable { private readonly ChannelFactory _channelFactory; private readonly SqsMessageConsumer _consumer; @@ -20,7 +21,7 @@ public AWSAssumeQueuesTests() var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), @@ -28,26 +29,22 @@ public AWSAssumeQueuesTests() makeChannels: OnMissingChannel.Assume, sqsType: SnsSqsType.Fifo ); - + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - + //create the topic, we want the queue to be the issue //We need to create the topic at least, to check the queues - var producer = new SqsMessageProducer(awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo - }); - - producer.ConfirmTopicExistsAsync(topicName).Wait(); - + var producer = new SqsMessageProducer(awsConnection, + new SnsPublication { MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo }); + + producer.ConfirmTopicExistsAsync(topicName).Wait(); + _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateChannel(subscription); - + var channel = _channelFactory.CreateSyncChannel(subscription); + //We need to create the topic at least, to check the queues - _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), routingKey); + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); } [Fact] @@ -56,9 +53,14 @@ public void When_queues_missing_assume_throws() //we will try to get the queue url, and fail because it does not exist Assert.Throws(() => _consumer.Receive(TimeSpan.FromMilliseconds(1000))); } - + public void Dispose() { - _channelFactory.DeleteTopic(); + _channelFactory.DeleteTopicAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs index e1669fc54b..3f17e46891 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Amazon; using Amazon.Runtime; using Amazon.SQS.Model; @@ -9,8 +10,8 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; -[Trait("Category", "AWS")] -public class AWSValidateQueuesTests : IDisposable +[Trait("Category", "AWS")] +public class AWSValidateQueuesTests : IDisposable, IAsyncDisposable { private readonly AWSMessagingGatewayConnection _awsConnection; private readonly SqsSubscription _subscription; @@ -21,7 +22,7 @@ public AWSValidateQueuesTests() var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - + _subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), @@ -29,19 +30,14 @@ public AWSValidateQueuesTests() makeChannels: OnMissingChannel.Validate, sqsType: SnsSqsType.Fifo ); - + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); _awsConnection = new AWSMessagingGatewayConnection(credentials, region); - + //We need to create the topic at least, to check the queues - var producer = new SqsMessageProducer(_awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo - }); - producer.ConfirmTopicExistsAsync(topicName).Wait(); - + var producer = new SqsMessageProducer(_awsConnection, + new SnsPublication { MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo }); + producer.ConfirmTopicExistsAsync(topicName).Wait(); } [Fact] @@ -50,11 +46,16 @@ public void When_queues_missing_verify_throws() //We have no queues so we should throw //We need to do this manually in a test - will create the channel from subscriber parameters _channelFactory = new ChannelFactory(_awsConnection); - Assert.Throws(() => _channelFactory.CreateChannel(_subscription)); + Assert.Throws(() => _channelFactory.CreateSyncChannel(_subscription)); } - + public void Dispose() { - _channelFactory.DeleteTopic(); + _channelFactory.DeleteTopicAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs index 04e7257081..8314c73dc3 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Amazon; using Amazon.Runtime; using FluentAssertions; @@ -10,13 +11,13 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; -[Trait("Category", "AWS")] +[Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class SqsRawMessageDeliveryTests : IDisposable +public class SqsRawMessageDeliveryTests : IDisposable, IAsyncDisposable { private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; - private readonly IAmAChannel _channel; + private readonly IAmAChannelSync _channel; private readonly RoutingKey _routingKey; public SqsRawMessageDeliveryTests() @@ -31,22 +32,20 @@ public SqsRawMessageDeliveryTests() var bufferSize = 10; //Set rawMessageDelivery to false - _channel = _channelFactory.CreateChannel(new SqsSubscription( + _channel = _channelFactory.CreateSyncChannel(new SqsSubscription( name: new SubscriptionName(channelName), - channelName:new ChannelName(channelName), - routingKey:_routingKey, + channelName: new ChannelName(channelName), + routingKey: _routingKey, bufferSize: bufferSize, makeChannels: OnMissingChannel.Create, rawMessageDelivery: false, sqsType: SnsSqsType.Fifo, contentBasedDeduplication: true)); - _messageProducer = new SqsMessageProducer(awsConnection, + _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo, - Deduplication = true + MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo, Deduplication = true }); } @@ -56,21 +55,15 @@ public void When_raw_message_delivery_disabled() //arrange var partitionKey = Guid.NewGuid().ToString(); var deduplicationId = Guid.NewGuid().ToString(); - + var messageHeader = new MessageHeader( - Guid.NewGuid().ToString(), - _routingKey, - MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), - replyTo: RoutingKey.Empty, + Guid.NewGuid().ToString(), + _routingKey, + MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), + replyTo: RoutingKey.Empty, contentType: "text\\plain", - partitionKey: partitionKey) - { - Bag = - { - [HeaderNames.DeduplicationId] = deduplicationId - } - }; + partitionKey: partitionKey) { Bag = { [HeaderNames.DeduplicationId] = deduplicationId } }; var customHeaderItem = new KeyValuePair("custom-header-item", "custom-header-item-value"); messageHeader.Bag.Add(customHeaderItem.Key, customHeaderItem.Value); @@ -96,12 +89,17 @@ public void When_raw_message_delivery_disabled() messageReceived.Header.Bag.Should().ContainKey(HeaderNames.DeduplicationId).And.ContainValue(deduplicationId); messageReceived.Header.Bag.Should().ContainKey(HeaderNames.MessageGroupId).And.ContainValue(partitionKey); messageReceived.Body.Value.Should().Be(messageToSent.Body.Value); - } public void Dispose() { - _channelFactory.DeleteTopic(); - _channelFactory.DeleteQueue(); + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs index 66f2ecc07f..a99fd89ead 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs @@ -13,17 +13,17 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class SqsMessageConsumerRequeueTests : IDisposable +public class SqsMessageConsumerRequeueTests : IDisposable, IAsyncDisposable { private readonly Message _message; - private readonly IAmAChannel _channel; + private readonly IAmAChannelSync _channel; private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; public SqsMessageConsumerRequeueTests() { - _myCommand = new MyCommand{Value = "Test"}; + _myCommand = new MyCommand { Value = "Test" }; const string replyTo = "http:\\queueUrl"; const string contentType = "text\\plain"; var correlationId = Guid.NewGuid().ToString(); @@ -31,33 +31,30 @@ public SqsMessageConsumerRequeueTests() var topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var partitionKey = $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( + + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, sqsType: SnsSqsType.Fifo ); - + _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); - + //Must have credentials stored in the SDK Credentials store or shared credentials file (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - + //We need to do this manually in a test - will create the channel from subscriber parameters _channelFactory = new ChannelFactory(awsConnection); - _channel = _channelFactory.CreateChannel(subscription); - - _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication - { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo - }); + _channel = _channelFactory.CreateSyncChannel(subscription); + + _messageProducer = new SqsMessageProducer(awsConnection, + new SnsPublication { MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo }); } [Fact] @@ -66,7 +63,7 @@ public void When_rejecting_a_message_through_gateway_with_requeue() _messageProducer.Send(_message); var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - + _channel.Reject(message); //Let the timeout change @@ -74,7 +71,7 @@ public void When_rejecting_a_message_through_gateway_with_requeue() //should requeue_the_message message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - + //clear the queue _channel.Acknowledge(message); @@ -83,8 +80,13 @@ public void When_rejecting_a_message_through_gateway_with_requeue() public void Dispose() { - //Clean up resources that we have created - _channelFactory.DeleteTopic(); - _channelFactory.DeleteQueue(); + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs index 13f1c0e9b2..3dc22400b2 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs @@ -1,5 +1,6 @@ using System; using System.Text.Json; +using System.Threading.Tasks; using Amazon; using Amazon.Runtime; using Amazon.Runtime.CredentialManagement; @@ -12,12 +13,12 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; [Trait("Category", "AWS")] -public class SqsMessageProducerRequeueTests : IDisposable +public class SqsMessageProducerRequeueTests : IDisposable, IAsyncDisposable { private Message _requeuedMessage; private Message _receivedMessage; private readonly IAmAMessageProducerSync _sender; - private readonly IAmAChannel _channel; + private readonly IAmAChannelSync _channel; private readonly ChannelFactory _channelFactory; private readonly Message _message; @@ -29,7 +30,7 @@ public SqsMessageProducerRequeueTests() const string contentType = "text\\plain"; var channelName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var partitionKey= $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var partitionKey = $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); var subscription = new SqsSubscription( @@ -50,15 +51,12 @@ public SqsMessageProducerRequeueTests() (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - _sender = new SqsMessageProducer(awsConnection, new SnsPublication - { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo - }); + _sender = new SqsMessageProducer(awsConnection, + new SnsPublication { MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo }); //We need to do this manually in a test - will create the channel from subscriber parameters _channelFactory = new ChannelFactory(awsConnection); - _channel = _channelFactory.CreateChannel(subscription); + _channel = _channelFactory.CreateSyncChannel(subscription); } [Fact] @@ -78,7 +76,13 @@ public void When_requeueing_a_message() public void Dispose() { - _channelFactory.DeleteTopic(); - _channelFactory.DeleteQueue(); + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs index 50146f45f2..49ca9b72cf 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs @@ -16,10 +16,10 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class SqsMessageProducerDlqTests : IDisposable +public class SqsMessageProducerDlqTests : IDisposable, IAsyncDisposable { private readonly SqsMessageProducer _sender; - private readonly IAmAChannel _channel; + private readonly IAmAChannelSync _channel; private readonly ChannelFactory _channelFactory; private readonly Message _message; private readonly AWSMessagingGatewayConnection _awsConnection; @@ -62,7 +62,7 @@ public SqsMessageProducerDlqTests() //We need to do this manually in a test - will create the channel from subscriber parameters _channelFactory = new ChannelFactory(_awsConnection); - _channel = _channelFactory.CreateChannel(subscription); + _channel = _channelFactory.CreateSyncChannel(subscription); } [Fact] @@ -87,8 +87,14 @@ public void When_requeueing_redrives_to_the_queue() public void Dispose() { - _channelFactory.DeleteTopic(); - _channelFactory.DeleteQueue(); + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); } private int GetDLQCount(string queueName) diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs index 57de059a70..c62eb0ca68 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs @@ -19,15 +19,16 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class SnsReDrivePolicySDlqTests +public class SnsReDrivePolicySDlqTests : IDisposable, IAsyncDisposable { private readonly IAmAMessagePump _messagePump; private readonly Message _message; private readonly string _dlqChannelName; - private readonly IAmAChannel _channel; + private readonly IAmAChannelSync _channel; private readonly SqsMessageProducer _sender; private readonly AWSMessagingGatewayConnection _awsConnection; private readonly SqsSubscription _subscription; + private readonly ChannelFactory _channelFactory; public SnsReDrivePolicySDlqTests() { @@ -71,19 +72,19 @@ public SnsReDrivePolicySDlqTests() //how do we send to the queue _sender = new SqsMessageProducer( - _awsConnection, - new SnsPublication - { - Topic = routingKey, - RequestType = typeof(MyDeferredCommand), + _awsConnection, + new SnsPublication + { + Topic = routingKey, + RequestType = typeof(MyDeferredCommand), MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo } ); //We need to do this manually in a test - will create the channel from subscriber parameters - ChannelFactory channelFactory = new(_awsConnection); - _channel = channelFactory.CreateChannel(_subscription); + _channelFactory = new(_awsConnection); + _channel = _channelFactory.CreateSyncChannel(_subscription); //how do we handle a command IHandleRequests handler = new MyDeferredCommandHandler(); @@ -104,12 +105,13 @@ public SnsReDrivePolicySDlqTests() var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory(_ => new MyDeferredCommandMessageMapper()), null - ); + ); messageMapperRegistry.Register(); - + + //pump messages from a channel to a handler - in essence we are building our own dispatcher in this test //pump messages from a channel to a handler - in essence we are building our own dispatcher in this test - _messagePump = new MessagePumpBlocking(provider, messageMapperRegistry, - null, new InMemoryRequestContextFactory(), _channel) + _messagePump = new Reactor(provider, messageMapperRegistry, + null, new InMemoryRequestContextFactory(), _channel) { Channel = _channel, TimeOut = TimeSpan.FromMilliseconds(5000), RequeueCount = 3 }; @@ -129,7 +131,8 @@ public int GetDLQCount(string queueName) if (response.HttpStatusCode != HttpStatusCode.OK) { - throw new AmazonSQSException($"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + throw new AmazonSQSException( + $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); } return response.Messages.Count; @@ -152,10 +155,22 @@ public async Task When_throwing_defer_action_respect_redrive() //wait for the pump to stop once it gets a quit message await Task.WhenAll(task); - + await Task.Delay(5000); //inspect the dlq GetDLQCount(_dlqChannelName).Should().Be(1); } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs index e568d397bf..f6b62107e1 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Text.Json; +using System.Threading.Tasks; using Amazon; using Amazon.Runtime; using FluentAssertions; @@ -13,8 +14,9 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] [Trait("Fragile", "CI")] - public class AWSAssumeInfrastructureTests : IDisposable, IAsyncDisposable - { private readonly Message _message; + public class AWSAssumeInfrastructureTests : IDisposable, IAsyncDisposable + { + private readonly Message _message; private readonly SqsMessageConsumer _consumer; private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; @@ -22,14 +24,14 @@ public class AWSAssumeInfrastructureTests : IDisposable, IAsyncDisposable public AWSAssumeInfrastructureTests() { - _myCommand = new MyCommand{Value = "Test"}; + _myCommand = new MyCommand { Value = "Test" }; string correlationId = Guid.NewGuid().ToString(); string replyTo = "http:\\queueUrl"; string contentType = "text\\plain"; var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - + SqsSubscription subscription = new( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), @@ -37,23 +39,23 @@ public AWSAssumeInfrastructureTests() messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Create ); - + _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - + //We need to do this manually in a test - will create the channel from subscriber parameters //This doesn't look that different from our create tests - this is because we create using the channel factory in //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) _channelFactory = new ChannelFactory(awsConnection); var channel = _channelFactory.CreateSyncChannel(subscription); - + //Now change the subscription to validate, just check what we made subscription = new( name: new SubscriptionName(channelName), @@ -62,8 +64,9 @@ public AWSAssumeInfrastructureTests() messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Assume ); - - _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Assume}); + + _messageProducer = new SqsMessageProducer(awsConnection, + new SnsPublication { MakeChannels = OnMissingChannel.Assume }); _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); } @@ -73,9 +76,9 @@ public void When_infastructure_exists_can_assume() { //arrange _messageProducer.Send(_message); - + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); - + //Assert var message = messages.First(); message.Id.Should().Be(_myCommand.Id); @@ -83,7 +86,7 @@ public void When_infastructure_exists_can_assume() //clear the queue _consumer.Acknowledge(message); } - + public void Dispose() { //Clean up resources that we have created @@ -96,5 +99,5 @@ public async ValueTask DisposeAsync() await _channelFactory.DeleteTopicAsync(); await _channelFactory.DeleteQueueAsync(); } - } + } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs index a71aeedd0e..d5365618cd 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs @@ -19,7 +19,7 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] [Trait("Fragile", "CI")] - public class SnsReDrivePolicySDlqTests + public class SnsReDrivePolicySDlqTests : IDisposable, IAsyncDisposable { private readonly IAmAMessagePump _messagePump; private readonly Message _message; From c9b7e83882ea786058c0292ba3dda96ea636e816 Mon Sep 17 00:00:00 2001 From: Rafael Lillo Date: Mon, 30 Dec 2024 18:41:41 +0000 Subject: [PATCH 05/18] GH-1294 Improve support to LocalStack --- docker-compose-localstack.yaml | 1 + .../ChannelFactory.cs | 11 +- .../Helpers/GatewayFactory.cs | 40 +++ ...essage_consumer_reads_multiple_messages.cs | 176 ----------- .../When_infastructure_exists_can_verify.cs | 120 -------- .../When_requeueing_redrives_to_the_dlq.cs | 119 -------- .../Fifo/When_topic_missing_verify_throws.cs | 45 --- ...essage_consumer_reads_multiple_messages.cs | 206 +++++++------ ..._consumer_reads_multiple_messages_async.cs | 131 +++++++++ .../When_customising_aws_client_config.cs | 153 +++++----- ...en_customising_aws_client_config_async.cs} | 59 ++-- .../When_infastructure_exists_can_assume.cs | 173 ++++++----- ..._infastructure_exists_can_assume_async.cs} | 72 ++--- .../When_infastructure_exists_can_verify.cs | 204 +++++++------ ..._infastructure_exists_can_verify_by_arn.cs | 187 ++++++------ ...ructure_exists_can_verify_by_convention.cs | 198 ++++++------- ..._infrastructure_exists_can_verify_async.cs | 7 +- ...ructure_exists_can_verify_by_arn_async.cs} | 71 ++--- ...ucture_exists_can_verify_by_convention.cs} | 61 ++-- ...ing_a_message_via_the_messaging_gateway.cs | 173 ++++++----- ...essage_via_the_messaging_gateway_async.cs} | 86 ++---- .../When_queues_missing_assume_throws.cs | 96 +++--- ...hen_queues_missing_assume_throws_async.cs} | 32 +- .../When_queues_missing_verify_throws.cs | 92 +++--- ...hen_queues_missing_verify_throws_async.cs} | 34 +-- .../When_raw_message_delivery_disabled.cs | 138 +++++---- ...en_raw_message_delivery_disabled_async.cs} | 64 ++-- ..._a_message_through_gateway_with_requeue.cs | 122 ++++---- ...age_through_gateway_with_requeue_async.cs} | 54 ++-- .../Standard/When_requeueing_a_message.cs | 116 ++++---- .../When_requeueing_a_message_async.cs} | 48 ++- .../When_requeueing_redrives_to_the_dlq.cs | 162 +++++------ ...en_requeueing_redrives_to_the_dlq_async.cs | 113 ++++++++ ...n_throwing_defer_action_respect_redrive.cs | 274 +++++++++--------- ...ing_defer_action_respect_redrive_async.cs} | 89 ++---- .../When_topic_missing_verify_throws.cs | 60 ++-- .../When_topic_missing_verify_throws_async.cs | 43 +++ ..._consumer_reads_multiple_messages_async.cs | 135 --------- ...hen_customising_aws_client_config_async.cs | 96 ------ ...n_infastructure_exists_can_assume_async.cs | 100 ------- ...tructure_exists_can_verify_by_arn_async.cs | 118 -------- ...ructure_exists_can_verify_by_convention.cs | 108 ------- ...message_via_the_messaging_gateway_async.cs | 113 -------- ...When_queues_missing_assume_throws_async.cs | 70 ----- ...When_queues_missing_verify_throws_async.cs | 59 ---- ...hen_raw_message_delivery_disabled_async.cs | 97 ------- ...sage_through_gateway_with_requeue_async.cs | 90 ------ .../When_requeueing_a_message_async.cs | 86 ------ ...en_requeueing_redrives_to_the_dlq_async.cs | 115 -------- ...wing_defer_action_respect_redrive_async.cs | 153 ---------- .../When_topic_missing_verify_throws_async.cs | 45 --- 51 files changed, 1762 insertions(+), 3453 deletions(-) create mode 100644 tests/Paramore.Brighter.AWS.Tests/Helpers/GatewayFactory.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages_async.cs rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo/When_customising_aws_client_config.cs => Standard/When_customising_aws_client_config_async.cs} (59%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo/When_infastructure_exists_can_assume.cs => Standard/When_infastructure_exists_can_assume_async.cs} (59%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{ => Standard}/When_infrastructure_exists_can_verify_async.cs (93%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo/When_infastructure_exists_can_verify_by_arn.cs => Standard/When_infrastructure_exists_can_verify_by_arn_async.cs} (59%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo/When_infastructure_exists_can_verify_by_convention.cs => Standard/When_infrastructure_exists_can_verify_by_convention.cs} (53%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo/When_posting_a_message_via_the_messaging_gateway.cs => Standard/When_posting_a_message_via_the_messaging_gateway_async.cs} (54%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo/When_queues_missing_assume_throws.cs => Standard/When_queues_missing_assume_throws_async.cs} (60%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo/When_queues_missing_verify_throws.cs => Standard/When_queues_missing_verify_throws_async.cs} (53%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo/When_raw_message_delivery_disabled.cs => Standard/When_raw_message_delivery_disabled_async.cs} (52%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs => Standard/When_rejecting_a_message_through_gateway_with_requeue_async.cs} (50%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo/When_requeueing_a_message.cs => Standard/When_requeueing_a_message_async.cs} (51%) create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq_async.cs rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo/When_throwing_defer_action_respect_redrive.cs => Standard/When_throwing_defer_action_respect_redrive_async.cs} (54%) create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws_async.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_a_message_consumer_reads_multiple_messages_async.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_customising_aws_client_config_async.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_assume_async.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infrastructure_exists_can_verify_by_arn_async.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infrastructure_exists_can_verify_by_convention.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_posting_a_message_via_the_messaging_gateway_async.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_queues_missing_assume_throws_async.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_queues_missing_verify_throws_async.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_raw_message_delivery_disabled_async.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_rejecting_a_message_through_gateway_with_requeue_async.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_requeueing_a_message_async.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_requeueing_redrives_to_the_dlq_async.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_throwing_defer_action_respect_redrive_async.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_topic_missing_verify_throws_async.cs diff --git a/docker-compose-localstack.yaml b/docker-compose-localstack.yaml index 04b60b0578..2f4208247f 100644 --- a/docker-compose-localstack.yaml +++ b/docker-compose-localstack.yaml @@ -6,6 +6,7 @@ services: environment: # LocalStack configuration: https://docs.localstack.cloud/references/configuration/ - "SERVICES=s3,sqs,sns,dynamodb" + - "DEBUG=1" ports: - "4566:4566" # LocalStack Gateway - "4510-4559:4510-4559" # External services port range diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs index a1597bbb8a..7d21d062ea 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs @@ -209,8 +209,8 @@ private async Task EnsureQueueAsync() if (_subscription.MakeChannels == OnMissingChannel.Assume) return; - - using var sqsClient = new AmazonSQSClient(AwsConnection.Credentials, AwsConnection.Region); + + using var sqsClient = new AWSClientFactory(AwsConnection).CreateSqsClient(); var queueName = _subscription.ChannelName.ToValidSQSQueueName(); var topicName = _subscription.RoutingKey.ToValidSNSTopicName(); @@ -309,7 +309,7 @@ private async Task CreateQueueAsync(AmazonSQSClient sqsClient) if (!string.IsNullOrEmpty(_queueUrl)) { s_logger.LogDebug("Queue created: {URL}", _queueUrl); - using var snsClient = new AmazonSimpleNotificationServiceClient(AwsConnection.Credentials, AwsConnection.Region); + using var snsClient = new AWSClientFactory(AwsConnection).CreateSnsClient(); await CheckSubscriptionAsync(_subscription.MakeChannels, sqsClient, snsClient); } else @@ -452,9 +452,14 @@ private async Task SubscribeToTopicAsync(AmazonSQSClient sqsClient, AmazonSimple exists = false; return true; } + return false; }); } + catch (QueueDoesNotExistException) + { + exists = false; + } return (exists, queueUrl); } diff --git a/tests/Paramore.Brighter.AWS.Tests/Helpers/GatewayFactory.cs b/tests/Paramore.Brighter.AWS.Tests/Helpers/GatewayFactory.cs new file mode 100644 index 0000000000..76cc08cf87 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/Helpers/GatewayFactory.cs @@ -0,0 +1,40 @@ +using System; +using System.Configuration; +using Amazon; +using Amazon.Runtime; +using Paramore.Brighter.MessagingGateway.AWSSQS; + +namespace Paramore.Brighter.AWS.Tests.Helpers; + +public class GatewayFactory +{ + public static AWSMessagingGatewayConnection CreateFactory() + { + var (credentials, region) = CredentialsChain.GetAwsCredentials(); + return CreateFactory(credentials, region, config => { }); + } + + public static AWSMessagingGatewayConnection CreateFactory(Action clientConfig) + { + var (credentials, region) = CredentialsChain.GetAwsCredentials(); + return CreateFactory(credentials, region, clientConfig); + } + + public static AWSMessagingGatewayConnection CreateFactory( + AWSCredentials credentials, + RegionEndpoint region, + Action? config = null) + { + return new AWSMessagingGatewayConnection(credentials, region, + cfg => + { + config?.Invoke(cfg); + + var serviceURL = Environment.GetEnvironmentVariable("LOCALSTACK_SERVICE_URL"); + if (!string.IsNullOrWhiteSpace(serviceURL)) + { + cfg.ServiceURL = serviceURL; + } + }); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs deleted file mode 100644 index 27edda7244..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; - -[Trait("Category", "AWS")] -[Trait("Fragile", "CI")] -public class SQSBufferedConsumerTests : IDisposable, IAsyncDisposable -{ - private readonly SqsMessageProducer _messageProducer; - private readonly SqsMessageConsumer _consumer; - private readonly string _topicName; - private readonly ChannelFactory _channelFactory; - private const string _contentType = "text\\plain"; - private const int _bufferSize = 3; - private const int _messageCount = 4; - - public SQSBufferedConsumerTests() - { - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - _channelFactory = new ChannelFactory(awsConnection); - - var channelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _topicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - - //we need the channel to create the queues and notifications - var routingKey = new RoutingKey(_topicName); - - var channel = _channelFactory.CreateSyncChannel(new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - bufferSize: _bufferSize, - makeChannels: OnMissingChannel.Create, - sqsType: SnsSqsType.Fifo, - contentBasedDeduplication: true - )); - - //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel - //just for the tests, so create a new consumer from the properties - _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), - _bufferSize); - _messageProducer = new SqsMessageProducer(awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo, Deduplication = true - }); - } - - [Fact] - public async Task When_a_message_consumer_reads_multiple_messages_per_group() - { - var routingKey = new RoutingKey(_topicName); - var messageGroupIdOne = "123"; - - var messageOne = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType, - partitionKey: messageGroupIdOne), - new MessageBody("test content one") - ); - - var messageTwo = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType, - partitionKey: messageGroupIdOne), - new MessageBody("test content two") - ); - - var messageThree = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType, - partitionKey: messageGroupIdOne), - new MessageBody("test content three") - ); - - var messageFour = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType, - partitionKey: messageGroupIdOne), - new MessageBody("test content four") - ); - var messageGroupIdTwo = "1234"; - var messageFive = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType, - partitionKey: messageGroupIdTwo), - new MessageBody("test content five") - ); - - var messageSix = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType, - partitionKey: messageGroupIdTwo), - new MessageBody("test content six") - ); - - var messageSeven = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType, - partitionKey: messageGroupIdTwo) { Bag = { [HeaderNames.DeduplicationId] = "123" } }, - new MessageBody("test content seven") - ); - - var messageEight = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: _contentType, - partitionKey: messageGroupIdTwo) { Bag = { [HeaderNames.DeduplicationId] = "123" } }, - new MessageBody("test content eight") - ); - - //send MESSAGE_COUNT messages - _messageProducer.Send(messageOne); - _messageProducer.Send(messageTwo); - _messageProducer.Send(messageThree); - _messageProducer.Send(messageFour); - _messageProducer.Send(messageFive); - _messageProducer.Send(messageSix); - _messageProducer.Send(messageSeven); - _messageProducer.Send(messageEight); - - int iteration = 0; - var messagesReceived = new List(); - var messagesReceivedCount = messagesReceived.Count; - do - { - iteration++; - - //retrieve messages - var messages = _consumer.Receive(TimeSpan.FromMilliseconds(10000)); - - // should not receive more number of message group - messages.Length.Should().BeLessOrEqualTo(2); - - var moreMessages = messages.Where(m => m.Header.MessageType == MessageType.MT_COMMAND); - foreach (var message in moreMessages) - { - messagesReceived.Add(message); - _consumer.Acknowledge(message); - } - - messagesReceivedCount = messagesReceived.Count; - - await Task.Delay(1000); - } while ((iteration <= 7) && (messagesReceivedCount < _messageCount)); - - - messagesReceivedCount.Should().Be(7); - } - - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - _messageProducer.Dispose(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - await ((IAmAMessageProducerAsync)_messageProducer).DisposeAsync(); - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs deleted file mode 100644 index 73eafb7097..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; - -[Trait("Category", "AWS")] -[Trait("Fragile", "CI")] -public class AWSValidateInfrastructureTests : IDisposable, IAsyncDisposable -{ - private readonly Message _message; - private readonly IAmAMessageConsumerSync _consumer; - private readonly SqsMessageProducer _messageProducer; - private readonly MyCommand _myCommand; - private readonly ChannelFactory _channelFactory; - - public AWSValidateInfrastructureTests() - { - _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - const string replyTo = "http:\\queueUrl"; - const string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var partitionKey = $"Partition-Key-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - var subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - makeChannels: OnMissingChannel.Create, - sqsType: SnsSqsType.Fifo - ); - - _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), - new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) - ); - - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - //We need to do this manually in a test - will create the channel from subscriber parameters - //This doesn't look that different from our create tests - this is because we create using the channel factory in - //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) - _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateSyncChannel(subscription); - - //Now change the subscription to validate, just check what we made - subscription = new( - name: new SubscriptionName(channelName), - channelName: channel.Name, - routingKey: routingKey, - findTopicBy: TopicFindBy.Name, - makeChannels: OnMissingChannel.Validate, - sqsType: SnsSqsType.Fifo, - contentBasedDeduplication: true - ); - - _messageProducer = new SqsMessageProducer( - awsConnection, - new SnsPublication - { - FindTopicBy = TopicFindBy.Name, - MakeChannels = OnMissingChannel.Validate, - Topic = new RoutingKey(topicName), - SnsType = SnsSqsType.Fifo, - Deduplication = true - } - ); - - _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); - } - - [Fact] - public async Task When_infrastructure_exists_can_verify_for_fifo() - { - //arrange - _messageProducer.Send(_message); - - await Task.Delay(1000); - - var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); - - //Assert - var message = messages.First(); - message.Id.Should().Be(_myCommand.Id); - - //clear the queue - _consumer.Acknowledge(message); - } - - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - _consumer.Dispose(); - _messageProducer.Dispose(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); - await _messageProducer.DisposeAsync(); - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs deleted file mode 100644 index 49ca9b72cf..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Net; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using Amazon.SQS; -using Amazon.SQS.Model; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; - -[Trait("Category", "AWS")] -[Trait("Fragile", "CI")] -public class SqsMessageProducerDlqTests : IDisposable, IAsyncDisposable -{ - private readonly SqsMessageProducer _sender; - private readonly IAmAChannelSync _channel; - private readonly ChannelFactory _channelFactory; - private readonly Message _message; - private readonly AWSMessagingGatewayConnection _awsConnection; - private readonly string _dlqChannelName; - - public SqsMessageProducerDlqTests() - { - var myCommand = new MyCommand { Value = "Test" }; - var correlationId = Guid.NewGuid().ToString(); - const string replyTo = "http:\\queueUrl"; - const string contentType = "text\\plain"; - var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var partitionKey = $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _dlqChannelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - var subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - redrivePolicy: new RedrivePolicy(_dlqChannelName, 2), - sqsType: SnsSqsType.Fifo - ); - - _message = new Message( - new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), - new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) - ); - - //Must have credentials stored in the SDK Credentials store or shared credentials file - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - _awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - _sender = new SqsMessageProducer(_awsConnection, - new SnsPublication { MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo }); - - _sender.ConfirmTopicExistsAsync(topicName).Wait(); - - //We need to do this manually in a test - will create the channel from subscriber parameters - _channelFactory = new ChannelFactory(_awsConnection); - _channel = _channelFactory.CreateSyncChannel(subscription); - } - - [Fact] - public void When_requeueing_redrives_to_the_queue() - { - _sender.Send(_message); - var receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - _channel.Requeue(receivedMessage); - - receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - _channel.Requeue(receivedMessage); - - //should force us into the dlq - receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - _channel.Requeue(receivedMessage); - - Task.Delay(5000); - - //inspect the dlq - GetDLQCount(_dlqChannelName).Should().Be(1); - } - - public void Dispose() - { - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } - - private int GetDLQCount(string queueName) - { - using var sqsClient = new AmazonSQSClient(_awsConnection.Credentials, _awsConnection.Region); - var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); - var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest - { - QueueUrl = queueUrlResponse.QueueUrl, - WaitTimeSeconds = 5, - MessageAttributeNames = ["All", "ApproximateReceiveCount"] - }).GetAwaiter().GetResult(); - - if (response.HttpStatusCode != HttpStatusCode.OK) - { - throw new AmazonSQSException( - $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); - } - - return response.Messages.Count; - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs deleted file mode 100644 index 1ed01318c0..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using Amazon; -using Amazon.Runtime; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; - -[Trait("Category", "AWS")] -public class AWSValidateMissingTopicTests -{ - private readonly AWSMessagingGatewayConnection _awsConnection; - private readonly RoutingKey _routingKey; - - public AWSValidateMissingTopicTests() - { - var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _routingKey = new RoutingKey(topicName); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - _awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - //Because we don't use channel factory to create the infrastructure -it won't exist - } - - [Theory] - [InlineData(SnsSqsType.Standard, null)] - [InlineData(SnsSqsType.Fifo, "123")] - public void When_topic_missing_verify_throws(SnsSqsType type, string partitionKey) - { - //arrange - var producer = new SqsMessageProducer(_awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Validate, - SnsType = type - }); - - //act && assert - Assert.Throws(() => producer.Send(new Message( - new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text") { PartitionKey = partitionKey}, - new MessageBody("Test")))); - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages.cs index f83b990a3d..6c0e56a7a7 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages.cs @@ -2,143 +2,137 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SQSBufferedConsumerTests : IDisposable, IAsyncDisposable { - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class SQSBufferedConsumerTests : IDisposable, IAsyncDisposable - { - private readonly SqsMessageProducer _messageProducer; - private readonly SqsMessageConsumer _consumer; - private readonly string _topicName; - private readonly ChannelFactory _channelFactory; - private const string ContentType = "text\\plain"; - private const int BufferSize = 3; - private const int MessageCount = 4; + private readonly SqsMessageProducer _messageProducer; + private readonly SqsMessageConsumer _consumer; + private readonly string _topicName; + private readonly ChannelFactory _channelFactory; + private const string ContentType = "text\\plain"; + private const int BufferSize = 3; + private const int MessageCount = 4; - public SQSBufferedConsumerTests() - { - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + public SQSBufferedConsumerTests() + { + var awsConnection = GatewayFactory.CreateFactory(); - _channelFactory = new ChannelFactory(awsConnection); - var channelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _topicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _channelFactory = new ChannelFactory(awsConnection); + var channelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _topicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - //we need the channel to create the queues and notifications - var routingKey = new RoutingKey(_topicName); + //we need the channel to create the queues and notifications + var routingKey = new RoutingKey(_topicName); - var channel = _channelFactory.CreateSyncChannel(new SqsSubscription( - name: new SubscriptionName(channelName), - channelName:new ChannelName(channelName), - routingKey:routingKey, - bufferSize: BufferSize, - makeChannels: OnMissingChannel.Create - )); + var channel = _channelFactory.CreateSyncChannel(new SqsSubscription( + name: new SubscriptionName(channelName), + channelName:new ChannelName(channelName), + routingKey:routingKey, + bufferSize: BufferSize, + makeChannels: OnMissingChannel.Create + )); - //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel - //just for the tests, so create a new consumer from the properties - _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), BufferSize); - _messageProducer = new SqsMessageProducer(awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create - }); - } + //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel + //just for the tests, so create a new consumer from the properties + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), BufferSize); + _messageProducer = new SqsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create + }); + } - [Fact] - public async Task When_a_message_consumer_reads_multiple_messages() - { - var routingKey = new RoutingKey(_topicName); + [Fact] + public async Task When_a_message_consumer_reads_multiple_messages() + { + var routingKey = new RoutingKey(_topicName); - var messageOne = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: ContentType), - new MessageBody("test content one") - ); + var messageOne = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType), + new MessageBody("test content one") + ); - var messageTwo= new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: ContentType), - new MessageBody("test content two") - ); + var messageTwo= new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType), + new MessageBody("test content two") + ); - var messageThree= new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: ContentType), - new MessageBody("test content three") - ); + var messageThree= new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType), + new MessageBody("test content three") + ); - var messageFour= new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: ContentType), - new MessageBody("test content four") - ); + var messageFour= new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType), + new MessageBody("test content four") + ); - //send MESSAGE_COUNT messages - _messageProducer.Send(messageOne); - _messageProducer.Send(messageTwo); - _messageProducer.Send(messageThree); - _messageProducer.Send(messageFour); + //send MESSAGE_COUNT messages + _messageProducer.Send(messageOne); + _messageProducer.Send(messageTwo); + _messageProducer.Send(messageThree); + _messageProducer.Send(messageFour); - int iteration = 0; - var messagesReceived = new List(); - var messagesReceivedCount = messagesReceived.Count; - do - { - iteration++; - var outstandingMessageCount = MessageCount - messagesReceivedCount; + int iteration = 0; + var messagesReceived = new List(); + var messagesReceivedCount = messagesReceived.Count; + do + { + iteration++; + var outstandingMessageCount = MessageCount - messagesReceivedCount; - //retrieve messages - var messages = _consumer.Receive(TimeSpan.FromMilliseconds(10000)); + //retrieve messages + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(10000)); - messages.Length.Should().BeLessOrEqualTo(outstandingMessageCount); + messages.Length.Should().BeLessOrEqualTo(outstandingMessageCount); - //should not receive more than buffer in one hit - messages.Length.Should().BeLessOrEqualTo(BufferSize); + //should not receive more than buffer in one hit + messages.Length.Should().BeLessOrEqualTo(BufferSize); - var moreMessages = messages.Where(m => m.Header.MessageType == MessageType.MT_COMMAND); - foreach (var message in moreMessages) - { - messagesReceived.Add(message); - _consumer.Acknowledge(message); - } + var moreMessages = messages.Where(m => m.Header.MessageType == MessageType.MT_COMMAND); + foreach (var message in moreMessages) + { + messagesReceived.Add(message); + _consumer.Acknowledge(message); + } - messagesReceivedCount = messagesReceived.Count; + messagesReceivedCount = messagesReceived.Count; - await Task.Delay(1000); + await Task.Delay(1000); - } while ((iteration <= 5) && (messagesReceivedCount < MessageCount)); + } while ((iteration <= 5) && (messagesReceivedCount < MessageCount)); - messagesReceivedCount.Should().Be(4); + messagesReceivedCount.Should().Be(4); - } + } - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - _messageProducer.Dispose(); - } + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _messageProducer.Dispose(); + } - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - await ((IAmAMessageProducerAsync) _messageProducer).DisposeAsync(); - } + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageProducerAsync) _messageProducer).DisposeAsync(); } } - - diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages_async.cs new file mode 100644 index 0000000000..d75a11b6af --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages_async.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SQSBufferedConsumerTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly SqsMessageProducer _messageProducer; + private readonly SqsMessageConsumer _consumer; + private readonly string _topicName; + private readonly ChannelFactory _channelFactory; + private const string ContentType = "text\\plain"; + private const int BufferSize = 3; + private const int MessageCount = 4; + + public SQSBufferedConsumerTestsAsync() + { + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + var channelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _topicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + + //we need the channel to create the queues and notifications + var routingKey = new RoutingKey(_topicName); + + var channel = _channelFactory.CreateAsyncChannelAsync(new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + bufferSize: BufferSize, + makeChannels: OnMissingChannel.Create + )).GetAwaiter().GetResult(); + + //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel + //just for the tests, so create a new consumer from the properties + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), BufferSize); + _messageProducer = new SqsMessageProducer(awsConnection, + new SnsPublication { MakeChannels = OnMissingChannel.Create }); + } + + [Fact] + public async Task When_a_message_consumer_reads_multiple_messages_async() + { + var routingKey = new RoutingKey(_topicName); + + var messageOne = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType), + new MessageBody("test content one") + ); + + var messageTwo = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType), + new MessageBody("test content two") + ); + + var messageThree = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType), + new MessageBody("test content three") + ); + + var messageFour = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType), + new MessageBody("test content four") + ); + + //send MESSAGE_COUNT messages + await _messageProducer.SendAsync(messageOne); + await _messageProducer.SendAsync(messageTwo); + await _messageProducer.SendAsync(messageThree); + await _messageProducer.SendAsync(messageFour); + + int iteration = 0; + var messagesReceived = new List(); + var messagesReceivedCount = messagesReceived.Count; + do + { + iteration++; + var outstandingMessageCount = MessageCount - messagesReceivedCount; + + //retrieve messages + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(10000)); + + messages.Length.Should().BeLessOrEqualTo(outstandingMessageCount); + + //should not receive more than buffer in one hit + messages.Length.Should().BeLessOrEqualTo(BufferSize); + + var moreMessages = messages.Where(m => m.Header.MessageType == MessageType.MT_COMMAND); + foreach (var message in moreMessages) + { + messagesReceived.Add(message); + await _consumer.AcknowledgeAsync(message); + } + + messagesReceivedCount = messagesReceived.Count; + + await Task.Delay(1000); + + } while ((iteration <= 5) && (messagesReceivedCount < MessageCount)); + + messagesReceivedCount.Should().Be(4); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _messageProducer.DisposeAsync(); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().GetAwaiter().GetResult(); + _channelFactory.DeleteQueueAsync().GetAwaiter().GetResult(); + _messageProducer.DisposeAsync().GetAwaiter().GetResult(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs index 214e73f3fa..8fda695e4a 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs @@ -1,96 +1,93 @@ using System; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +public class CustomisingAwsClientConfigTests : IDisposable, IAsyncDisposable { - [Trait("Category", "AWS")] - public class CustomisingAwsClientConfigTests : IDisposable, IAsyncDisposable + private readonly Message _message; + private readonly IAmAChannelSync _channel; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + + private readonly InterceptingDelegatingHandler _publishHttpHandler = new(); + private readonly InterceptingDelegatingHandler _subscribeHttpHandler = new(); + + public CustomisingAwsClientConfigTests() { - private readonly Message _message; - private readonly IAmAChannelSync _channel; - private readonly SqsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; + MyCommand myCommand = new() { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); - private readonly InterceptingDelegatingHandler _publishHttpHandler = new(); - private readonly InterceptingDelegatingHandler _subscribeHttpHandler = new(); + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + messagePumpType: MessagePumpType.Reactor, + routingKey: routingKey + ); - public CustomisingAwsClientConfigTests() - { - MyCommand myCommand = new() {Value = "Test"}; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - messagePumpType: MessagePumpType.Reactor, - routingKey: routingKey - ); - - _message = new Message( - new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) myCommand, JsonSerialisationOptions.Options)) - ); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var subscribeAwsConnection = new AWSMessagingGatewayConnection(credentials, region, config => - { - config.HttpClientFactory = new InterceptingHttpClientFactory(_subscribeHttpHandler); - }); - - _channelFactory = new ChannelFactory(subscribeAwsConnection); - _channel = _channelFactory.CreateSyncChannel(subscription); - - var publishAwsConnection = new AWSMessagingGatewayConnection(credentials, region, config => - { - config.HttpClientFactory = new InterceptingHttpClientFactory(_publishHttpHandler); - }); - - _messageProducer = new SqsMessageProducer(publishAwsConnection, new SnsPublication{Topic = new RoutingKey(topicName), MakeChannels = OnMissingChannel.Create}); - } - - [Fact] - public async Task When_customising_aws_client_config() - { - //arrange - _messageProducer.Send(_message); - - await Task.Delay(1000); - - var message =_channel.Receive(TimeSpan.FromMilliseconds(5000)); - - //clear the queue - _channel.Acknowledge(message); - - //publish_and_subscribe_should_use_custom_http_client_factory - _publishHttpHandler.RequestCount.Should().BeGreaterThan(0); - _subscribeHttpHandler.RequestCount.Should().BeGreaterThan(0); - } - - public void Dispose() + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + var subscribeAwsConnection = GatewayFactory.CreateFactory(config => { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - } + config.HttpClientFactory = new InterceptingHttpClientFactory(_subscribeHttpHandler); + }); - public async ValueTask DisposeAsync() + _channelFactory = new ChannelFactory(subscribeAwsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); + + var publishAwsConnection = GatewayFactory.CreateFactory(config => { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } + config.HttpClientFactory = new InterceptingHttpClientFactory(_publishHttpHandler); + }); + + _messageProducer = new SqsMessageProducer(publishAwsConnection, + new SnsPublication { Topic = new RoutingKey(topicName), MakeChannels = OnMissingChannel.Create }); + } + + [Fact] + public async Task When_customising_aws_client_config() + { + //arrange + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + //clear the queue + _channel.Acknowledge(message); + + //publish_and_subscribe_should_use_custom_http_client_factory + _publishHttpHandler.RequestCount.Should().BeGreaterThan(0); + _subscribeHttpHandler.RequestCount.Should().BeGreaterThan(0); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_customising_aws_client_config.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config_async.cs similarity index 59% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_customising_aws_client_config.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config_async.cs index 5ab95554d9..0d47364099 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_customising_aws_client_config.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config_async.cs @@ -1,85 +1,76 @@ using System; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Category", "AWS")] -public class CustomisingAwsClientConfigTests : IDisposable, IAsyncDisposable +public class CustomisingAwsClientConfigTestsAsync : IDisposable, IAsyncDisposable { private readonly Message _message; - private readonly IAmAChannelSync _channel; + private readonly IAmAChannelAsync _channel; private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly InterceptingDelegatingHandler _publishHttpHandler = new(); private readonly InterceptingDelegatingHandler _subscribeHttpHandler = new(); - public CustomisingAwsClientConfigTests() + public CustomisingAwsClientConfigTestsAsync() { - MyCommand myCommand = new() { Value = "Test" }; + MyCommand myCommand = new() {Value = "Test"}; string correlationId = Guid.NewGuid().ToString(); - const string replyTo = "http:\\queueUrl"; - const string contentType = "text\\plain"; + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var partitionKey = $"Partition-Key-{Guid.NewGuid().ToString()}".Truncate(45); - + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - + SqsSubscription subscription = new( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), - routingKey: routingKey, - sqsType: SnsSqsType.Fifo + messagePumpType: MessagePumpType.Proactor, + routingKey: routingKey ); - + _message = new Message( new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), - new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object) myCommand, JsonSerialisationOptions.Options)) ); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var subscribeAwsConnection = new AWSMessagingGatewayConnection(credentials, region, config => + var subscribeAwsConnection = GatewayFactory.CreateFactory(config => { config.HttpClientFactory = new InterceptingHttpClientFactory(_subscribeHttpHandler); }); - + _channelFactory = new ChannelFactory(subscribeAwsConnection); - _channel = _channelFactory.CreateSyncChannel(subscription); + _channel = _channelFactory.CreateAsyncChannel(subscription); - var publishAwsConnection = new AWSMessagingGatewayConnection(credentials, region, config => + var publishAwsConnection = GatewayFactory.CreateFactory(config => { config.HttpClientFactory = new InterceptingHttpClientFactory(_publishHttpHandler); }); - _messageProducer = new SqsMessageProducer(publishAwsConnection, - new SnsPublication - { - Topic = new RoutingKey(topicName), MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo - }); + _messageProducer = new SqsMessageProducer(publishAwsConnection, new SnsPublication{Topic = new RoutingKey(topicName), MakeChannels = OnMissingChannel.Create}); } [Fact] public async Task When_customising_aws_client_config() { //arrange - _messageProducer.Send(_message); - + await _messageProducer.SendAsync(_message); + await Task.Delay(1000); - - var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - + + var message =await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + //clear the queue - _channel.Acknowledge(message); + await _channel.AcknowledgeAsync(message); //publish_and_subscribe_should_use_custom_http_client_factory _publishHttpHandler.RequestCount.Should().BeGreaterThan(0); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs index f6b62107e1..36bce40beb 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs @@ -2,102 +2,97 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSAssumeInfrastructureTests : IDisposable, IAsyncDisposable { - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class AWSAssumeInfrastructureTests : IDisposable, IAsyncDisposable + private readonly Message _message; + private readonly SqsMessageConsumer _consumer; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSAssumeInfrastructureTests() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Assume + ); + + _messageProducer = new SqsMessageProducer(awsConnection, + new SnsPublication { MakeChannels = OnMissingChannel.Assume }); + + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); + } + + [Fact] + public void When_infastructure_exists_can_assume() + { + //arrange + _messageProducer.Send(_message); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() { - private readonly Message _message; - private readonly SqsMessageConsumer _consumer; - private readonly SqsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly MyCommand _myCommand; - - public AWSAssumeInfrastructureTests() - { - _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Create - ); - - _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) - ); - - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - //We need to do this manually in a test - will create the channel from subscriber parameters - //This doesn't look that different from our create tests - this is because we create using the channel factory in - //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) - _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateSyncChannel(subscription); - - //Now change the subscription to validate, just check what we made - subscription = new( - name: new SubscriptionName(channelName), - channelName: channel.Name, - routingKey: routingKey, - messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Assume - ); - - _messageProducer = new SqsMessageProducer(awsConnection, - new SnsPublication { MakeChannels = OnMissingChannel.Assume }); - - _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); - } - - [Fact] - public void When_infastructure_exists_can_assume() - { - //arrange - _messageProducer.Send(_message); - - var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); - - //Assert - var message = messages.First(); - message.Id.Should().Be(_myCommand.Id); - - //clear the queue - _consumer.Acknowledge(message); - } - - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume_async.cs similarity index 59% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume_async.cs index 4d9376e529..5362394df8 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume_async.cs @@ -2,93 +2,85 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class AWSAssumeInfrastructureTests : IDisposable, IAsyncDisposable -{ - private readonly Message _message; - private readonly MyCommand _myCommand; +public class AWSAssumeInfrastructureTestsAsync : IDisposable, IAsyncDisposable +{ private readonly Message _message; private readonly SqsMessageConsumer _consumer; private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; - public AWSAssumeInfrastructureTests() + public AWSAssumeInfrastructureTestsAsync() { - _myCommand = new MyCommand { Value = "Test" }; + _myCommand = new MyCommand{Value = "Test"}; string correlationId = Guid.NewGuid().ToString(); - const string replyTo = "http:\\queueUrl"; - const string contentType = "text\\plain"; - + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var partitionKey = $"Partition-Key-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - - var subscription = new SqsSubscription( + + SqsSubscription subscription = new( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, - makeChannels: OnMissingChannel.Create, - sqsType: SnsSqsType.Fifo + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create ); - + _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), - new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) ); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - + var awsConnection = GatewayFactory.CreateFactory(); + //We need to do this manually in a test - will create the channel from subscriber parameters //This doesn't look that different from our create tests - this is because we create using the channel factory in //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateSyncChannel(subscription); - + var channel = _channelFactory.CreateAsyncChannel(subscription); + //Now change the subscription to validate, just check what we made subscription = new( name: new SubscriptionName(channelName), channelName: channel.Name, routingKey: routingKey, - makeChannels: OnMissingChannel.Assume, - sqsType: SnsSqsType.Fifo + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Assume ); - - _messageProducer = new SqsMessageProducer(awsConnection, - new SnsPublication { MakeChannels = OnMissingChannel.Assume, SnsType = SnsSqsType.Fifo }); + + _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Assume}); _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); } [Fact] - public void When_infastructure_exists_can_assume() + public async Task When_infastructure_exists_can_assume() { //arrange - _messageProducer.Send(_message); - - var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); - + await _messageProducer.SendAsync(_message); + + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + //Assert var message = messages.First(); message.Id.Should().Be(_myCommand.Id); //clear the queue - _consumer.Acknowledge(message); + await _consumer.AcknowledgeAsync(message); } - + public void Dispose() { //Clean up resources that we have created diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify.cs index 1cb5504015..9b6b938204 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify.cs @@ -2,115 +2,111 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureTests : IDisposable, IAsyncDisposable { - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class AWSValidateInfrastructureTests : IDisposable, IAsyncDisposable - { private readonly Message _message; - private readonly IAmAMessageConsumerSync _consumer; - private readonly SqsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly MyCommand _myCommand; - - public AWSValidateInfrastructureTests() - { - _myCommand = new MyCommand{Value = "Test"}; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Create - ); - - _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) - ); - - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - //We need to do this manually in a test - will create the channel from subscriber parameters - //This doesn't look that different from our create tests - this is because we create using the channel factory in - //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) - _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateSyncChannel(subscription); - - //Now change the subscription to validate, just check what we made - subscription = new( - name: new SubscriptionName(channelName), - channelName: channel.Name, - routingKey: routingKey, - findTopicBy: TopicFindBy.Name, - messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Validate - ); - - _messageProducer = new SqsMessageProducer( - awsConnection, - new SnsPublication - { - FindTopicBy = TopicFindBy.Name, - MakeChannels = OnMissingChannel.Validate, - Topic = new RoutingKey(topicName) - } - ); - - _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); - } - - [Fact] - public async Task When_infrastructure_exists_can_verify() - { - //arrange - _messageProducer.Send(_message); - - await Task.Delay(1000); - - var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); - - //Assert - var message = messages.First(); - message.Id.Should().Be(_myCommand.Id); - - //clear the queue - _consumer.Acknowledge(message); - } - - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - _consumer.Dispose(); - _messageProducer.Dispose(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); - await _messageProducer.DisposeAsync(); - } - } + private readonly Message _message; + private readonly IAmAMessageConsumerSync _consumer; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureTests() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + findTopicBy: TopicFindBy.Name, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Validate + ); + + _messageProducer = new SqsMessageProducer( + awsConnection, + new SnsPublication + { + FindTopicBy = TopicFindBy.Name, + MakeChannels = OnMissingChannel.Validate, + Topic = new RoutingKey(topicName) + } + ); + + _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify() + { + //arrange + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _consumer.Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); + await _messageProducer.DisposeAsync(); + } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_arn.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_arn.cs index 25877359ef..ee3711aef5 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_arn.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_arn.cs @@ -11,117 +11,116 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard -{ - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class AWSValidateInfrastructureByArnTests : IDisposable, IAsyncDisposable - { private readonly Message _message; - private readonly IAmAMessageConsumerSync _consumer; - private readonly SqsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly MyCommand _myCommand; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; - public AWSValidateInfrastructureByArnTests() - { - _myCommand = new MyCommand{Value = "Test"}; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey($"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureByArnTests : IDisposable, IAsyncDisposable +{ private readonly Message _message; + private readonly IAmAMessageConsumerSync _consumer; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureByArnTests() + { + _myCommand = new MyCommand{Value = "Test"}; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey($"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Create - ); + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create + ); - _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) - ); + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + ); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var awsConnection = GatewayFactory.CreateFactory(credentials, region); - //We need to do this manually in a test - will create the channel from subscriber parameters - //This doesn't look that different from our create tests - this is because we create using the channel factory in - //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) - _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateSyncChannel(subscription); + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); - var topicArn = FindTopicArn(credentials, region, routingKey.Value); - var routingKeyArn = new RoutingKey(topicArn); + var topicArn = FindTopicArn(credentials, region, routingKey.Value); + var routingKeyArn = new RoutingKey(topicArn); - //Now change the subscription to validate, just check what we made - subscription = new( - name: new SubscriptionName(channelName), - channelName: channel.Name, - routingKey: routingKeyArn, - findTopicBy: TopicFindBy.Arn, - messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Validate - ); + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKeyArn, + findTopicBy: TopicFindBy.Arn, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Validate + ); - _messageProducer = new SqsMessageProducer( - awsConnection, - new SnsPublication - { - Topic = routingKey, - TopicArn = topicArn, - FindTopicBy = TopicFindBy.Arn, - MakeChannels = OnMissingChannel.Validate - }); + _messageProducer = new SqsMessageProducer( + awsConnection, + new SnsPublication + { + Topic = routingKey, + TopicArn = topicArn, + FindTopicBy = TopicFindBy.Arn, + MakeChannels = OnMissingChannel.Validate + }); - _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); - } + _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + } - [Fact] - public async Task When_infrastructure_exists_can_verify() - { - //arrange - _messageProducer.Send(_message); + [Fact] + public async Task When_infrastructure_exists_can_verify() + { + //arrange + _messageProducer.Send(_message); - await Task.Delay(1000); + await Task.Delay(1000); - var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); - //Assert - var message = messages.First(); - message.Id.Should().Be(_myCommand.Id); + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); - //clear the queue - _consumer.Acknowledge(message); - } + //clear the queue + _consumer.Acknowledge(message); + } - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - _consumer.Dispose(); - _messageProducer.Dispose(); - } + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _consumer.Dispose(); + _messageProducer.Dispose(); + } - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); - await _messageProducer.DisposeAsync(); - } + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); + await _messageProducer.DisposeAsync(); + } - private string FindTopicArn(AWSCredentials credentials, RegionEndpoint region, string topicName) - { - var snsClient = new AmazonSimpleNotificationServiceClient(credentials, region); - var topicResponse = snsClient.FindTopicAsync(topicName).GetAwaiter().GetResult(); - return topicResponse.TopicArn; - } + private string FindTopicArn(AWSCredentials credentials, RegionEndpoint region, string topicName) + { + var snsClient = new AmazonSimpleNotificationServiceClient(credentials, region); + var topicResponse = snsClient.FindTopicAsync(topicName).GetAwaiter().GetResult(); + return topicResponse.TopicArn; + } - } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_convention.cs index 3f3a86ed27..2b0d408cb5 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_convention.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_convention.cs @@ -2,114 +2,106 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureByConventionTests : IDisposable, IAsyncDisposable { - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class AWSValidateInfrastructureByConventionTests : IDisposable, IAsyncDisposable - { private readonly Message _message; - private readonly IAmAMessageConsumerSync _consumer; - private readonly SqsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly MyCommand _myCommand; - - public AWSValidateInfrastructureByConventionTests() - { - _myCommand = new MyCommand{Value = "Test"}; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Create - ); - - _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) - ); - - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - //We need to do this manually in a test - will create the channel from subscriber parameters - //This doesn't look that different from our create tests - this is because we create using the channel factory in - //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) - _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateSyncChannel(subscription); - - //Now change the subscription to validate, just check what we made - will make the SNS Arn to prevent ListTopics call - subscription = new( - name: new SubscriptionName(channelName), - channelName: channel.Name, - routingKey: routingKey, - findTopicBy: TopicFindBy.Convention, - messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Validate - ); - - _messageProducer = new SqsMessageProducer( - awsConnection, - new SnsPublication{ - FindTopicBy = TopicFindBy.Convention, - MakeChannels = OnMissingChannel.Validate - } - ); - - _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); - } - - [Fact] - public async Task When_infrastructure_exists_can_verify() - { - //arrange - _messageProducer.Send(_message); - - await Task.Delay(1000); - - var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); - - //Assert - var message = messages.First(); - message.Id.Should().Be(_myCommand.Id); - - //clear the queue - _consumer.Acknowledge(message); - } - - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - _consumer.Dispose(); - _messageProducer.Dispose(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); - await _messageProducer.DisposeAsync(); - } - - } + private readonly Message _message; + private readonly IAmAMessageConsumerSync _consumer; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureByConventionTests() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + //Now change the subscription to validate, just check what we made - will make the SNS Arn to prevent ListTopics call + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + findTopicBy: TopicFindBy.Convention, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Validate + ); + + _messageProducer = new SqsMessageProducer( + awsConnection, + new SnsPublication { FindTopicBy = TopicFindBy.Convention, MakeChannels = OnMissingChannel.Validate } + ); + + _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify() + { + //arrange + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _consumer.Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); + await _messageProducer.DisposeAsync(); + } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infrastructure_exists_can_verify_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_async.cs similarity index 93% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infrastructure_exists_can_verify_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_async.cs index fef735052b..8c7779fbb1 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infrastructure_exists_can_verify_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_async.cs @@ -2,15 +2,13 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard { [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -46,8 +44,7 @@ public AWSValidateInfrastructureTestsAsync() new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + var awsConnection = GatewayFactory.CreateFactory(); _channelFactory = new ChannelFactory(awsConnection); var channel = _channelFactory.CreateAsyncChannel(subscription); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_arn_async.cs similarity index 59% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_arn_async.cs index 2148f1b340..2ed5f8813b 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_arn_async.cs @@ -11,63 +11,56 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class AWSValidateInfrastructureByArnTests : IDisposable, IAsyncDisposable +public class AWSValidateInfrastructureByArnTestsAsync : IAsyncDisposable, IDisposable { private readonly Message _message; - private readonly IAmAMessageConsumerSync _consumer; + private readonly IAmAMessageConsumerAsync _consumer; private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; - public AWSValidateInfrastructureByArnTests() + public AWSValidateInfrastructureByArnTestsAsync() { _myCommand = new MyCommand { Value = "Test" }; string correlationId = Guid.NewGuid().ToString(); - const string replyTo = "http:\\queueUrl"; - const string contentType = "text\\plain"; + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var partitionKey = $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey($"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); - var subscription = new SqsSubscription( + SqsSubscription subscription = new( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, - makeChannels: OnMissingChannel.Create, - sqsType: SnsSqsType.Fifo + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create ); _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), + replyTo: new RoutingKey(replyTo), contentType: contentType), new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + var awsConnection = GatewayFactory.CreateFactory(credentials, region); - //We need to do this manually in a test - will create the channel from subscriber parameters - //This doesn't look that different from our create tests - this is because we create using the channel factory in - //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateSyncChannel(subscription); + var channel = _channelFactory.CreateAsyncChannel(subscription); - var topicArn = FindTopicArn(credentials, region, routingKey.Value); + var topicArn = FindTopicArn(credentials, region, routingKey.Value).Result; var routingKeyArn = new RoutingKey(topicArn); - //Now change the subscription to validate, just check what we made subscription = new( name: new SubscriptionName(channelName), channelName: channel.Name, routingKey: routingKeyArn, findTopicBy: TopicFindBy.Arn, - makeChannels: OnMissingChannel.Validate, - sqsType: SnsSqsType.Fifo + makeChannels: OnMissingChannel.Validate ); _messageProducer = new SqsMessageProducer( @@ -77,52 +70,48 @@ public AWSValidateInfrastructureByArnTests() Topic = routingKey, TopicArn = topicArn, FindTopicBy = TopicFindBy.Arn, - MakeChannels = OnMissingChannel.Validate, - SnsType = SnsSqsType.Fifo + MakeChannels = OnMissingChannel.Validate }); - _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); } [Fact] - public async Task When_infrastructure_exists_can_verify() + public async Task When_infrastructure_exists_can_verify_async() { - //arrange - _messageProducer.Send(_message); + await _messageProducer.SendAsync(_message); await Task.Delay(1000); - var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - //Assert var message = messages.First(); message.Id.Should().Be(_myCommand.Id); - //clear the queue - _consumer.Acknowledge(message); + await _consumer.AcknowledgeAsync(message); } + private async Task FindTopicArn(AWSCredentials credentials, RegionEndpoint region, string topicName) + { + var snsClient = new AmazonSimpleNotificationServiceClient(credentials, region); + var topicResponse = await snsClient.FindTopicAsync(topicName); + return topicResponse.TopicArn; + } + public void Dispose() { //Clean up resources that we have created _channelFactory.DeleteTopicAsync().Wait(); _channelFactory.DeleteQueueAsync().Wait(); - _consumer.Dispose(); + ((IAmAMessageConsumerSync)_consumer).Dispose(); _messageProducer.Dispose(); } - + public async ValueTask DisposeAsync() { await _channelFactory.DeleteTopicAsync(); await _channelFactory.DeleteQueueAsync(); - await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); + await _consumer.DisposeAsync(); await _messageProducer.DisposeAsync(); } - - private static string FindTopicArn(AWSCredentials credentials, RegionEndpoint region, string topicName) - { - var snsClient = new AmazonSimpleNotificationServiceClient(credentials, region); - var topicResponse = snsClient.FindTopicAsync(topicName).GetAwaiter().GetResult(); - return topicResponse.TopicArn; - } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_convention.cs similarity index 53% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_convention.cs index f8d3e649ee..0b9aa55123 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_convention.cs @@ -2,69 +2,60 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class AWSValidateInfrastructureByConventionTests : IDisposable, IAsyncDisposable +public class AWSValidateInfrastructureByConventionTestsAsync : IAsyncDisposable, IDisposable { private readonly Message _message; - private readonly IAmAMessageConsumerSync _consumer; + private readonly IAmAMessageConsumerAsync _consumer; private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; - public AWSValidateInfrastructureByConventionTests() + public AWSValidateInfrastructureByConventionTestsAsync() { _myCommand = new MyCommand { Value = "Test" }; string correlationId = Guid.NewGuid().ToString(); - const string replyTo = "http:\\queueUrl"; - const string contentType = "text\\plain"; + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var partitionKey = $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - var subscription = new SqsSubscription( + SqsSubscription subscription = new( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, - makeChannels: OnMissingChannel.Create, - sqsType: SnsSqsType.Fifo + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create ); _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), + replyTo: new RoutingKey(replyTo), contentType: contentType), new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); + var awsConnection = GatewayFactory.CreateFactory(); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - //We need to do this manually in a test - will create the channel from subscriber parameters - //This doesn't look that different from our create tests - this is because we create using the channel factory in - //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateSyncChannel(subscription); + var channel = _channelFactory.CreateAsyncChannel(subscription); - //Now change the subscription to validate, just check what we made - will make the SNS Arn to prevent ListTopics call subscription = new( name: new SubscriptionName(channelName), channelName: channel.Name, routingKey: routingKey, findTopicBy: TopicFindBy.Convention, - makeChannels: OnMissingChannel.Validate, - sqsType: SnsSqsType.Fifo + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Validate ); _messageProducer = new SqsMessageProducer( @@ -72,38 +63,34 @@ public AWSValidateInfrastructureByConventionTests() new SnsPublication { FindTopicBy = TopicFindBy.Convention, - MakeChannels = OnMissingChannel.Validate, - SnsType = SnsSqsType.Fifo + MakeChannels = OnMissingChannel.Validate } ); - _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); } [Fact] - public async Task When_infrastructure_exists_can_verify() + public async Task When_infrastructure_exists_can_verify_async() { - //arrange - _messageProducer.Send(_message); + await _messageProducer.SendAsync(_message); await Task.Delay(1000); - var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - //Assert var message = messages.First(); message.Id.Should().Be(_myCommand.Id); - //clear the queue - _consumer.Acknowledge(message); + await _consumer.AcknowledgeAsync(message); } - + public void Dispose() { //Clean up resources that we have created _channelFactory.DeleteTopicAsync().Wait(); _channelFactory.DeleteQueueAsync().Wait(); - _consumer.Dispose(); + ((IAmAMessageConsumerSync)_consumer).Dispose(); _messageProducer.Dispose(); } @@ -111,7 +98,7 @@ public async ValueTask DisposeAsync() { await _channelFactory.DeleteTopicAsync(); await _channelFactory.DeleteQueueAsync(); - await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); + await _consumer.DisposeAsync(); await _messageProducer.DisposeAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway.cs index 03c55db41e..3f57a612b2 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway.cs @@ -1,117 +1,110 @@ using System; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +public class SqsMessageProducerSendTests : IDisposable, IAsyncDisposable { - [Trait("Category", "AWS")] - public class SqsMessageProducerSendTests : IDisposable, IAsyncDisposable - { - private readonly Message _message; - private readonly IAmAChannelSync _channel; - private readonly SqsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly MyCommand _myCommand; - private readonly string _correlationId; - private readonly string _replyTo; - private readonly string _contentType; - private readonly string _topicName; + private readonly Message _message; + private readonly IAmAChannelSync _channel; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + private readonly string _correlationId; + private readonly string _replyTo; + private readonly string _contentType; + private readonly string _topicName; - public SqsMessageProducerSendTests() - { - _myCommand = new MyCommand{Value = "Test"}; - _correlationId = Guid.NewGuid().ToString(); - _replyTo = "http:\\queueUrl"; - _contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(_topicName); + public SqsMessageProducerSendTests() + { + _myCommand = new MyCommand{Value = "Test"}; + _correlationId = Guid.NewGuid().ToString(); + _replyTo = "http:\\queueUrl"; + _contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(_topicName); - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Reactor, - rawMessageDelivery: false - ); + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + rawMessageDelivery: false + ); - _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: _correlationId, - replyTo: new RoutingKey(_replyTo), contentType: _contentType), - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) - ); - + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: _correlationId, + replyTo: new RoutingKey(_replyTo), contentType: _contentType), + new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + ); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + var awsConnection = GatewayFactory.CreateFactory(); - _channelFactory = new ChannelFactory(awsConnection); - _channel = _channelFactory.CreateSyncChannel(subscription); + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); - _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication{Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create}); - } - - + _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication{Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create}); + } - [Fact] - public async Task When_posting_a_message_via_the_producer() - { - //arrange - _message.Header.Subject = "test subject"; - _messageProducer.Send(_message); + [Fact] + public async Task When_posting_a_message_via_the_producer() + { + //arrange + _message.Header.Subject = "test subject"; + _messageProducer.Send(_message); - await Task.Delay(1000); + await Task.Delay(1000); - var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - //clear the queue - _channel.Acknowledge(message); + //clear the queue + _channel.Acknowledge(message); - //should_send_the_message_to_aws_sqs - message.Header.MessageType.Should().Be(MessageType.MT_COMMAND); + //should_send_the_message_to_aws_sqs + message.Header.MessageType.Should().Be(MessageType.MT_COMMAND); - message.Id.Should().Be(_myCommand.Id); - message.Redelivered.Should().BeFalse(); - message.Header.MessageId.Should().Be(_myCommand.Id); - message.Header.Topic.Value.Should().Contain(_topicName); - message.Header.CorrelationId.Should().Be(_correlationId); - message.Header.ReplyTo.Should().Be(_replyTo); - message.Header.ContentType.Should().Be(_contentType); - message.Header.HandledCount.Should().Be(0); - message.Header.Subject.Should().Be(_message.Header.Subject); - //allow for clock drift in the following test, more important to have a contemporary timestamp than anything - message.Header.TimeStamp.Should().BeAfter(RoundToSeconds(DateTime.UtcNow.AddMinutes(-1))); - message.Header.Delayed.Should().Be(TimeSpan.Zero); - //{"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} - message.Body.Value.Should().Be(_message.Body.Value); - } + message.Id.Should().Be(_myCommand.Id); + message.Redelivered.Should().BeFalse(); + message.Header.MessageId.Should().Be(_myCommand.Id); + message.Header.Topic.Value.Should().Contain(_topicName); + message.Header.CorrelationId.Should().Be(_correlationId); + message.Header.ReplyTo.Should().Be(_replyTo); + message.Header.ContentType.Should().Be(_contentType); + message.Header.HandledCount.Should().Be(0); + message.Header.Subject.Should().Be(_message.Header.Subject); + //allow for clock drift in the following test, more important to have a contemporary timestamp than anything + message.Header.TimeStamp.Should().BeAfter(RoundToSeconds(DateTime.UtcNow.AddMinutes(-1))); + message.Header.Delayed.Should().Be(TimeSpan.Zero); + //{"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} + message.Body.Value.Should().Be(_message.Body.Value); + } - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - _messageProducer.Dispose(); - } + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _messageProducer.Dispose(); + } - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - await _messageProducer.DisposeAsync(); - } + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _messageProducer.DisposeAsync(); + } - private static DateTime RoundToSeconds(DateTime dateTime) - { - return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind); - } - + private static DateTime RoundToSeconds(DateTime dateTime) + { + return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind); } + } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway_async.cs similarity index 54% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway_async.cs index aa9c32f714..6263358d56 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway_async.cs @@ -1,101 +1,74 @@ using System; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Category", "AWS")] -public class SqsMessageProducerSendTests : IDisposable, IAsyncDisposable +public class SqsMessageProducerSendAsyncTests : IAsyncDisposable, IDisposable { private readonly Message _message; - private readonly IAmAChannelSync _channel; + private readonly IAmAChannelAsync _channel; private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; - private readonly string _topicName; - private readonly string _partitionKey; - private readonly string _deduplicationId; - private readonly string _correlationId; private readonly string _replyTo; private readonly string _contentType; + private readonly string _topicName; - public SqsMessageProducerSendTests() + public SqsMessageProducerSendAsyncTests() { _myCommand = new MyCommand { Value = "Test" }; _correlationId = Guid.NewGuid().ToString(); _replyTo = "http:\\queueUrl"; _contentType = "text\\plain"; - _topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _partitionKey = $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _deduplicationId = $"Deduplication-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(_topicName); - var subscription = new SqsSubscription( + SqsSubscription subscription = new( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, - rawMessageDelivery: false, - sqsType: SnsSqsType.Fifo, - contentBasedDeduplication: true + messagePumpType: MessagePumpType.Proactor, + rawMessageDelivery: false ); _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: _correlationId, - replyTo: new RoutingKey(_replyTo), contentType: _contentType, partitionKey: _partitionKey), + replyTo: new RoutingKey(_replyTo), contentType: _contentType), new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + var awsConnection = GatewayFactory.CreateFactory(); _channelFactory = new ChannelFactory(awsConnection); - _channel = _channelFactory.CreateSyncChannel(subscription); - - _messageProducer = new SqsMessageProducer(awsConnection, - new SnsPublication - { - Topic = new RoutingKey(_topicName), - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo, - Deduplication = true - }); + _channel = _channelFactory.CreateAsyncChannel(subscription); + + _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication { Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create }); } - [Theory] - [InlineData("test subject", true)] - [InlineData(null, true)] - [InlineData("test subject", false)] - [InlineData(null, false)] - public async Task When_posting_a_message_via_the_producer_for_fifo(string subject, bool sendAsync) + [Fact] + public async Task When_posting_a_message_via_the_producer_async() { - //arrange - _message.Header.Subject = subject; - if (sendAsync) - { - await _messageProducer.SendAsync(_message); - } - else - { - _messageProducer.Send(_message); - } + // arrange + _message.Header.Subject = "test subject"; + await _messageProducer.SendAsync(_message); await Task.Delay(1000); - var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + var message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - //clear the queue - _channel.Acknowledge(message); + // clear the queue + await _channel.AcknowledgeAsync(message); - //should_send_the_message_to_aws_sqs + // should_send_the_message_to_aws_sqs message.Header.MessageType.Should().Be(MessageType.MT_COMMAND); message.Id.Should().Be(_myCommand.Id); @@ -106,19 +79,14 @@ public async Task When_posting_a_message_via_the_producer_for_fifo(string subjec message.Header.ReplyTo.Should().Be(_replyTo); message.Header.ContentType.Should().Be(_contentType); message.Header.HandledCount.Should().Be(0); - message.Header.Subject.Should().Be(subject); - //allow for clock drift in the following test, more important to have a contemporary timestamp than anything + message.Header.Subject.Should().Be(_message.Header.Subject); + // allow for clock drift in the following test, more important to have a contemporary timestamp than anything message.Header.TimeStamp.Should().BeAfter(RoundToSeconds(DateTime.UtcNow.AddMinutes(-1))); message.Header.Delayed.Should().Be(TimeSpan.Zero); - //{"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} + // {"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} message.Body.Value.Should().Be(_message.Body.Value); - message.Header.Bag.Should().ContainKey(HeaderNames.DeduplicationId); - message.Header.Bag[HeaderNames.DeduplicationId].Should().Be(_deduplicationId); - message.Header.PartitionKey.Should().Be(_partitionKey); - message.Header.Bag.Should().ContainKey(HeaderNames.MessageGroupId); - message.Header.Bag[HeaderNames.MessageGroupId].Should().Be(_partitionKey); } - + public void Dispose() { //Clean up resources that we have created diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws.cs index cbeaad5a2f..a4cc2797e9 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws.cs @@ -1,71 +1,67 @@ using System; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using Amazon.SQS.Model; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +public class AWSAssumeQueuesTests : IDisposable, IAsyncDisposable { - [Trait("Category", "AWS")] - public class AWSAssumeQueuesTests : IDisposable, IAsyncDisposable - { - private readonly ChannelFactory _channelFactory; - private readonly SqsMessageConsumer _consumer; + private readonly ChannelFactory _channelFactory; + private readonly SqsMessageConsumer _consumer; - public AWSAssumeQueuesTests() - { - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + public AWSAssumeQueuesTests() + { + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); - var subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Assume - ); + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Assume + ); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + var awsConnection = GatewayFactory.CreateFactory(); - //create the topic, we want the queue to be the issue - //We need to create the topic at least, to check the queues - var producer = new SqsMessageProducer(awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create - }); + //create the topic, we want the queue to be the issue + //We need to create the topic at least, to check the queues + var producer = new SqsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create + }); - producer.ConfirmTopicExistsAsync(topicName).Wait(); + producer.ConfirmTopicExistsAsync(topicName).Wait(); - _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateSyncChannel(subscription); + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); - //We need to create the topic at least, to check the queues - _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); - } + //We need to create the topic at least, to check the queues + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); + } - [Fact] - public void When_queues_missing_assume_throws() - { - //we will try to get the queue url, and fail because it does not exist - Assert.Throws(() => _consumer.Receive(TimeSpan.FromMilliseconds(1000))); - } + [Fact] + public void When_queues_missing_assume_throws() + { + //we will try to get the queue url, and fail because it does not exist + Assert.Throws(() => _consumer.Receive(TimeSpan.FromMilliseconds(1000))); + } - public void Dispose() - { - _channelFactory.DeleteTopicAsync().Wait(); - } + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + } - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - } + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + } - } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws_async.cs similarity index 60% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws_async.cs index 11cea2ebca..695b4fb611 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws_async.cs @@ -1,22 +1,20 @@ using System; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using Amazon.SQS.Model; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Category", "AWS")] -public class AWSAssumeQueuesTests : IDisposable, IAsyncDisposable +public class AWSAssumeQueuesTestsAsync : IAsyncDisposable, IDisposable { private readonly ChannelFactory _channelFactory; - private readonly SqsMessageConsumer _consumer; + private readonly IAmAMessageConsumerAsync _consumer; - public AWSAssumeQueuesTests() + public AWSAssumeQueuesTestsAsync() { var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); @@ -27,36 +25,38 @@ public AWSAssumeQueuesTests() channelName: new ChannelName(channelName), routingKey: routingKey, makeChannels: OnMissingChannel.Assume, - sqsType: SnsSqsType.Fifo + messagePumpType: MessagePumpType.Proactor ); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + var awsConnection = GatewayFactory.CreateFactory(); //create the topic, we want the queue to be the issue //We need to create the topic at least, to check the queues var producer = new SqsMessageProducer(awsConnection, - new SnsPublication { MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo }); + new SnsPublication + { + MakeChannels = OnMissingChannel.Create + }); producer.ConfirmTopicExistsAsync(topicName).Wait(); _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateSyncChannel(subscription); + var channel = _channelFactory.CreateAsyncChannel(subscription); //We need to create the topic at least, to check the queues - _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); + _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); } [Fact] - public void When_queues_missing_assume_throws() + public async Task When_queues_missing_assume_throws_async() { //we will try to get the queue url, and fail because it does not exist - Assert.Throws(() => _consumer.Receive(TimeSpan.FromMilliseconds(1000))); + await Assert.ThrowsAsync(async () => await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(1000))); } - + public void Dispose() { - _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteTopicAsync().Wait(); } public async ValueTask DisposeAsync() diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws.cs index 9b6bb719f8..e4e5ddd1e5 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws.cs @@ -1,66 +1,62 @@ using System; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using Amazon.SQS.Model; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +public class AWSValidateQueuesTests : IDisposable, IAsyncDisposable { - [Trait("Category", "AWS")] - public class AWSValidateQueuesTests : IDisposable, IAsyncDisposable - { - private readonly AWSMessagingGatewayConnection _awsConnection; - private readonly SqsSubscription _subscription; - private ChannelFactory _channelFactory; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; + private ChannelFactory _channelFactory; - public AWSValidateQueuesTests() - { - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + public AWSValidateQueuesTests() + { + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); - _subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Validate - ); + _subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Validate + ); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - _awsConnection = new AWSMessagingGatewayConnection(credentials, region); + _awsConnection = GatewayFactory.CreateFactory(); - //We need to create the topic at least, to check the queues - var producer = new SqsMessageProducer(_awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create - }); - producer.ConfirmTopicExistsAsync(topicName).Wait(); + //We need to create the topic at least, to check the queues + var producer = new SqsMessageProducer(_awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create + }); + producer.ConfirmTopicExistsAsync(topicName).Wait(); - } + } - [Fact] - public void When_queues_missing_verify_throws() - { - //We have no queues so we should throw - //We need to do this manually in a test - will create the channel from subscriber parameters - _channelFactory = new ChannelFactory(_awsConnection); - Assert.Throws(() => _channelFactory.CreateSyncChannel(_subscription)); - } + [Fact] + public void When_queues_missing_verify_throws() + { + //We have no queues so we should throw + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(_awsConnection); + Assert.Throws(() => _channelFactory.CreateSyncChannel(_subscription)); + } - public void Dispose() - { - _channelFactory.DeleteTopicAsync().Wait(); - } + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + } - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - } - } + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws_async.cs similarity index 53% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws_async.cs index 3f17e46891..377c827a03 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws_async.cs @@ -1,23 +1,21 @@ using System; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using Amazon.SQS.Model; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Category", "AWS")] -public class AWSValidateQueuesTests : IDisposable, IAsyncDisposable +public class AWSValidateQueuesTestsAsync : IAsyncDisposable { private readonly AWSMessagingGatewayConnection _awsConnection; private readonly SqsSubscription _subscription; private ChannelFactory _channelFactory; - public AWSValidateQueuesTests() + public AWSValidateQueuesTestsAsync() { var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); @@ -27,31 +25,27 @@ public AWSValidateQueuesTests() name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, - makeChannels: OnMissingChannel.Validate, - sqsType: SnsSqsType.Fifo + makeChannels: OnMissingChannel.Validate ); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - _awsConnection = new AWSMessagingGatewayConnection(credentials, region); + _awsConnection = GatewayFactory.CreateFactory(); - //We need to create the topic at least, to check the queues + // We need to create the topic at least, to check the queues var producer = new SqsMessageProducer(_awsConnection, - new SnsPublication { MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo }); + new SnsPublication + { + MakeChannels = OnMissingChannel.Create + }); producer.ConfirmTopicExistsAsync(topicName).Wait(); } [Fact] - public void When_queues_missing_verify_throws() + public async Task When_queues_missing_verify_throws_async() { - //We have no queues so we should throw - //We need to do this manually in a test - will create the channel from subscriber parameters + // We have no queues so we should throw + // We need to do this manually in a test - will create the channel from subscriber parameters _channelFactory = new ChannelFactory(_awsConnection); - Assert.Throws(() => _channelFactory.CreateSyncChannel(_subscription)); - } - - public void Dispose() - { - _channelFactory.DeleteTopicAsync().Wait(); + await Assert.ThrowsAsync(async () => await _channelFactory.CreateAsyncChannelAsync(_subscription)); } public async ValueTask DisposeAsync() diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled.cs index 0375ad9434..199e79090e 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled.cs @@ -9,90 +9,88 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsRawMessageDeliveryTests : IDisposable, IAsyncDisposable { - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class SqsRawMessageDeliveryTests : IDisposable, IAsyncDisposable - { - private readonly SqsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly IAmAChannelSync _channel; - private readonly RoutingKey _routingKey; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly IAmAChannelSync _channel; + private readonly RoutingKey _routingKey; - public SqsRawMessageDeliveryTests() - { - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + public SqsRawMessageDeliveryTests() + { + var awsConnection = GatewayFactory.CreateFactory(); - _channelFactory = new ChannelFactory(awsConnection); - var channelName = $"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _routingKey = new RoutingKey($"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); + _channelFactory = new ChannelFactory(awsConnection); + var channelName = $"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey($"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); - var bufferSize = 10; + var bufferSize = 10; - //Set rawMessageDelivery to false - _channel = _channelFactory.CreateSyncChannel(new SqsSubscription( - name: new SubscriptionName(channelName), - channelName:new ChannelName(channelName), - routingKey:_routingKey, - bufferSize: bufferSize, - makeChannels: OnMissingChannel.Create, - messagePumpType: MessagePumpType.Reactor, - rawMessageDelivery: false)); + //Set rawMessageDelivery to false + _channel = _channelFactory.CreateSyncChannel(new SqsSubscription( + name: new SubscriptionName(channelName), + channelName:new ChannelName(channelName), + routingKey:_routingKey, + bufferSize: bufferSize, + makeChannels: OnMissingChannel.Create, + messagePumpType: MessagePumpType.Reactor, + rawMessageDelivery: false)); - _messageProducer = new SqsMessageProducer(awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create - }); - } + _messageProducer = new SqsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create + }); + } - [Fact] - public void When_raw_message_delivery_disabled() - { - //arrange - var messageHeader = new MessageHeader( - Guid.NewGuid().ToString(), - _routingKey, - MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), - replyTo: RoutingKey.Empty, - contentType: "text\\plain"); + [Fact] + public void When_raw_message_delivery_disabled() + { + //arrange + var messageHeader = new MessageHeader( + Guid.NewGuid().ToString(), + _routingKey, + MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), + replyTo: RoutingKey.Empty, + contentType: "text\\plain"); - var customHeaderItem = new KeyValuePair("custom-header-item", "custom-header-item-value"); - messageHeader.Bag.Add(customHeaderItem.Key, customHeaderItem.Value); + var customHeaderItem = new KeyValuePair("custom-header-item", "custom-header-item-value"); + messageHeader.Bag.Add(customHeaderItem.Key, customHeaderItem.Value); - var messageToSent = new Message(messageHeader, new MessageBody("test content one")); + var messageToSent = new Message(messageHeader, new MessageBody("test content one")); - //act - _messageProducer.Send(messageToSent); + //act + _messageProducer.Send(messageToSent); - var messageReceived = _channel.Receive(TimeSpan.FromMilliseconds(10000)); + var messageReceived = _channel.Receive(TimeSpan.FromMilliseconds(10000)); - _channel.Acknowledge(messageReceived); + _channel.Acknowledge(messageReceived); - //assert - messageReceived.Id.Should().Be(messageToSent.Id); - messageReceived.Header.Topic.Should().Be(messageToSent.Header.Topic); - messageReceived.Header.MessageType.Should().Be(messageToSent.Header.MessageType); - messageReceived.Header.CorrelationId.Should().Be(messageToSent.Header.CorrelationId); - messageReceived.Header.ReplyTo.Should().Be(messageToSent.Header.ReplyTo); - messageReceived.Header.ContentType.Should().Be(messageToSent.Header.ContentType); - messageReceived.Header.Bag.Should().ContainKey(customHeaderItem.Key).And.ContainValue(customHeaderItem.Value); - messageReceived.Body.Value.Should().Be(messageToSent.Body.Value); - } + //assert + messageReceived.Id.Should().Be(messageToSent.Id); + messageReceived.Header.Topic.Should().Be(messageToSent.Header.Topic); + messageReceived.Header.MessageType.Should().Be(messageToSent.Header.MessageType); + messageReceived.Header.CorrelationId.Should().Be(messageToSent.Header.CorrelationId); + messageReceived.Header.ReplyTo.Should().Be(messageToSent.Header.ReplyTo); + messageReceived.Header.ContentType.Should().Be(messageToSent.Header.ContentType); + messageReceived.Header.Bag.Should().ContainKey(customHeaderItem.Key).And.ContainValue(customHeaderItem.Value); + messageReceived.Body.Value.Should().Be(messageToSent.Body.Value); + } - public void Dispose() - { - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - } + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled_async.cs similarity index 52% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled_async.cs index 8314c73dc3..4e1aef5330 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled_async.cs @@ -9,21 +9,20 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class SqsRawMessageDeliveryTests : IDisposable, IAsyncDisposable +public class SqsRawMessageDeliveryTestsAsync : IAsyncDisposable, IDisposable { private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; - private readonly IAmAChannelSync _channel; + private readonly IAmAChannelAsync _channel; private readonly RoutingKey _routingKey; - public SqsRawMessageDeliveryTests() + public SqsRawMessageDeliveryTestsAsync() { - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + var awsConnection = GatewayFactory.CreateFactory(); _channelFactory = new ChannelFactory(awsConnection); var channelName = $"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45); @@ -31,69 +30,60 @@ public SqsRawMessageDeliveryTests() var bufferSize = 10; - //Set rawMessageDelivery to false - _channel = _channelFactory.CreateSyncChannel(new SqsSubscription( + // Set rawMessageDelivery to false + _channel = _channelFactory.CreateAsyncChannel(new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: _routingKey, bufferSize: bufferSize, makeChannels: OnMissingChannel.Create, - rawMessageDelivery: false, - sqsType: SnsSqsType.Fifo, - contentBasedDeduplication: true)); + rawMessageDelivery: false)); _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo, Deduplication = true + MakeChannels = OnMissingChannel.Create }); } [Fact] - public void When_raw_message_delivery_disabled() + public async Task When_raw_message_delivery_disabled_async() { - //arrange - var partitionKey = Guid.NewGuid().ToString(); - var deduplicationId = Guid.NewGuid().ToString(); - + // Arrange var messageHeader = new MessageHeader( Guid.NewGuid().ToString(), _routingKey, MessageType.MT_COMMAND, correlationId: Guid.NewGuid().ToString(), replyTo: RoutingKey.Empty, - contentType: "text\\plain", - partitionKey: partitionKey) { Bag = { [HeaderNames.DeduplicationId] = deduplicationId } }; + contentType: "text\\plain"); var customHeaderItem = new KeyValuePair("custom-header-item", "custom-header-item-value"); messageHeader.Bag.Add(customHeaderItem.Key, customHeaderItem.Value); - var messageToSent = new Message(messageHeader, new MessageBody("test content one")); + var messageToSend = new Message(messageHeader, new MessageBody("test content one")); - //act - _messageProducer.Send(messageToSent); + // Act + await _messageProducer.SendAsync(messageToSend); - var messageReceived = _channel.Receive(TimeSpan.FromMilliseconds(10000)); + var messageReceived = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(10000)); - _channel.Acknowledge(messageReceived); + await _channel.AcknowledgeAsync(messageReceived); - //assert - messageReceived.Id.Should().Be(messageToSent.Id); - messageReceived.Header.Topic.Should().Be(messageToSent.Header.Topic); - messageReceived.Header.MessageType.Should().Be(messageToSent.Header.MessageType); - messageReceived.Header.CorrelationId.Should().Be(messageToSent.Header.CorrelationId); - messageReceived.Header.ReplyTo.Should().Be(messageToSent.Header.ReplyTo); - messageReceived.Header.ContentType.Should().Be(messageToSent.Header.ContentType); - messageReceived.Header.PartitionKey.Should().Be(partitionKey); + // Assert + messageReceived.Id.Should().Be(messageToSend.Id); + messageReceived.Header.Topic.Should().Be(messageToSend.Header.Topic); + messageReceived.Header.MessageType.Should().Be(messageToSend.Header.MessageType); + messageReceived.Header.CorrelationId.Should().Be(messageToSend.Header.CorrelationId); + messageReceived.Header.ReplyTo.Should().Be(messageToSend.Header.ReplyTo); + messageReceived.Header.ContentType.Should().Be(messageToSend.Header.ContentType); messageReceived.Header.Bag.Should().ContainKey(customHeaderItem.Key).And.ContainValue(customHeaderItem.Value); - messageReceived.Header.Bag.Should().ContainKey(HeaderNames.DeduplicationId).And.ContainValue(deduplicationId); - messageReceived.Header.Bag.Should().ContainKey(HeaderNames.MessageGroupId).And.ContainValue(partitionKey); - messageReceived.Body.Value.Should().Be(messageToSent.Body.Value); + messageReceived.Body.Value.Should().Be(messageToSend.Body.Value); } - + public void Dispose() { - _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteTopicAsync().Wait(); _channelFactory.DeleteQueueAsync().Wait(); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue.cs index faabe7a851..dad4b358b1 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue.cs @@ -9,83 +9,81 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageConsumerRequeueTests : IDisposable { - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class SqsMessageConsumerRequeueTests : IDisposable - { - private readonly Message _message; - private readonly IAmAChannelSync _channel; - private readonly SqsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly MyCommand _myCommand; + private readonly Message _message; + private readonly IAmAChannelSync _channel; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; - public SqsMessageConsumerRequeueTests() - { - _myCommand = new MyCommand{Value = "Test"}; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + public SqsMessageConsumerRequeueTests() + { + _myCommand = new MyCommand{Value = "Test"}; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - messagePumpType: MessagePumpType.Reactor, - routingKey: routingKey - ); + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + messagePumpType: MessagePumpType.Reactor, + routingKey: routingKey + ); - _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) - ); + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + ); - //Must have credentials stored in the SDK Credentials store or shared credentials file - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + //Must have credentials stored in the SDK Credentials store or shared credentials file + var awsConnection = GatewayFactory.CreateFactory(); - //We need to do this manually in a test - will create the channel from subscriber parameters - _channelFactory = new ChannelFactory(awsConnection); - _channel = _channelFactory.CreateSyncChannel(subscription); + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); - _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); - } + _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); + } - [Fact] - public void When_rejecting_a_message_through_gateway_with_requeue() - { - _messageProducer.Send(_message); + [Fact] + public void When_rejecting_a_message_through_gateway_with_requeue() + { + _messageProducer.Send(_message); - var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - _channel.Reject(message); + _channel.Reject(message); - //Let the timeout change - Task.Delay(TimeSpan.FromMilliseconds(3000)); + //Let the timeout change + Task.Delay(TimeSpan.FromMilliseconds(3000)); - //should requeue_the_message - message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + //should requeue_the_message + message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - //clear the queue - _channel.Acknowledge(message); + //clear the queue + _channel.Acknowledge(message); - message.Id.Should().Be(_myCommand.Id); - } + message.Id.Should().Be(_myCommand.Id); + } - public void Dispose() - { - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - } + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue_async.cs similarity index 50% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue_async.cs index a99fd89ead..bf00294f3e 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue_async.cs @@ -9,71 +9,67 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class SqsMessageConsumerRequeueTests : IDisposable, IAsyncDisposable +public class SqsMessageConsumerRequeueTestsAsync : IDisposable, IAsyncDisposable { private readonly Message _message; - private readonly IAmAChannelSync _channel; + private readonly IAmAChannelAsync _channel; private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; - public SqsMessageConsumerRequeueTests() + public SqsMessageConsumerRequeueTestsAsync() { _myCommand = new MyCommand { Value = "Test" }; - const string replyTo = "http:\\queueUrl"; - const string contentType = "text\\plain"; - var correlationId = Guid.NewGuid().ToString(); + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; var channelName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var partitionKey = $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - var subscription = new SqsSubscription( + SqsSubscription subscription = new( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, - sqsType: SnsSqsType.Fifo + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create ); _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), + replyTo: new RoutingKey(replyTo), contentType: contentType), new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); - //Must have credentials stored in the SDK Credentials store or shared credentials file - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + var awsConnection = GatewayFactory.CreateFactory(); - //We need to do this manually in a test - will create the channel from subscriber parameters _channelFactory = new ChannelFactory(awsConnection); - _channel = _channelFactory.CreateSyncChannel(subscription); + _channel = _channelFactory.CreateAsyncChannel(subscription); - _messageProducer = new SqsMessageProducer(awsConnection, - new SnsPublication { MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo }); + _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); } [Fact] - public void When_rejecting_a_message_through_gateway_with_requeue() + public async Task When_rejecting_a_message_through_gateway_with_requeue_async() { - _messageProducer.Send(_message); + await _messageProducer.SendAsync(_message); - var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + var message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - _channel.Reject(message); + await _channel.RejectAsync(message); - //Let the timeout change - Task.Delay(TimeSpan.FromMilliseconds(3000)); + // Let the timeout change + await Task.Delay(TimeSpan.FromMilliseconds(3000)); - //should requeue_the_message - message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + // should requeue_the_message + message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - //clear the queue - _channel.Acknowledge(message); + // clear the queue + await _channel.AcknowledgeAsync(message); message.Id.Should().Be(_myCommand.Id); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message.cs index 544e491d6a..643b217ff3 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message.cs @@ -10,78 +10,76 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +public class SqsMessageProducerRequeueTests : IDisposable, IAsyncDisposable { - [Trait("Category", "AWS")] - public class SqsMessageProducerRequeueTests : IDisposable, IAsyncDisposable - { - private readonly IAmAMessageProducerSync _sender; - private Message _requeuedMessage; - private Message _receivedMessage; - private readonly IAmAChannelSync _channel; - private readonly ChannelFactory _channelFactory; - private readonly Message _message; + private readonly IAmAMessageProducerSync _sender; + private Message _requeuedMessage; + private Message _receivedMessage; + private readonly IAmAChannelSync _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; - public SqsMessageProducerRequeueTests() - { - MyCommand myCommand = new MyCommand{Value = "Test"}; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + public SqsMessageProducerRequeueTests() + { + MyCommand myCommand = new MyCommand{Value = "Test"}; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); - var subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey - ); + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey + ); - _message = new Message( - new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) myCommand, JsonSerialisationOptions.Options)) - ); + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object) myCommand, JsonSerialisationOptions.Options)) + ); - //Must have credentials stored in the SDK Credentials store or shared credentials file - new CredentialProfileStoreChain(); + //Must have credentials stored in the SDK Credentials store or shared credentials file + new CredentialProfileStoreChain(); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + var awsConnection = GatewayFactory.CreateFactory(); - _sender = new SqsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); + _sender = new SqsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); - //We need to do this manually in a test - will create the channel from subscriber parameters - _channelFactory = new ChannelFactory(awsConnection); - _channel = _channelFactory.CreateSyncChannel(subscription); - } + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); + } - [Fact] - public void When_requeueing_a_message() - { - _sender.Send(_message); - _receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - _channel.Requeue(_receivedMessage); + [Fact] + public void When_requeueing_a_message() + { + _sender.Send(_message); + _receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(_receivedMessage); - _requeuedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _requeuedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - //clear the queue - _channel.Acknowledge(_requeuedMessage ); + //clear the queue + _channel.Acknowledge(_requeuedMessage ); - _requeuedMessage.Body.Value.Should().Be(_receivedMessage.Body.Value); - } + _requeuedMessage.Body.Value.Should().Be(_receivedMessage.Body.Value); + } - public void Dispose() - { - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - } + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message_async.cs similarity index 51% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message_async.cs index 3dc22400b2..da6ac72b1c 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message_async.cs @@ -10,66 +10,62 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Category", "AWS")] -public class SqsMessageProducerRequeueTests : IDisposable, IAsyncDisposable +public class SqsMessageProducerRequeueTestsAsync : IDisposable, IAsyncDisposable { + private readonly IAmAMessageProducerAsync _sender; private Message _requeuedMessage; private Message _receivedMessage; - private readonly IAmAMessageProducerSync _sender; - private readonly IAmAChannelSync _channel; + private readonly IAmAChannelAsync _channel; private readonly ChannelFactory _channelFactory; private readonly Message _message; - public SqsMessageProducerRequeueTests() + public SqsMessageProducerRequeueTestsAsync() { - MyCommand myCommand = new() { Value = "Test" }; + MyCommand myCommand = new MyCommand { Value = "Test" }; string correlationId = Guid.NewGuid().ToString(); - const string replyTo = "http:\\queueUrl"; - const string contentType = "text\\plain"; + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; var channelName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var partitionKey = $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), - routingKey: routingKey + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create ); _message = new Message( new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), + replyTo: new RoutingKey(replyTo), contentType: contentType), new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) ); - //Must have credentials stored in the SDK Credentials store or shared credentials file new CredentialProfileStoreChain(); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); + var awsConnection = GatewayFactory.CreateFactory(); - _sender = new SqsMessageProducer(awsConnection, - new SnsPublication { MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo }); + _sender = new SqsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); - //We need to do this manually in a test - will create the channel from subscriber parameters _channelFactory = new ChannelFactory(awsConnection); - _channel = _channelFactory.CreateSyncChannel(subscription); + _channel = _channelFactory.CreateAsyncChannel(subscription); } [Fact] - public void When_requeueing_a_message() + public async Task When_requeueing_a_message_async() { - _sender.Send(_message); - _receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - _channel.Requeue(_receivedMessage); + await _sender.SendAsync(_message); + _receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + await _channel.RequeueAsync(_receivedMessage); - _requeuedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _requeuedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - //clear the queue - _channel.Acknowledge(_requeuedMessage); + await _channel.AcknowledgeAsync(_requeuedMessage); _requeuedMessage.Body.Value.Should().Be(_receivedMessage.Body.Value); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq.cs index 579b3172f6..0dc759b324 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq.cs @@ -13,106 +13,104 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageProducerDlqTests : IDisposable, IAsyncDisposable { - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class SqsMessageProducerDlqTests : IDisposable, IAsyncDisposable - { - private readonly SqsMessageProducer _sender; - private readonly IAmAChannelSync _channel; - private readonly ChannelFactory _channelFactory; - private readonly Message _message; - private readonly AWSMessagingGatewayConnection _awsConnection; - private readonly string _dlqChannelName; + private readonly SqsMessageProducer _sender; + private readonly IAmAChannelSync _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly string _dlqChannelName; - public SqsMessageProducerDlqTests () - { - MyCommand myCommand = new MyCommand{Value = "Test"}; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _dlqChannelName =$"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + public SqsMessageProducerDlqTests () + { + MyCommand myCommand = new MyCommand{Value = "Test"}; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _dlqChannelName =$"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); - SqsSubscription subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - redrivePolicy: new RedrivePolicy(_dlqChannelName, 2) - ); + SqsSubscription subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + redrivePolicy: new RedrivePolicy(_dlqChannelName, 2) + ); - _message = new Message( - new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) myCommand, JsonSerialisationOptions.Options)) - ); + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object) myCommand, JsonSerialisationOptions.Options)) + ); - //Must have credentials stored in the SDK Credentials store or shared credentials file - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - _awsConnection = new AWSMessagingGatewayConnection(credentials, region); + //Must have credentials stored in the SDK Credentials store or shared credentials file + _awsConnection = GatewayFactory.CreateFactory(); - _sender = new SqsMessageProducer(_awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); + _sender = new SqsMessageProducer(_awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); - _sender.ConfirmTopicExistsAsync(topicName).Wait(); + _sender.ConfirmTopicExistsAsync(topicName).Wait(); - //We need to do this manually in a test - will create the channel from subscriber parameters - _channelFactory = new ChannelFactory(_awsConnection); - _channel = _channelFactory.CreateSyncChannel(subscription); - } + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); + } - [Fact] - public void When_requeueing_redrives_to_the_queue() - { - _sender.Send(_message); - var receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - _channel.Requeue(receivedMessage ); + [Fact] + public void When_requeueing_redrives_to_the_queue() + { + _sender.Send(_message); + var receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(receivedMessage ); - receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - _channel.Requeue(receivedMessage ); + receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(receivedMessage ); - //should force us into the dlq - receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - _channel.Requeue(receivedMessage) ; + //should force us into the dlq + receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(receivedMessage) ; - Task.Delay(5000); + Task.Delay(5000); - //inspect the dlq - GetDLQCount(_dlqChannelName).Should().Be(1); - } + //inspect the dlq + GetDLQCount(_dlqChannelName).Should().Be(1); + } - public int GetDLQCount(string queueName) + public int GetDLQCount(string queueName) + { + using var sqsClient = new AmazonSQSClient(_awsConnection.Credentials, _awsConnection.Region); + var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); + var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest { - using var sqsClient = new AmazonSQSClient(_awsConnection.Credentials, _awsConnection.Region); - var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); - var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest - { - QueueUrl = queueUrlResponse.QueueUrl, - WaitTimeSeconds = 5, - MessageAttributeNames = new List { "All", "ApproximateReceiveCount" } - }).GetAwaiter().GetResult(); + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageAttributeNames = new List { "All", "ApproximateReceiveCount" } + }).GetAwaiter().GetResult(); - if (response.HttpStatusCode != HttpStatusCode.OK) - { - throw new AmazonSQSException($"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); - } - - return response.Messages.Count; - } - - public void Dispose() + if (response.HttpStatusCode != HttpStatusCode.OK) { - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); + throw new AmazonSQSException($"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); } + + return response.Messages.Count; + } - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } - + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } + } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq_async.cs new file mode 100644 index 0000000000..0f8ec875ac --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq_async.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using Amazon.SQS; +using Amazon.SQS.Model; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageProducerDlqTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly SqsMessageProducer _sender; + private readonly IAmAChannelAsync _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly string _dlqChannelName; + + public SqsMessageProducerDlqTestsAsync() + { + MyCommand myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _dlqChannelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + redrivePolicy: new RedrivePolicy(_dlqChannelName, 2) + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + _awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SqsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + + _sender.ConfirmTopicExistsAsync(topicName).Wait(); + + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateAsyncChannel(subscription); + } + + [Fact] + public async Task When_requeueing_redrives_to_the_queue_async() + { + await _sender.SendAsync(_message); + var receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + await _channel.RequeueAsync(receivedMessage); + + receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + await _channel.RequeueAsync(receivedMessage); + + receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + await _channel.RequeueAsync(receivedMessage); + + await Task.Delay(5000); + + int dlqCount = await GetDLQCountAsync(_dlqChannelName); + dlqCount.Should().Be(1); + } + + public async Task GetDLQCountAsync(string queueName) + { + using var sqsClient = new AmazonSQSClient(_awsConnection.Credentials, _awsConnection.Region); + var queueUrlResponse = await sqsClient.GetQueueUrlAsync(queueName); + var response = await sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest + { + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageAttributeNames = new List { "All", "ApproximateReceiveCount" } + }); + + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new AmazonSQSException($"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + } + + return response.Messages.Count; + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs index d5365618cd..1392ccb051 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs @@ -15,159 +15,157 @@ using Polly.Registry; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SnsReDrivePolicySDlqTests : IDisposable, IAsyncDisposable { - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class SnsReDrivePolicySDlqTests : IDisposable, IAsyncDisposable + private readonly IAmAMessagePump _messagePump; + private readonly Message _message; + private readonly string _dlqChannelName; + private readonly IAmAChannelSync _channel; + private readonly SqsMessageProducer _sender; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; + private readonly ChannelFactory _channelFactory; + + public SnsReDrivePolicySDlqTests() { - private readonly IAmAMessagePump _messagePump; - private readonly Message _message; - private readonly string _dlqChannelName; - private readonly IAmAChannelSync _channel; - private readonly SqsMessageProducer _sender; - private readonly AWSMessagingGatewayConnection _awsConnection; - private readonly SqsSubscription _subscription; - private readonly ChannelFactory _channelFactory; - - public SnsReDrivePolicySDlqTests() - { - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _dlqChannelName = $"Redrive-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - //how are we consuming - _subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - //don't block the redrive policy from owning retry management - requeueCount: -1, - //delay before requeuing - requeueDelay: TimeSpan.FromMilliseconds(50), - messagePumpType: MessagePumpType.Reactor, - //we want our SNS subscription to manage requeue limits using the DLQ for 'too many requeues' - redrivePolicy: new RedrivePolicy - ( - deadLetterQueueName: new ChannelName(_dlqChannelName), - maxReceiveCount: 2 - )); - - //what do we send - var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; - _message = new Message( - new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) - ); - - //Must have credentials stored in the SDK Credentials store or shared credentials file - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - _awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - //how do we send to the queue - _sender = new SqsMessageProducer( - _awsConnection, - new SnsPublication - { - Topic = routingKey, - RequestType = typeof(MyDeferredCommand), - MakeChannels = OnMissingChannel.Create - } - ); - - //We need to do this manually in a test - will create the channel from subscriber parameters - _channelFactory = new ChannelFactory(_awsConnection); - _channel = _channelFactory.CreateSyncChannel(_subscription); - - //how do we handle a command - IHandleRequests handler = new MyDeferredCommandHandler(); - - //hook up routing for the command processor - var subscriberRegistry = new SubscriberRegistry(); - subscriberRegistry.Register(); - - //once we read, how do we dispatch to a handler. N.B. we don't use this for reading here - IAmACommandProcessor commandProcessor = new CommandProcessor( - subscriberRegistry: subscriberRegistry, - handlerFactory: new QuickHandlerFactory(() => handler), - requestContextFactory: new InMemoryRequestContextFactory(), - policyRegistry: new PolicyRegistry() - ); - var provider = new CommandProcessorProvider(commandProcessor); - - var messageMapperRegistry = new MessageMapperRegistry( - new SimpleMessageMapperFactory(_ => new MyDeferredCommandMessageMapper()), - null - ); - messageMapperRegistry.Register(); + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _dlqChannelName = $"Redrive-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + //how are we consuming + _subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + //don't block the redrive policy from owning retry management + requeueCount: -1, + //delay before requeuing + requeueDelay: TimeSpan.FromMilliseconds(50), + messagePumpType: MessagePumpType.Reactor, + //we want our SNS subscription to manage requeue limits using the DLQ for 'too many requeues' + redrivePolicy: new RedrivePolicy + ( + deadLetterQueueName: new ChannelName(_dlqChannelName), + maxReceiveCount: 2 + )); + + //what do we send + var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + //Must have credentials stored in the SDK Credentials store or shared credentials file + _awsConnection = GatewayFactory.CreateFactory(); + + //how do we send to the queue + _sender = new SqsMessageProducer( + _awsConnection, + new SnsPublication + { + Topic = routingKey, + RequestType = typeof(MyDeferredCommand), + MakeChannels = OnMissingChannel.Create + } + ); + + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateSyncChannel(_subscription); + + //how do we handle a command + IHandleRequests handler = new MyDeferredCommandHandler(); + + //hook up routing for the command processor + var subscriberRegistry = new SubscriberRegistry(); + subscriberRegistry.Register(); + + //once we read, how do we dispatch to a handler. N.B. we don't use this for reading here + IAmACommandProcessor commandProcessor = new CommandProcessor( + subscriberRegistry: subscriberRegistry, + handlerFactory: new QuickHandlerFactory(() => handler), + requestContextFactory: new InMemoryRequestContextFactory(), + policyRegistry: new PolicyRegistry() + ); + var provider = new CommandProcessorProvider(commandProcessor); + + var messageMapperRegistry = new MessageMapperRegistry( + new SimpleMessageMapperFactory(_ => new MyDeferredCommandMessageMapper()), + null + ); + messageMapperRegistry.Register(); - //pump messages from a channel to a handler - in essence we are building our own dispatcher in this test - _messagePump = new Reactor(provider, messageMapperRegistry, - null, new InMemoryRequestContextFactory(), _channel) - { - Channel = _channel, TimeOut = TimeSpan.FromMilliseconds(5000), RequeueCount = 3 - }; - } + //pump messages from a channel to a handler - in essence we are building our own dispatcher in this test + _messagePump = new Reactor(provider, messageMapperRegistry, + null, new InMemoryRequestContextFactory(), _channel) + { + Channel = _channel, TimeOut = TimeSpan.FromMilliseconds(5000), RequeueCount = 3 + }; + } - public int GetDLQCount(string queueName) + public int GetDLQCount(string queueName) + { + using var sqsClient = new AmazonSQSClient(_awsConnection.Credentials, _awsConnection.Region); + var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); + var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest { - using var sqsClient = new AmazonSQSClient(_awsConnection.Credentials, _awsConnection.Region); - var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); - var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest - { - QueueUrl = queueUrlResponse.QueueUrl, - WaitTimeSeconds = 5, - MessageSystemAttributeNames = ["ApproximateReceiveCount"], - MessageAttributeNames = new List { "All" } - }).GetAwaiter().GetResult(); - - if (response.HttpStatusCode != HttpStatusCode.OK) - { - throw new AmazonSQSException($"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); - } + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageSystemAttributeNames = ["ApproximateReceiveCount"], + MessageAttributeNames = new List { "All" } + }).GetAwaiter().GetResult(); - return response.Messages.Count; + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new AmazonSQSException($"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); } + return response.Messages.Count; + } - [Fact] - public async Task When_throwing_defer_action_respect_redrive() - { - //put something on an SNS topic, which will be delivered to our SQS queue - _sender.Send(_message); - //start a message pump, let it process messages - var task = Task.Factory.StartNew(() => _messagePump.Run(), TaskCreationOptions.LongRunning); - await Task.Delay(5000); + [Fact] + public async Task When_throwing_defer_action_respect_redrive() + { + //put something on an SNS topic, which will be delivered to our SQS queue + _sender.Send(_message); + + //start a message pump, let it process messages + var task = Task.Factory.StartNew(() => _messagePump.Run(), TaskCreationOptions.LongRunning); + await Task.Delay(5000); - //send a quit message to the pump to terminate it - var quitMessage = MessageFactory.CreateQuitMessage(_subscription.RoutingKey); - _channel.Enqueue(quitMessage); + //send a quit message to the pump to terminate it + var quitMessage = MessageFactory.CreateQuitMessage(_subscription.RoutingKey); + _channel.Enqueue(quitMessage); - //wait for the pump to stop once it gets a quit message - await Task.WhenAll(task); + //wait for the pump to stop once it gets a quit message + await Task.WhenAll(task); - await Task.Delay(5000); + await Task.Delay(5000); - //inspect the dlq - GetDLQCount(_dlqChannelName).Should().Be(1); - } + //inspect the dlq + GetDLQCount(_dlqChannelName).Should().Be(1); + } - public void Dispose() - { - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - } + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive_async.cs similarity index 54% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive_async.cs index c62eb0ca68..2585807160 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive_async.cs @@ -15,88 +15,71 @@ using Polly.Registry; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class SnsReDrivePolicySDlqTests : IDisposable, IAsyncDisposable +public class SnsReDrivePolicySDlqTestsAsync : IDisposable, IAsyncDisposable { private readonly IAmAMessagePump _messagePump; private readonly Message _message; private readonly string _dlqChannelName; - private readonly IAmAChannelSync _channel; + private readonly IAmAChannelAsync _channel; private readonly SqsMessageProducer _sender; private readonly AWSMessagingGatewayConnection _awsConnection; private readonly SqsSubscription _subscription; private readonly ChannelFactory _channelFactory; - public SnsReDrivePolicySDlqTests() + public SnsReDrivePolicySDlqTestsAsync() { string correlationId = Guid.NewGuid().ToString(); - const string replyTo = "http:\\queueUrl"; - const string contentType = "text\\plain"; + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; var channelName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); _dlqChannelName = $"Redrive-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var topicName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var partitionKey = $"PartitionKey-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - //how are we consuming _subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, - //don't block the redrive policy from owning retry management requeueCount: -1, - //delay before requeuing requeueDelay: TimeSpan.FromMilliseconds(50), - sqsType: SnsSqsType.Fifo, - //we want our SNS subscription to manage requeue limits using the DLQ for 'too many requeues' - redrivePolicy: new RedrivePolicy - ( - deadLetterQueueName: new ChannelName(_dlqChannelName), - maxReceiveCount: 2 - )); - - //what do we send + messagePumpType: MessagePumpType.Proactor, + redrivePolicy: new RedrivePolicy(new ChannelName(_dlqChannelName), 2) + ); + var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; _message = new Message( new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: partitionKey), + replyTo: new RoutingKey(replyTo), contentType: contentType), new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) ); - //Must have credentials stored in the SDK Credentials store or shared credentials file - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - _awsConnection = new AWSMessagingGatewayConnection(credentials, region); + _awsConnection = GatewayFactory.CreateFactory(); - //how do we send to the queue _sender = new SqsMessageProducer( _awsConnection, new SnsPublication { Topic = routingKey, RequestType = typeof(MyDeferredCommand), - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo + MakeChannels = OnMissingChannel.Create } ); - //We need to do this manually in a test - will create the channel from subscriber parameters - _channelFactory = new(_awsConnection); - _channel = _channelFactory.CreateSyncChannel(_subscription); + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateAsyncChannel(_subscription); - //how do we handle a command - IHandleRequests handler = new MyDeferredCommandHandler(); + IHandleRequestsAsync handler = new MyDeferredCommandHandlerAsync(); - //hook up routing for the command processor var subscriberRegistry = new SubscriberRegistry(); - subscriberRegistry.Register(); + subscriberRegistry.RegisterAsync(); - //once we read, how do we dispatch to a handler. N.B. we don't use this for reading here IAmACommandProcessor commandProcessor = new CommandProcessor( subscriberRegistry: subscriberRegistry, - handlerFactory: new QuickHandlerFactory(() => handler), + handlerFactory: new QuickHandlerFactoryAsync(() => handler), requestContextFactory: new InMemoryRequestContextFactory(), policyRegistry: new PolicyRegistry() ); @@ -108,58 +91,50 @@ public SnsReDrivePolicySDlqTests() ); messageMapperRegistry.Register(); - //pump messages from a channel to a handler - in essence we are building our own dispatcher in this test - //pump messages from a channel to a handler - in essence we are building our own dispatcher in this test - _messagePump = new Reactor(provider, messageMapperRegistry, - null, new InMemoryRequestContextFactory(), _channel) + _messagePump = new Proactor(provider, messageMapperRegistry, + new EmptyMessageTransformerFactoryAsync(), new InMemoryRequestContextFactory(), _channel) { Channel = _channel, TimeOut = TimeSpan.FromMilliseconds(5000), RequeueCount = 3 }; } - public int GetDLQCount(string queueName) + public async Task GetDLQCountAsync(string queueName) { using var sqsClient = new AmazonSQSClient(_awsConnection.Credentials, _awsConnection.Region); - var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); - var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest + var queueUrlResponse = await sqsClient.GetQueueUrlAsync(queueName); + var response = await sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest { QueueUrl = queueUrlResponse.QueueUrl, WaitTimeSeconds = 5, - MessageSystemAttributeNames = ["ApproximateReceiveCount"], + MessageSystemAttributeNames = new List { "ApproximateReceiveCount" }, MessageAttributeNames = new List { "All" } - }).GetAwaiter().GetResult(); + }); if (response.HttpStatusCode != HttpStatusCode.OK) { - throw new AmazonSQSException( - $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + throw new AmazonSQSException($"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); } return response.Messages.Count; } - - [Fact] - public async Task When_throwing_defer_action_respect_redrive() + [Fact(Skip = "Failing async tests caused by task scheduler issues")] + public async Task When_throwing_defer_action_respect_redrive_async() { - //put something on an SNS topic, which will be delivered to our SQS queue - _sender.Send(_message); + await _sender.SendAsync(_message); - //start a message pump, let it process messages var task = Task.Factory.StartNew(() => _messagePump.Run(), TaskCreationOptions.LongRunning); await Task.Delay(5000); - //send a quit message to the pump to terminate it var quitMessage = MessageFactory.CreateQuitMessage(_subscription.RoutingKey); _channel.Enqueue(quitMessage); - //wait for the pump to stop once it gets a quit message await Task.WhenAll(task); await Task.Delay(5000); - //inspect the dlq - GetDLQCount(_dlqChannelName).Should().Be(1); + int dlqCount = await GetDLQCountAsync(_dlqChannelName); + dlqCount.Should().Be(1); } public void Dispose() diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs index 53bd529068..684be1d5b6 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs @@ -5,42 +5,40 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +public class AWSValidateMissingTopicTests { - [Trait("Category", "AWS")] - public class AWSValidateMissingTopicTests - { - private readonly AWSMessagingGatewayConnection _awsConnection; - private readonly RoutingKey _routingKey; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly RoutingKey _routingKey; - public AWSValidateMissingTopicTests() - { - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _routingKey = new RoutingKey(topicName); + public AWSValidateMissingTopicTests() + { + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey(topicName); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - _awsConnection = new AWSMessagingGatewayConnection(credentials, region); + _awsConnection = GatewayFactory.CreateFactory(); - //Because we don't use channel factory to create the infrastructure -it won't exist - } + //Because we don't use channel factory to create the infrastructure -it won't exist + } - [Theory] - [InlineData(SnsSqsType.Standard, null)] - [InlineData(SnsSqsType.Fifo, "123")] - public void When_topic_missing_verify_throws(SnsSqsType type, string partitionKey) - { - //arrange - var producer = new SqsMessageProducer(_awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Validate, - SnsType = type - }); + [Theory] + [InlineData(SnsSqsType.Standard, null)] + [InlineData(SnsSqsType.Fifo, "123")] + public void When_topic_missing_verify_throws(SnsSqsType type, string partitionKey) + { + //arrange + var producer = new SqsMessageProducer(_awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Validate, + SnsType = type + }); - //act && assert - Assert.Throws(() => producer.Send(new Message( - new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text") { PartitionKey = partitionKey}, - new MessageBody("Test")))); - } + //act && assert + Assert.Throws(() => producer.Send(new Message( + new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text") { PartitionKey = partitionKey}, + new MessageBody("Test")))); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws_async.cs new file mode 100644 index 0000000000..701d0b95f7 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws_async.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; + +[Trait("Category", "AWS")] +public class AWSValidateMissingTopicTestsAsync +{ + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly RoutingKey _routingKey; + + public AWSValidateMissingTopicTestsAsync() + { + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey(topicName); + + _awsConnection = GatewayFactory.CreateFactory(); + + // Because we don't use channel factory to create the infrastructure - it won't exist + } + + [Fact] + public async Task When_topic_missing_verify_throws_async() + { + // arrange + var producer = new SqsMessageProducer(_awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Validate + }); + + // act & assert + await Assert.ThrowsAsync(async () => + await producer.SendAsync(new Message( + new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text"), + new MessageBody("Test")))); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_a_message_consumer_reads_multiple_messages_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_a_message_consumer_reads_multiple_messages_async.cs deleted file mode 100644 index 3e06a5ecbe..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_a_message_consumer_reads_multiple_messages_async.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway -{ - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class SQSBufferedConsumerTestsAsync : IDisposable, IAsyncDisposable - { - private readonly SqsMessageProducer _messageProducer; - private readonly SqsMessageConsumer _consumer; - private readonly string _topicName; - private readonly ChannelFactory _channelFactory; - private const string ContentType = "text\\plain"; - private const int BufferSize = 3; - private const int MessageCount = 4; - - public SQSBufferedConsumerTestsAsync() - { - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - _channelFactory = new ChannelFactory(awsConnection); - var channelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _topicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - - //we need the channel to create the queues and notifications - var routingKey = new RoutingKey(_topicName); - - var channel = _channelFactory.CreateAsyncChannelAsync(new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - bufferSize: BufferSize, - makeChannels: OnMissingChannel.Create - )).GetAwaiter().GetResult(); - - //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel - //just for the tests, so create a new consumer from the properties - _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), BufferSize); - _messageProducer = new SqsMessageProducer(awsConnection, - new SnsPublication { MakeChannels = OnMissingChannel.Create }); - } - - [Fact] - public async Task When_a_message_consumer_reads_multiple_messages_async() - { - var routingKey = new RoutingKey(_topicName); - - var messageOne = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: ContentType), - new MessageBody("test content one") - ); - - var messageTwo = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: ContentType), - new MessageBody("test content two") - ); - - var messageThree = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: ContentType), - new MessageBody("test content three") - ); - - var messageFour = new Message( - new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), contentType: ContentType), - new MessageBody("test content four") - ); - - //send MESSAGE_COUNT messages - await _messageProducer.SendAsync(messageOne); - await _messageProducer.SendAsync(messageTwo); - await _messageProducer.SendAsync(messageThree); - await _messageProducer.SendAsync(messageFour); - - int iteration = 0; - var messagesReceived = new List(); - var messagesReceivedCount = messagesReceived.Count; - do - { - iteration++; - var outstandingMessageCount = MessageCount - messagesReceivedCount; - - //retrieve messages - var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(10000)); - - messages.Length.Should().BeLessOrEqualTo(outstandingMessageCount); - - //should not receive more than buffer in one hit - messages.Length.Should().BeLessOrEqualTo(BufferSize); - - var moreMessages = messages.Where(m => m.Header.MessageType == MessageType.MT_COMMAND); - foreach (var message in moreMessages) - { - messagesReceived.Add(message); - await _consumer.AcknowledgeAsync(message); - } - - messagesReceivedCount = messagesReceived.Count; - - await Task.Delay(1000); - - } while ((iteration <= 5) && (messagesReceivedCount < MessageCount)); - - messagesReceivedCount.Should().Be(4); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - await _messageProducer.DisposeAsync(); - } - - public void Dispose() - { - _channelFactory.DeleteTopicAsync().GetAwaiter().GetResult(); - _channelFactory.DeleteQueueAsync().GetAwaiter().GetResult(); - _messageProducer.DisposeAsync().GetAwaiter().GetResult(); - } - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_customising_aws_client_config_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_customising_aws_client_config_async.cs deleted file mode 100644 index 5f7be4ca28..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_customising_aws_client_config_async.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway -{ - [Trait("Category", "AWS")] - public class CustomisingAwsClientConfigTestsAsync : IDisposable, IAsyncDisposable - { - private readonly Message _message; - private readonly IAmAChannelAsync _channel; - private readonly SqsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - - private readonly InterceptingDelegatingHandler _publishHttpHandler = new(); - private readonly InterceptingDelegatingHandler _subscribeHttpHandler = new(); - - public CustomisingAwsClientConfigTestsAsync() - { - MyCommand myCommand = new() {Value = "Test"}; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - messagePumpType: MessagePumpType.Proactor, - routingKey: routingKey - ); - - _message = new Message( - new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) myCommand, JsonSerialisationOptions.Options)) - ); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var subscribeAwsConnection = new AWSMessagingGatewayConnection(credentials, region, config => - { - config.HttpClientFactory = new InterceptingHttpClientFactory(_subscribeHttpHandler); - }); - - _channelFactory = new ChannelFactory(subscribeAwsConnection); - _channel = _channelFactory.CreateAsyncChannel(subscription); - - var publishAwsConnection = new AWSMessagingGatewayConnection(credentials, region, config => - { - config.HttpClientFactory = new InterceptingHttpClientFactory(_publishHttpHandler); - }); - - _messageProducer = new SqsMessageProducer(publishAwsConnection, new SnsPublication{Topic = new RoutingKey(topicName), MakeChannels = OnMissingChannel.Create}); - } - - [Fact] - public async Task When_customising_aws_client_config() - { - //arrange - await _messageProducer.SendAsync(_message); - - await Task.Delay(1000); - - var message =await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - - //clear the queue - await _channel.AcknowledgeAsync(message); - - //publish_and_subscribe_should_use_custom_http_client_factory - _publishHttpHandler.RequestCount.Should().BeGreaterThan(0); - _subscribeHttpHandler.RequestCount.Should().BeGreaterThan(0); - } - - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_assume_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_assume_async.cs deleted file mode 100644 index e1d5b3d92c..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infastructure_exists_can_assume_async.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway -{ - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class AWSAssumeInfrastructureTestsAsync : IDisposable, IAsyncDisposable - { private readonly Message _message; - private readonly SqsMessageConsumer _consumer; - private readonly SqsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly MyCommand _myCommand; - - public AWSAssumeInfrastructureTestsAsync() - { - _myCommand = new MyCommand{Value = "Test"}; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Create - ); - - _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) - ); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - //We need to do this manually in a test - will create the channel from subscriber parameters - //This doesn't look that different from our create tests - this is because we create using the channel factory in - //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) - _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateAsyncChannel(subscription); - - //Now change the subscription to validate, just check what we made - subscription = new( - name: new SubscriptionName(channelName), - channelName: channel.Name, - routingKey: routingKey, - messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Assume - ); - - _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Assume}); - - _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); - } - - [Fact] - public async Task When_infastructure_exists_can_assume() - { - //arrange - await _messageProducer.SendAsync(_message); - - var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - - //Assert - var message = messages.First(); - message.Id.Should().Be(_myCommand.Id); - - //clear the queue - await _consumer.AcknowledgeAsync(message); - } - - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infrastructure_exists_can_verify_by_arn_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infrastructure_exists_can_verify_by_arn_async.cs deleted file mode 100644 index fcec7e29a2..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infrastructure_exists_can_verify_by_arn_async.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using Amazon.SimpleNotificationService; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway -{ - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class AWSValidateInfrastructureByArnTestsAsync : IAsyncDisposable, IDisposable - { - private readonly Message _message; - private readonly IAmAMessageConsumerAsync _consumer; - private readonly SqsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly MyCommand _myCommand; - - public AWSValidateInfrastructureByArnTestsAsync() - { - _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey($"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Create - ); - - _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) - ); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateAsyncChannel(subscription); - - var topicArn = FindTopicArn(credentials, region, routingKey.Value).Result; - var routingKeyArn = new RoutingKey(topicArn); - - subscription = new( - name: new SubscriptionName(channelName), - channelName: channel.Name, - routingKey: routingKeyArn, - findTopicBy: TopicFindBy.Arn, - makeChannels: OnMissingChannel.Validate - ); - - _messageProducer = new SqsMessageProducer( - awsConnection, - new SnsPublication - { - Topic = routingKey, - TopicArn = topicArn, - FindTopicBy = TopicFindBy.Arn, - MakeChannels = OnMissingChannel.Validate - }); - - _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); - } - - [Fact] - public async Task When_infrastructure_exists_can_verify_async() - { - await _messageProducer.SendAsync(_message); - - await Task.Delay(1000); - - var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - - var message = messages.First(); - message.Id.Should().Be(_myCommand.Id); - - await _consumer.AcknowledgeAsync(message); - } - - private async Task FindTopicArn(AWSCredentials credentials, RegionEndpoint region, string topicName) - { - var snsClient = new AmazonSimpleNotificationServiceClient(credentials, region); - var topicResponse = await snsClient.FindTopicAsync(topicName); - return topicResponse.TopicArn; - } - - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - ((IAmAMessageConsumerSync)_consumer).Dispose(); - _messageProducer.Dispose(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - await _consumer.DisposeAsync(); - await _messageProducer.DisposeAsync(); - } - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infrastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infrastructure_exists_can_verify_by_convention.cs deleted file mode 100644 index 2d07a58097..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_infrastructure_exists_can_verify_by_convention.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway -{ - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class AWSValidateInfrastructureByConventionTestsAsync : IAsyncDisposable, IDisposable - { - private readonly Message _message; - private readonly IAmAMessageConsumerAsync _consumer; - private readonly SqsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly MyCommand _myCommand; - - public AWSValidateInfrastructureByConventionTestsAsync() - { - _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Create - ); - - _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) - ); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateAsyncChannel(subscription); - - subscription = new( - name: new SubscriptionName(channelName), - channelName: channel.Name, - routingKey: routingKey, - findTopicBy: TopicFindBy.Convention, - messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Validate - ); - - _messageProducer = new SqsMessageProducer( - awsConnection, - new SnsPublication - { - FindTopicBy = TopicFindBy.Convention, - MakeChannels = OnMissingChannel.Validate - } - ); - - _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); - } - - [Fact] - public async Task When_infrastructure_exists_can_verify_async() - { - await _messageProducer.SendAsync(_message); - - await Task.Delay(1000); - - var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - - var message = messages.First(); - message.Id.Should().Be(_myCommand.Id); - - await _consumer.AcknowledgeAsync(message); - } - - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - ((IAmAMessageConsumerSync)_consumer).Dispose(); - _messageProducer.Dispose(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - await _consumer.DisposeAsync(); - await _messageProducer.DisposeAsync(); - } - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_posting_a_message_via_the_messaging_gateway_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_posting_a_message_via_the_messaging_gateway_async.cs deleted file mode 100644 index c25b8f5b88..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_posting_a_message_via_the_messaging_gateway_async.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway -{ - [Trait("Category", "AWS")] - public class SqsMessageProducerSendAsyncTests : IAsyncDisposable, IDisposable - { - private readonly Message _message; - private readonly IAmAChannelAsync _channel; - private readonly SqsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly MyCommand _myCommand; - private readonly string _correlationId; - private readonly string _replyTo; - private readonly string _contentType; - private readonly string _topicName; - - public SqsMessageProducerSendAsyncTests() - { - _myCommand = new MyCommand { Value = "Test" }; - _correlationId = Guid.NewGuid().ToString(); - _replyTo = "http:\\queueUrl"; - _contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(_topicName); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Proactor, - rawMessageDelivery: false - ); - - _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: _correlationId, - replyTo: new RoutingKey(_replyTo), contentType: _contentType), - new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) - ); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - _channelFactory = new ChannelFactory(awsConnection); - _channel = _channelFactory.CreateAsyncChannel(subscription); - - _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication { Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create }); - } - - [Fact] - public async Task When_posting_a_message_via_the_producer_async() - { - // arrange - _message.Header.Subject = "test subject"; - await _messageProducer.SendAsync(_message); - - await Task.Delay(1000); - - var message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - - // clear the queue - await _channel.AcknowledgeAsync(message); - - // should_send_the_message_to_aws_sqs - message.Header.MessageType.Should().Be(MessageType.MT_COMMAND); - - message.Id.Should().Be(_myCommand.Id); - message.Redelivered.Should().BeFalse(); - message.Header.MessageId.Should().Be(_myCommand.Id); - message.Header.Topic.Value.Should().Contain(_topicName); - message.Header.CorrelationId.Should().Be(_correlationId); - message.Header.ReplyTo.Should().Be(_replyTo); - message.Header.ContentType.Should().Be(_contentType); - message.Header.HandledCount.Should().Be(0); - message.Header.Subject.Should().Be(_message.Header.Subject); - // allow for clock drift in the following test, more important to have a contemporary timestamp than anything - message.Header.TimeStamp.Should().BeAfter(RoundToSeconds(DateTime.UtcNow.AddMinutes(-1))); - message.Header.Delayed.Should().Be(TimeSpan.Zero); - // {"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} - message.Body.Value.Should().Be(_message.Body.Value); - } - - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - _messageProducer.Dispose(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - await _messageProducer.DisposeAsync(); - } - - private static DateTime RoundToSeconds(DateTime dateTime) - { - return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind); - } - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_queues_missing_assume_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_queues_missing_assume_throws_async.cs deleted file mode 100644 index 178316874f..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_queues_missing_assume_throws_async.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using Amazon.SQS.Model; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway -{ - [Trait("Category", "AWS")] - public class AWSAssumeQueuesTestsAsync : IAsyncDisposable, IDisposable - { - private readonly ChannelFactory _channelFactory; - private readonly IAmAMessageConsumerAsync _consumer; - - public AWSAssumeQueuesTestsAsync() - { - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - var subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - makeChannels: OnMissingChannel.Assume, - messagePumpType: MessagePumpType.Proactor - ); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - //create the topic, we want the queue to be the issue - //We need to create the topic at least, to check the queues - var producer = new SqsMessageProducer(awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create - }); - - producer.ConfirmTopicExistsAsync(topicName).Wait(); - - _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateAsyncChannel(subscription); - - //We need to create the topic at least, to check the queues - _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); - } - - [Fact] - public async Task When_queues_missing_assume_throws_async() - { - //we will try to get the queue url, and fail because it does not exist - await Assert.ThrowsAsync(async () => await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(1000))); - } - - public void Dispose() - { - _channelFactory.DeleteTopicAsync().Wait(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - } - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_queues_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_queues_missing_verify_throws_async.cs deleted file mode 100644 index 4fb96ac19c..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_queues_missing_verify_throws_async.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using Amazon.SQS.Model; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway -{ - [Trait("Category", "AWS")] - public class AWSValidateQueuesTestsAsync : IAsyncDisposable - { - private readonly AWSMessagingGatewayConnection _awsConnection; - private readonly SqsSubscription _subscription; - private ChannelFactory _channelFactory; - - public AWSValidateQueuesTestsAsync() - { - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - _subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - makeChannels: OnMissingChannel.Validate - ); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - _awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - // We need to create the topic at least, to check the queues - var producer = new SqsMessageProducer(_awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create - }); - producer.ConfirmTopicExistsAsync(topicName).Wait(); - } - - [Fact] - public async Task When_queues_missing_verify_throws_async() - { - // We have no queues so we should throw - // We need to do this manually in a test - will create the channel from subscriber parameters - _channelFactory = new ChannelFactory(_awsConnection); - await Assert.ThrowsAsync(async () => await _channelFactory.CreateAsyncChannelAsync(_subscription)); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - } - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_raw_message_delivery_disabled_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_raw_message_delivery_disabled_async.cs deleted file mode 100644 index ad435b5a46..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_raw_message_delivery_disabled_async.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway -{ - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class SqsRawMessageDeliveryTestsAsync : IAsyncDisposable, IDisposable - { - private readonly SqsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly IAmAChannelAsync _channel; - private readonly RoutingKey _routingKey; - - public SqsRawMessageDeliveryTestsAsync() - { - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - _channelFactory = new ChannelFactory(awsConnection); - var channelName = $"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _routingKey = new RoutingKey($"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); - - var bufferSize = 10; - - // Set rawMessageDelivery to false - _channel = _channelFactory.CreateAsyncChannel(new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: _routingKey, - bufferSize: bufferSize, - makeChannels: OnMissingChannel.Create, - rawMessageDelivery: false)); - - _messageProducer = new SqsMessageProducer(awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create - }); - } - - [Fact] - public async Task When_raw_message_delivery_disabled_async() - { - // Arrange - var messageHeader = new MessageHeader( - Guid.NewGuid().ToString(), - _routingKey, - MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), - replyTo: RoutingKey.Empty, - contentType: "text\\plain"); - - var customHeaderItem = new KeyValuePair("custom-header-item", "custom-header-item-value"); - messageHeader.Bag.Add(customHeaderItem.Key, customHeaderItem.Value); - - var messageToSend = new Message(messageHeader, new MessageBody("test content one")); - - // Act - await _messageProducer.SendAsync(messageToSend); - - var messageReceived = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(10000)); - - await _channel.AcknowledgeAsync(messageReceived); - - // Assert - messageReceived.Id.Should().Be(messageToSend.Id); - messageReceived.Header.Topic.Should().Be(messageToSend.Header.Topic); - messageReceived.Header.MessageType.Should().Be(messageToSend.Header.MessageType); - messageReceived.Header.CorrelationId.Should().Be(messageToSend.Header.CorrelationId); - messageReceived.Header.ReplyTo.Should().Be(messageToSend.Header.ReplyTo); - messageReceived.Header.ContentType.Should().Be(messageToSend.Header.ContentType); - messageReceived.Header.Bag.Should().ContainKey(customHeaderItem.Key).And.ContainValue(customHeaderItem.Value); - messageReceived.Body.Value.Should().Be(messageToSend.Body.Value); - } - - public void Dispose() - { - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_rejecting_a_message_through_gateway_with_requeue_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_rejecting_a_message_through_gateway_with_requeue_async.cs deleted file mode 100644 index 3af75acf8e..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_rejecting_a_message_through_gateway_with_requeue_async.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway -{ - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class SqsMessageConsumerRequeueTestsAsync : IDisposable, IAsyncDisposable - { - private readonly Message _message; - private readonly IAmAChannelAsync _channel; - private readonly SqsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly MyCommand _myCommand; - - public SqsMessageConsumerRequeueTestsAsync() - { - _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Create - ); - - _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) - ); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - _channelFactory = new ChannelFactory(awsConnection); - _channel = _channelFactory.CreateAsyncChannel(subscription); - - _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); - } - - [Fact] - public async Task When_rejecting_a_message_through_gateway_with_requeue_async() - { - await _messageProducer.SendAsync(_message); - - var message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - - await _channel.RejectAsync(message); - - // Let the timeout change - await Task.Delay(TimeSpan.FromMilliseconds(3000)); - - // should requeue_the_message - message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - - // clear the queue - await _channel.AcknowledgeAsync(message); - - message.Id.Should().Be(_myCommand.Id); - } - - public void Dispose() - { - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_requeueing_a_message_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_requeueing_a_message_async.cs deleted file mode 100644 index faa37a7128..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_requeueing_a_message_async.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using Amazon.Runtime.CredentialManagement; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway -{ - [Trait("Category", "AWS")] - public class SqsMessageProducerRequeueTestsAsync : IDisposable, IAsyncDisposable - { - private readonly IAmAMessageProducerAsync _sender; - private Message _requeuedMessage; - private Message _receivedMessage; - private readonly IAmAChannelAsync _channel; - private readonly ChannelFactory _channelFactory; - private readonly Message _message; - - public SqsMessageProducerRequeueTestsAsync() - { - MyCommand myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - var subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Create - ); - - _message = new Message( - new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) - ); - - new CredentialProfileStoreChain(); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - _sender = new SqsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); - - _channelFactory = new ChannelFactory(awsConnection); - _channel = _channelFactory.CreateAsyncChannel(subscription); - } - - [Fact] - public async Task When_requeueing_a_message_async() - { - await _sender.SendAsync(_message); - _receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - await _channel.RequeueAsync(_receivedMessage); - - _requeuedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - - await _channel.AcknowledgeAsync(_requeuedMessage); - - _requeuedMessage.Body.Value.Should().Be(_receivedMessage.Body.Value); - } - - public void Dispose() - { - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_requeueing_redrives_to_the_dlq_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_requeueing_redrives_to_the_dlq_async.cs deleted file mode 100644 index 65d1fc3706..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_requeueing_redrives_to_the_dlq_async.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using Amazon.SQS; -using Amazon.SQS.Model; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway -{ - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class SqsMessageProducerDlqTestsAsync : IDisposable, IAsyncDisposable - { - private readonly SqsMessageProducer _sender; - private readonly IAmAChannelAsync _channel; - private readonly ChannelFactory _channelFactory; - private readonly Message _message; - private readonly AWSMessagingGatewayConnection _awsConnection; - private readonly string _dlqChannelName; - - public SqsMessageProducerDlqTestsAsync() - { - MyCommand myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _dlqChannelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Proactor, - redrivePolicy: new RedrivePolicy(_dlqChannelName, 2) - ); - - _message = new Message( - new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) - ); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - _awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - _sender = new SqsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); - - _sender.ConfirmTopicExistsAsync(topicName).Wait(); - - _channelFactory = new ChannelFactory(_awsConnection); - _channel = _channelFactory.CreateAsyncChannel(subscription); - } - - [Fact] - public async Task When_requeueing_redrives_to_the_queue_async() - { - await _sender.SendAsync(_message); - var receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - await _channel.RequeueAsync(receivedMessage); - - receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - await _channel.RequeueAsync(receivedMessage); - - receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - await _channel.RequeueAsync(receivedMessage); - - await Task.Delay(5000); - - int dlqCount = await GetDLQCountAsync(_dlqChannelName); - dlqCount.Should().Be(1); - } - - public async Task GetDLQCountAsync(string queueName) - { - using var sqsClient = new AmazonSQSClient(_awsConnection.Credentials, _awsConnection.Region); - var queueUrlResponse = await sqsClient.GetQueueUrlAsync(queueName); - var response = await sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest - { - QueueUrl = queueUrlResponse.QueueUrl, - WaitTimeSeconds = 5, - MessageAttributeNames = new List { "All", "ApproximateReceiveCount" } - }); - - if (response.HttpStatusCode != HttpStatusCode.OK) - { - throw new AmazonSQSException($"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); - } - - return response.Messages.Count; - } - - public void Dispose() - { - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_throwing_defer_action_respect_redrive_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_throwing_defer_action_respect_redrive_async.cs deleted file mode 100644 index f7445b49b7..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_throwing_defer_action_respect_redrive_async.cs +++ /dev/null @@ -1,153 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Text.Json; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using Amazon.SQS; -using Amazon.SQS.Model; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Paramore.Brighter.ServiceActivator; -using Polly.Registry; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway -{ - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class SnsReDrivePolicySDlqTestsAsync : IDisposable, IAsyncDisposable - { - private readonly IAmAMessagePump _messagePump; - private readonly Message _message; - private readonly string _dlqChannelName; - private readonly IAmAChannelAsync _channel; - private readonly SqsMessageProducer _sender; - private readonly AWSMessagingGatewayConnection _awsConnection; - private readonly SqsSubscription _subscription; - private readonly ChannelFactory _channelFactory; - - public SnsReDrivePolicySDlqTestsAsync() - { - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _dlqChannelName = $"Redrive-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - _subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - requeueCount: -1, - requeueDelay: TimeSpan.FromMilliseconds(50), - messagePumpType: MessagePumpType.Proactor, - redrivePolicy: new RedrivePolicy(new ChannelName(_dlqChannelName), 2) - ); - - var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; - _message = new Message( - new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) - ); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - _awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - _sender = new SqsMessageProducer( - _awsConnection, - new SnsPublication - { - Topic = routingKey, - RequestType = typeof(MyDeferredCommand), - MakeChannels = OnMissingChannel.Create - } - ); - - _channelFactory = new ChannelFactory(_awsConnection); - _channel = _channelFactory.CreateAsyncChannel(_subscription); - - IHandleRequestsAsync handler = new MyDeferredCommandHandlerAsync(); - - var subscriberRegistry = new SubscriberRegistry(); - subscriberRegistry.RegisterAsync(); - - IAmACommandProcessor commandProcessor = new CommandProcessor( - subscriberRegistry: subscriberRegistry, - handlerFactory: new QuickHandlerFactoryAsync(() => handler), - requestContextFactory: new InMemoryRequestContextFactory(), - policyRegistry: new PolicyRegistry() - ); - var provider = new CommandProcessorProvider(commandProcessor); - - var messageMapperRegistry = new MessageMapperRegistry( - new SimpleMessageMapperFactory(_ => new MyDeferredCommandMessageMapper()), - null - ); - messageMapperRegistry.Register(); - - _messagePump = new Proactor(provider, messageMapperRegistry, - new EmptyMessageTransformerFactoryAsync(), new InMemoryRequestContextFactory(), _channel) - { - Channel = _channel, TimeOut = TimeSpan.FromMilliseconds(5000), RequeueCount = 3 - }; - } - - public async Task GetDLQCountAsync(string queueName) - { - using var sqsClient = new AmazonSQSClient(_awsConnection.Credentials, _awsConnection.Region); - var queueUrlResponse = await sqsClient.GetQueueUrlAsync(queueName); - var response = await sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest - { - QueueUrl = queueUrlResponse.QueueUrl, - WaitTimeSeconds = 5, - MessageSystemAttributeNames = new List { "ApproximateReceiveCount" }, - MessageAttributeNames = new List { "All" } - }); - - if (response.HttpStatusCode != HttpStatusCode.OK) - { - throw new AmazonSQSException($"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); - } - - return response.Messages.Count; - } - - [Fact(Skip = "Failing async tests caused by task scheduler issues")] - public async Task When_throwing_defer_action_respect_redrive_async() - { - await _sender.SendAsync(_message); - - var task = Task.Factory.StartNew(() => _messagePump.Run(), TaskCreationOptions.LongRunning); - await Task.Delay(5000); - - var quitMessage = MessageFactory.CreateQuitMessage(_subscription.RoutingKey); - _channel.Enqueue(quitMessage); - - await Task.WhenAll(task); - - await Task.Delay(5000); - - int dlqCount = await GetDLQCountAsync(_dlqChannelName); - dlqCount.Should().Be(1); - } - - public void Dispose() - { - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_topic_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_topic_missing_verify_throws_async.cs deleted file mode 100644 index dc3102fc53..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/When_topic_missing_verify_throws_async.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway -{ - [Trait("Category", "AWS")] - public class AWSValidateMissingTopicTestsAsync - { - private readonly AWSMessagingGatewayConnection _awsConnection; - private readonly RoutingKey _routingKey; - - public AWSValidateMissingTopicTestsAsync() - { - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _routingKey = new RoutingKey(topicName); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - _awsConnection = new AWSMessagingGatewayConnection(credentials, region); - - // Because we don't use channel factory to create the infrastructure - it won't exist - } - - [Fact] - public async Task When_topic_missing_verify_throws_async() - { - // arrange - var producer = new SqsMessageProducer(_awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Validate - }); - - // act & assert - await Assert.ThrowsAsync(async () => - await producer.SendAsync(new Message( - new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text"), - new MessageBody("Test")))); - } - } -} From c94003eeb27aa549830087ebaa3fae10e122908a Mon Sep 17 00:00:00 2001 From: Rafael Andrade Date: Tue, 31 Dec 2024 14:39:37 +0000 Subject: [PATCH 06/18] GH-1294 Use AwsFactory --- src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs index 7d21d062ea..86c74d4838 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs @@ -160,7 +160,7 @@ public async Task DeleteTopicAsync() if (ChannelTopicArn == null) return; - using var snsClient = new AmazonSimpleNotificationServiceClient(AwsConnection.Credentials, AwsConnection.Region); + using var snsClient = new AWSClientFactory(AwsConnection).CreateSnsClient(); (bool exists, string? _) = await new ValidateTopicByArn(snsClient).ValidateAsync(ChannelTopicArn); if (exists) { From 85db988cde67154e464aa39202595089509aca37 Mon Sep 17 00:00:00 2001 From: Rafael Andrade Date: Thu, 2 Jan 2025 10:48:47 +0000 Subject: [PATCH 07/18] GH-1294 Fixes the majority of test, improve support to localstack and rename SQS to SNS --- docker-compose-localstack.yaml | 3 +- .../AWSClientFactory.cs | 57 ++---- .../AWSMessagingGateway.cs | 6 +- .../ChannelFactory.cs | 177 +++++++++++------- .../SnsMessageProducer.cs | 177 ++++++++++++++++++ .../SnsMessageProducerFactory.cs | 4 +- .../SqsMessageConsumer.cs | 87 ++++++--- .../SqsMessageProducer.cs | 174 ----------------- .../ValidateTopicByArnConvention.cs | 2 +- .../ValidateTopicByName.cs | 5 +- .../AWSS3Connection.cs | 40 ++-- .../S3LuggageOptions.cs | 30 +-- .../Helpers/AWSClientFactory.cs | 104 ++++++++++ ...essage_consumer_reads_multiple_messages.cs | 4 +- ..._consumer_reads_multiple_messages_async.cs | 4 +- .../When_customising_aws_client_config.cs | 4 +- ...hen_customising_aws_client_config_async.cs | 4 +- .../When_infastructure_exists_can_assume.cs | 16 +- ...n_infastructure_exists_can_assume_async.cs | 4 +- .../When_infastructure_exists_can_verify.cs | 4 +- ..._infastructure_exists_can_verify_by_arn.cs | 40 ++-- ...ructure_exists_can_verify_by_convention.cs | 4 +- ..._infrastructure_exists_can_verify_async.cs | 4 +- ...tructure_exists_can_verify_by_arn_async.cs | 14 +- ...ructure_exists_can_verify_by_convention.cs | 4 +- ...ing_a_message_via_the_messaging_gateway.cs | 4 +- ...message_via_the_messaging_gateway_async.cs | 4 +- .../When_queues_missing_assume_throws.cs | 2 +- ...When_queues_missing_assume_throws_async.cs | 2 +- .../When_queues_missing_verify_throws.cs | 2 +- ...When_queues_missing_verify_throws_async.cs | 2 +- .../When_raw_message_delivery_disabled.cs | 4 +- ...hen_raw_message_delivery_disabled_async.cs | 4 +- ..._a_message_through_gateway_with_requeue.cs | 4 +- ...sage_through_gateway_with_requeue_async.cs | 4 +- .../Standard/When_requeueing_a_message.cs | 2 +- .../When_requeueing_a_message_async.cs | 2 +- .../When_requeueing_redrives_to_the_dlq.cs | 58 +++--- ...en_requeueing_redrives_to_the_dlq_async.cs | 11 +- ...n_throwing_defer_action_respect_redrive.cs | 33 ++-- ...wing_defer_action_respect_redrive_async.cs | 6 +- .../When_topic_missing_verify_throws.cs | 2 +- .../When_topic_missing_verify_throws_async.cs | 2 +- ...reating_luggagestore_missing_parameters.cs | 6 +- .../When_unwrapping_a_large_message.cs | 39 ++-- .../When_uploading_luggage_to_S3.cs | 7 +- .../When_validating_a_luggage_store_exists.cs | 8 +- .../When_wrapping_a_large_message.cs | 6 +- 48 files changed, 676 insertions(+), 510 deletions(-) create mode 100644 src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs delete mode 100644 src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/Helpers/AWSClientFactory.cs diff --git a/docker-compose-localstack.yaml b/docker-compose-localstack.yaml index 2f4208247f..6efa721fd8 100644 --- a/docker-compose-localstack.yaml +++ b/docker-compose-localstack.yaml @@ -5,7 +5,8 @@ services: image: localstack/localstack environment: # LocalStack configuration: https://docs.localstack.cloud/references/configuration/ - - "SERVICES=s3,sqs,sns,dynamodb" + - "SERVICES=s3,sqs,sns,sts,dynamodb" + - "DEFAULT_REGION=eu-west-1" - "DEBUG=1" ports: - "4566:4566" # LocalStack Gateway diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSClientFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSClientFactory.cs index 87a80ca9e9..a02f17fa30 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSClientFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSClientFactory.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2022 Ian Cooper @@ -31,68 +32,40 @@ THE SOFTWARE. */ namespace Paramore.Brighter.MessagingGateway.AWSSQS; -internal class AWSClientFactory +internal class AWSClientFactory( + AWSCredentials credentials, + RegionEndpoint region, + Action? clientConfigAction) { - private readonly AWSCredentials _credentials; - private readonly RegionEndpoint _region; - private readonly Action? _clientConfigAction; - public AWSClientFactory(AWSMessagingGatewayConnection connection) + : this(connection.Credentials, connection.Region, connection.ClientConfigAction) { - _credentials = connection.Credentials; - _region = connection.Region; - _clientConfigAction = connection.ClientConfigAction; - } - - public AWSClientFactory(AWSCredentials credentials, RegionEndpoint region, Action? clientConfigAction) - { - _credentials = credentials; - _region = region; - _clientConfigAction = clientConfigAction; } public AmazonSimpleNotificationServiceClient CreateSnsClient() { - var config = new AmazonSimpleNotificationServiceConfig - { - RegionEndpoint = _region - }; + var config = new AmazonSimpleNotificationServiceConfig { RegionEndpoint = region }; - if (_clientConfigAction != null) - { - _clientConfigAction(config); - } + clientConfigAction?.Invoke(config); - return new AmazonSimpleNotificationServiceClient(_credentials, config); + return new AmazonSimpleNotificationServiceClient(credentials, config); } public AmazonSQSClient CreateSqsClient() { - var config = new AmazonSQSConfig - { - RegionEndpoint = _region - }; + var config = new AmazonSQSConfig { RegionEndpoint = region }; - if (_clientConfigAction != null) - { - _clientConfigAction(config); - } + clientConfigAction?.Invoke(config); - return new AmazonSQSClient(_credentials, config); + return new AmazonSQSClient(credentials, config); } public AmazonSecurityTokenServiceClient CreateStsClient() { - var config = new AmazonSecurityTokenServiceConfig - { - RegionEndpoint = _region - }; + var config = new AmazonSecurityTokenServiceConfig { RegionEndpoint = region }; - if (_clientConfigAction != null) - { - _clientConfigAction(config); - } + clientConfigAction?.Invoke(config); - return new AmazonSecurityTokenServiceClient(_credentials, config); + return new AmazonSecurityTokenServiceClient(credentials, config); } } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs index 10a6e6c67b..1328aa6948 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs @@ -119,14 +119,12 @@ private IValidateTopic GetTopicValidationStrategy(TopicFindBy findTopicBy, SnsSq switch (findTopicBy) { case TopicFindBy.Arn: - return new ValidateTopicByArn(AwsConnection.Credentials, AwsConnection.Region, - AwsConnection.ClientConfigAction); + return new ValidateTopicByArn(_awsClientFactory.CreateSnsClient()); case TopicFindBy.Convention: return new ValidateTopicByArnConvention(AwsConnection.Credentials, AwsConnection.Region, AwsConnection.ClientConfigAction, type); case TopicFindBy.Name: - return new ValidateTopicByName(AwsConnection.Credentials, AwsConnection.Region, - AwsConnection.ClientConfigAction, type); + return new ValidateTopicByName(_awsClientFactory.CreateSnsClient(), type); default: throw new ConfigurationException("Unknown TopicFindBy used to determine how to read RoutingKey"); } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs index 86c74d4838..d7df8293c2 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2022 Ian Cooper @@ -19,6 +20,7 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + #endregion using System; @@ -61,12 +63,7 @@ public ChannelFactory(AWSMessagingGatewayConnection awsConnection) _messageConsumerFactory = new SqsMessageConsumerFactory(awsConnection); _retryPolicy = Policy .Handle() - .WaitAndRetryAsync(new[] - { - TimeSpan.FromSeconds(1), - TimeSpan.FromSeconds(5), - TimeSpan.FromSeconds(10) - }); + .WaitAndRetryAsync(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) }); } /// @@ -76,8 +73,13 @@ public ChannelFactory(AWSMessagingGatewayConnection awsConnection) /// An SqsSubscription, the subscription parameter to create the channel with. /// An instance of . /// Thrown when the subscription is not an SqsSubscription. - public IAmAChannelSync CreateSyncChannel(Subscription subscription) => BrighterSynchronizationHelper.Run(async () => await CreateSyncChannelAsync(subscription)); - + public IAmAChannelSync CreateSyncChannel(Subscription subscription) + { + // TODO: Uncomment when the BrighterSynchronizationHelper is fixed, today the code isn't working because it's stuck is wired infinity loop + // return BrighterSynchronizationHelper.Run(async () => await CreateSyncChannelAsync(subscription)); + return CreateSyncChannelAsync(subscription).GetAwaiter().GetResult(); + } + /// /// Creates the input channel. /// @@ -87,7 +89,12 @@ public ChannelFactory(AWSMessagingGatewayConnection awsConnection) /// An SqsSubscription, the subscription parameter to create the channel with. /// An instance of . /// Thrown when the subscription is not an SqsSubscription. - public IAmAChannelAsync CreateAsyncChannel(Subscription subscription) => BrighterSynchronizationHelper.Run(async () => await CreateAsyncChannelAsync(subscription)); + public IAmAChannelAsync CreateAsyncChannel(Subscription subscription) + { + // TODO: Uncomment when the BrighterSynchronizationHelper is fixed, today the code isn't working because it's stuck is wired infinity loop + // return BrighterSynchronizationHelper.Run(async () => await CreateAsyncChannelAsync(subscription)); + return CreateAsyncChannelAsync(subscription).GetAwaiter().GetResult(); + } /// /// Creates the input channel. @@ -96,18 +103,21 @@ public ChannelFactory(AWSMessagingGatewayConnection awsConnection) /// Cancels the creation operation /// An instance of . /// Thrown when the subscription is not an SqsSubscription. - public async Task CreateAsyncChannelAsync(Subscription subscription, CancellationToken ct = default) + public async Task CreateAsyncChannelAsync(Subscription subscription, + CancellationToken ct = default) { var channel = await _retryPolicy.ExecuteAsync(async () => { SqsSubscription? sqsSubscription = subscription as SqsSubscription; - _subscription = sqsSubscription ?? throw new ConfigurationException("We expect an SqsSubscription or SqsSubscription as a parameter"); + _subscription = sqsSubscription ?? + throw new ConfigurationException( + "We expect an SqsSubscription or SqsSubscription as a parameter"); - await EnsureTopicAsync(_subscription.RoutingKey, + await EnsureTopicAsync(_subscription.RoutingKey, _subscription.FindTopicBy, _subscription.SnsAttributes, _subscription.MakeChannels, - _subscription.SqsType, + _subscription.SqsType, _subscription.ContentBasedDeduplication, ct); await EnsureQueueAsync(); @@ -122,7 +132,7 @@ await EnsureTopicAsync(_subscription.RoutingKey, return channel; } - + /// /// Deletes the queue. /// @@ -131,8 +141,9 @@ public async Task DeleteQueueAsync() if (_subscription?.ChannelName is null) return; - using var sqsClient = new AmazonSQSClient(AwsConnection.Credentials, AwsConnection.Region); - (bool exists, string? queueUrl) queueExists = await QueueExistsAsync(sqsClient, _subscription.ChannelName.ToValidSQSQueueName()); + using var sqsClient = new AWSClientFactory(AwsConnection).CreateSqsClient(); + (bool exists, string? queueUrl) queueExists = + await QueueExistsAsync(sqsClient, _subscription.ChannelName.ToValidSQSQueueName()); if (queueExists.exists && queueExists.queueUrl != null) { @@ -156,7 +167,7 @@ public async Task DeleteTopicAsync() { if (_subscription == null) return; - + if (ChannelTopicArn == null) return; @@ -175,16 +186,18 @@ public async Task DeleteTopicAsync() } } } - + private async Task CreateSyncChannelAsync(Subscription subscription) { var channel = await _retryPolicy.ExecuteAsync(async () => { SqsSubscription? sqsSubscription = subscription as SqsSubscription; - _subscription = sqsSubscription ?? throw new ConfigurationException("We expect an SqsSubscription or SqsSubscription as a parameter"); + _subscription = sqsSubscription ?? + throw new ConfigurationException( + "We expect an SqsSubscription or SqsSubscription as a parameter"); - await EnsureTopicAsync(_subscription.RoutingKey, - _subscription.FindTopicBy, + await EnsureTopicAsync(_subscription.RoutingKey, + _subscription.FindTopicBy, _subscription.SnsAttributes, _subscription.MakeChannels, _subscription.SqsType, @@ -201,15 +214,15 @@ await EnsureTopicAsync(_subscription.RoutingKey, return channel; } - + private async Task EnsureQueueAsync() { if (_subscription is null) throw new InvalidOperationException("ChannelFactory: Subscription cannot be null"); - + if (_subscription.MakeChannels == OnMissingChannel.Assume) return; - + using var sqsClient = new AWSClientFactory(AwsConnection).CreateSqsClient(); var queueName = _subscription.ChannelName.ToValidSQSQueueName(); var topicName = _subscription.RoutingKey.ToValidSNSTopicName(); @@ -229,13 +242,15 @@ private async Task EnsureQueueAsync() else if (_subscription.MakeChannels == OnMissingChannel.Validate) { var message = $"Queue does not exist: {queueName} for {topicName} on {AwsConnection.Region}"; - s_logger.LogDebug("Queue does not exist: {ChannelName} for {Topic} on {Region}", queueName, topicName, AwsConnection.Region); + s_logger.LogDebug("Queue does not exist: {ChannelName} for {Topic} on {Region}", queueName, topicName, + AwsConnection.Region); throw new QueueDoesNotExistException(message); } } else { - s_logger.LogDebug("Queue exists: {ChannelName} subscribed to {Topic} on {Region}", queueName, topicName, AwsConnection.Region); + s_logger.LogDebug("Queue exists: {ChannelName} subscribed to {Topic} on {Region}", queueName, topicName, + AwsConnection.Region); } } @@ -243,16 +258,21 @@ private async Task CreateQueueAsync(AmazonSQSClient sqsClient) { if (_subscription is null) throw new InvalidOperationException("ChannelFactory: Subscription cannot be null"); - - s_logger.LogDebug("Queue does not exist, creating queue: {ChannelName} subscribed to {Topic} on {Region}", _subscription.ChannelName.Value, _subscription.RoutingKey.Value, AwsConnection.Region); + + s_logger.LogDebug("Queue does not exist, creating queue: {ChannelName} subscribed to {Topic} on {Region}", + _subscription.ChannelName.Value, _subscription.RoutingKey.Value, AwsConnection.Region); _queueUrl = null; try { var attributes = new Dictionary(); if (_subscription.RedrivePolicy != null && _dlqARN != null) { - var policy = new { maxReceiveCount = _subscription.RedrivePolicy.MaxReceiveCount, deadLetterTargetArn = _dlqARN }; - attributes.Add(QueueAttributeName.RedrivePolicy, JsonSerializer.Serialize(policy, JsonSerialisationOptions.Options)); + var policy = new + { + maxReceiveCount = _subscription.RedrivePolicy.MaxReceiveCount, deadLetterTargetArn = _dlqARN + }; + attributes.Add(QueueAttributeName.RedrivePolicy, + JsonSerializer.Serialize(policy, JsonSerialisationOptions.Options)); } attributes.Add(QueueAttributeName.DelaySeconds, _subscription.DelaySeconds.ToString()); @@ -260,7 +280,7 @@ private async Task CreateQueueAsync(AmazonSQSClient sqsClient) if (_subscription.IAMPolicy != null) attributes.Add(QueueAttributeName.Policy, _subscription.IAMPolicy); attributes.Add(QueueAttributeName.ReceiveMessageWaitTimeSeconds, _subscription.TimeOut.Seconds.ToString()); attributes.Add(QueueAttributeName.VisibilityTimeout, _subscription.LockTimeout.ToString()); - + var tags = new Dictionary { { "Source", "Brighter" } }; if (_subscription.Tags != null) { @@ -269,7 +289,7 @@ private async Task CreateQueueAsync(AmazonSQSClient sqsClient) tags.Add(tag.Key, tag.Value); } } - + var queueName = _subscription.ChannelName.Value; if (_subscription.SqsType == SnsSqsType.Fifo) { @@ -296,13 +316,9 @@ private async Task CreateQueueAsync(AmazonSQSClient sqsClient) }); } } - - var request = new CreateQueueRequest(queueName) - { - Attributes = attributes, - Tags = tags - }; + + var request = new CreateQueueRequest(queueName) { Attributes = attributes, Tags = tags }; var response = await sqsClient.CreateQueueAsync(request); _queueUrl = response.QueueUrl; @@ -314,26 +330,35 @@ private async Task CreateQueueAsync(AmazonSQSClient sqsClient) } else { - throw new InvalidOperationException($"Could not create queue: {_subscription.ChannelName.Value} subscribed to {ChannelTopicArn} on {AwsConnection.Region}"); + throw new InvalidOperationException( + $"Could not create queue: {_subscription.ChannelName.Value} subscribed to {ChannelTopicArn} on {AwsConnection.Region}"); } } catch (QueueDeletedRecentlyException ex) { - var error = $"Could not create queue {_subscription.ChannelName.Value} because {ex.Message} waiting 60s to retry"; - s_logger.LogError(ex, "Could not create queue {ChannelName} because {ErrorMessage} waiting 60s to retry", _subscription.ChannelName.Value, ex.Message); + var error = + $"Could not create queue {_subscription.ChannelName.Value} because {ex.Message} waiting 60s to retry"; + s_logger.LogError(ex, "Could not create queue {ChannelName} because {ErrorMessage} waiting 60s to retry", + _subscription.ChannelName.Value, ex.Message); Thread.Sleep(TimeSpan.FromSeconds(30)); throw new ChannelFailureException(error, ex); } catch (AmazonSQSException ex) { - var error = $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {AwsConnection.Region.DisplayName} because {ex.Message}"; - s_logger.LogError(ex, "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", _queueUrl, _subscription.RoutingKey.Value, AwsConnection.Region.DisplayName, ex.Message); + var error = + $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {AwsConnection.Region.DisplayName} because {ex.Message}"; + s_logger.LogError(ex, + "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", + _queueUrl, _subscription.RoutingKey.Value, AwsConnection.Region.DisplayName, ex.Message); throw new InvalidOperationException(error, ex); } catch (HttpErrorResponseException ex) { - var error = $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {AwsConnection.Region.DisplayName} because {ex.Message}"; - s_logger.LogError(ex, "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", _queueUrl, _subscription.RoutingKey.Value, AwsConnection.Region.DisplayName, ex.Message); + var error = + $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {AwsConnection.Region.DisplayName} because {ex.Message}"; + s_logger.LogError(ex, + "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", + _queueUrl, _subscription.RoutingKey.Value, AwsConnection.Region.DisplayName, ex.Message); throw new InvalidOperationException(error, ex); } } @@ -342,10 +367,10 @@ private async Task CreateDLQAsync(AmazonSQSClient sqsClient) { if (_subscription is null) throw new InvalidOperationException("ChannelFactory: Subscription cannot be null"); - + if (_subscription.RedrivePolicy == null) throw new InvalidOperationException("ChannelFactory: RedrivePolicy cannot be null when creating a DLQ"); - + try { var request = new CreateQueueRequest(_subscription.RedrivePolicy.DeadlLetterQueueName.Value); @@ -356,41 +381,51 @@ private async Task CreateDLQAsync(AmazonSQSClient sqsClient) { var attributesRequest = new GetQueueAttributesRequest { - QueueUrl = queueUrl, - AttributeNames = ["QueueArn"] + QueueUrl = queueUrl, AttributeNames = ["QueueArn"] }; var attributesResponse = await sqsClient.GetQueueAttributesAsync(attributesRequest); if (attributesResponse.HttpStatusCode != HttpStatusCode.OK) - throw new InvalidOperationException($"Could not find ARN of DLQ, status: {attributesResponse.HttpStatusCode}"); + throw new InvalidOperationException( + $"Could not find ARN of DLQ, status: {attributesResponse.HttpStatusCode}"); _dlqARN = attributesResponse.QueueARN; } else - throw new InvalidOperationException($"Could not find create DLQ, status: {createDeadLetterQueueResponse.HttpStatusCode}"); + throw new InvalidOperationException( + $"Could not find create DLQ, status: {createDeadLetterQueueResponse.HttpStatusCode}"); } catch (QueueDeletedRecentlyException ex) { - var error = $"Could not create queue {_subscription.ChannelName.Value} because {ex.Message} waiting 60s to retry"; - s_logger.LogError(ex, "Could not create queue {ChannelName} because {ErrorMessage} waiting 60s to retry", _subscription.ChannelName.Value, ex.Message); + var error = + $"Could not create queue {_subscription.ChannelName.Value} because {ex.Message} waiting 60s to retry"; + s_logger.LogError(ex, "Could not create queue {ChannelName} because {ErrorMessage} waiting 60s to retry", + _subscription.ChannelName.Value, ex.Message); Thread.Sleep(TimeSpan.FromSeconds(30)); throw new ChannelFailureException(error, ex); } catch (AmazonSQSException ex) { - var error = $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {AwsConnection.Region.DisplayName} because {ex.Message}"; - s_logger.LogError(ex, "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", _queueUrl, _subscription.RoutingKey.Value, AwsConnection.Region.DisplayName, ex.Message); + var error = + $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {AwsConnection.Region.DisplayName} because {ex.Message}"; + s_logger.LogError(ex, + "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", + _queueUrl, _subscription.RoutingKey.Value, AwsConnection.Region.DisplayName, ex.Message); throw new InvalidOperationException(error, ex); } catch (HttpErrorResponseException ex) { - var error = $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {AwsConnection.Region.DisplayName} because {ex.Message}"; - s_logger.LogError(ex, "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", _queueUrl, _subscription.RoutingKey.Value, AwsConnection.Region.DisplayName, ex.Message); + var error = + $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {AwsConnection.Region.DisplayName} because {ex.Message}"; + s_logger.LogError(ex, + "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", + _queueUrl, _subscription.RoutingKey.Value, AwsConnection.Region.DisplayName, ex.Message); throw new InvalidOperationException(error, ex); } } - private async Task CheckSubscriptionAsync(OnMissingChannel makeSubscriptions, AmazonSQSClient sqsClient, AmazonSimpleNotificationServiceClient snsClient) + private async Task CheckSubscriptionAsync(OnMissingChannel makeSubscriptions, AmazonSQSClient sqsClient, + AmazonSimpleNotificationServiceClient snsClient) { if (makeSubscriptions == OnMissingChannel.Assume) return; @@ -399,7 +434,8 @@ private async Task CheckSubscriptionAsync(OnMissingChannel makeSubscriptions, Am { if (makeSubscriptions == OnMissingChannel.Validate) { - throw new BrokerUnreachableException($"Subscription validation error: could not find subscription for {_queueUrl}"); + throw new BrokerUnreachableException( + $"Subscription validation error: could not find subscription for {_queueUrl}"); } else if (makeSubscriptions == OnMissingChannel.Create) { @@ -414,7 +450,8 @@ private async Task SubscribeToTopicAsync(AmazonSQSClient sqsClient, AmazonSimple if (!string.IsNullOrEmpty(arn)) { var response = await snsClient.SetSubscriptionAttributesAsync( - new SetSubscriptionAttributesRequest(arn, "RawMessageDelivery", _subscription?.RawMessageDelivery.ToString()) + new SetSubscriptionAttributesRequest(arn, "RawMessageDelivery", + _subscription?.RawMessageDelivery.ToString()) ); if (response.HttpStatusCode != HttpStatusCode.OK) { @@ -423,7 +460,8 @@ private async Task SubscribeToTopicAsync(AmazonSQSClient sqsClient, AmazonSimple } else { - throw new InvalidOperationException($"Could not subscribe to topic: {ChannelTopicArn} from queue: {_queueUrl} in region {AwsConnection.Region}"); + throw new InvalidOperationException( + $"Could not subscribe to topic: {ChannelTopicArn} from queue: {_queueUrl} in region {AwsConnection.Region}"); } } @@ -431,7 +469,7 @@ private async Task SubscribeToTopicAsync(AmazonSQSClient sqsClient, AmazonSimple { if (string.IsNullOrEmpty(channelName)) return (false, null); - + bool exists = false; string? queueUrl = null; try @@ -464,7 +502,8 @@ private async Task SubscribeToTopicAsync(AmazonSQSClient sqsClient, AmazonSimple return (exists, queueUrl); } - private async Task SubscriptionExistsAsync(AmazonSQSClient sqsClient, AmazonSimpleNotificationServiceClient snsClient) + private async Task SubscriptionExistsAsync(AmazonSQSClient sqsClient, + AmazonSimpleNotificationServiceClient snsClient) { string? queueArn = await GetQueueArnForChannelAsync(sqsClient); @@ -475,7 +514,8 @@ private async Task SubscriptionExistsAsync(AmazonSQSClient sqsClient, Amaz ListSubscriptionsByTopicResponse response; do { - response = await snsClient.ListSubscriptionsByTopicAsync(new ListSubscriptionsByTopicRequest { TopicArn = ChannelTopicArn }); + response = await snsClient.ListSubscriptionsByTopicAsync( + new ListSubscriptionsByTopicRequest { TopicArn = ChannelTopicArn }); exists = response.Subscriptions.Any(sub => (sub.Protocol.ToLower() == "sqs") && (sub.Endpoint == queueArn)); } while (!exists && response.NextToken != null); @@ -512,13 +552,16 @@ private async Task UnsubscribeFromTopicAsync(AmazonSimpleNotificationServiceClie ListSubscriptionsByTopicResponse response; do { - response = await snsClient.ListSubscriptionsByTopicAsync(new ListSubscriptionsByTopicRequest { TopicArn = ChannelTopicArn }); + response = await snsClient.ListSubscriptionsByTopicAsync( + new ListSubscriptionsByTopicRequest { TopicArn = ChannelTopicArn }); foreach (var sub in response.Subscriptions) { - var unsubscribe = await snsClient.UnsubscribeAsync(new UnsubscribeRequest { SubscriptionArn = sub.SubscriptionArn }); + var unsubscribe = + await snsClient.UnsubscribeAsync(new UnsubscribeRequest { SubscriptionArn = sub.SubscriptionArn }); if (unsubscribe.HttpStatusCode != HttpStatusCode.OK) { - s_logger.LogError("Error unsubscribing from {TopicResourceName} for sub {ChannelResourceName}", ChannelTopicArn, sub.SubscriptionArn); + s_logger.LogError("Error unsubscribing from {TopicResourceName} for sub {ChannelResourceName}", + ChannelTopicArn, sub.SubscriptionArn); } } } while (response.NextToken != null); diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs new file mode 100644 index 0000000000..93d22e3d66 --- /dev/null +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs @@ -0,0 +1,177 @@ +#region Licence + +/* The MIT License (MIT) +Copyright © 2022 Ian Cooper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion + +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Paramore.Brighter.Tasks; + +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +/// +/// Class SnsMessageProducer. +/// +public class SnsMessageProducer : AWSMessagingGateway, IAmAMessageProducerSync, IAmAMessageProducerAsync +{ + private readonly SnsPublication _publication; + private readonly AWSClientFactory _clientFactory; + + /// + /// The publication configuration for this producer + /// + public Publication Publication => _publication; + + /// + /// The OTel Span we are writing Producer events too + /// + public Activity? Span { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// How do we connect to AWS in order to manage middleware + /// Configuration of a producer + public SnsMessageProducer(AWSMessagingGatewayConnection connection, SnsPublication publication) + : base(connection) + { + _publication = publication; + _clientFactory = new AWSClientFactory(connection); + + if (publication.TopicArn != null) + { + ChannelTopicArn = publication.TopicArn; + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() { } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public ValueTask DisposeAsync() => new(); + + public bool ConfirmTopicExists(string? topic = null) => + BrighterSynchronizationHelper.Run(async () => await ConfirmTopicExistsAsync(topic)); + + public async Task ConfirmTopicExistsAsync(string? topic = null, + CancellationToken cancellationToken = default) + { + //Only do this on first send for a topic for efficiency; won't auto-recreate when goes missing at runtime as a result + if (!string.IsNullOrEmpty(ChannelTopicArn)) return !string.IsNullOrEmpty(ChannelTopicArn); + + RoutingKey? routingKey = null; + if (topic is null && _publication.Topic is not null) + routingKey = _publication.Topic; + else if (topic is not null) + routingKey = new RoutingKey(topic); + + if (routingKey is null) + throw new ConfigurationException("No topic specified for producer"); + + await EnsureTopicAsync( + routingKey, + _publication.FindTopicBy, + _publication.SnsAttributes, + _publication.MakeChannels, + _publication.SnsType, + _publication.Deduplication, + cancellationToken); + + return !string.IsNullOrEmpty(ChannelTopicArn); + } + + /// + /// Sends the specified message. + /// + /// The message. + /// Allows cancellation of the Send operation + public async Task SendAsync(Message message, CancellationToken cancellationToken = default) + { + s_logger.LogDebug( + "SQSMessageProducer: Publishing message with topic {Topic} and id {Id} and message: {Request}", + message.Header.Topic, message.Id, message.Body); + + await ConfirmTopicExistsAsync(message.Header.Topic, cancellationToken); + + if (string.IsNullOrEmpty(ChannelTopicArn)) + throw new InvalidOperationException( + $"Failed to publish message with topic {message.Header.Topic} and id {message.Id} and message: {message.Body} as the topic does not exist"); + + using var client = _clientFactory.CreateSnsClient(); + var publisher = new SqsMessagePublisher(ChannelTopicArn!, client, _publication.SnsType, _publication.Deduplication); + var messageId = await publisher.PublishAsync(message); + + if (messageId == null) + throw new InvalidOperationException( + $"Failed to publish message with topic {message.Header.Topic} and id {message.Id} and message: {message.Body}"); + + s_logger.LogDebug( + "SQSMessageProducer: Published message with topic {Topic}, Brighter messageId {MessageId} and SNS messageId {SNSMessageId}", + message.Header.Topic, message.Id, messageId); + } + + /// + /// Sends the specified message. + /// Sync over Async + /// + /// The message. + public void Send(Message message) + { + // TODO: Uncomment when the BrighterSynchronizationHelper is fixed, today the code isn't working because it's stuck is wired infinity loop + // BrighterSynchronizationHelper.Run(async () => await SendWithDelayAsync(message, delay)); + SendAsync(message).GetAwaiter().GetResult(); + } + + /// + /// Sends the specified message, with a delay. + /// + /// The message. + /// The sending delay + /// Task. + public void SendWithDelay(Message message, TimeSpan? delay = null) + { + // SNS doesn't support publish with delay + Send(message); + } + + /// + /// Sends the specified message, with a delay + /// + /// The message + /// The sending delay + /// Cancels the send operation + /// + public async Task SendWithDelayAsync(Message message, TimeSpan? delay, + CancellationToken cancellationToken = default) + { + // SNS doesn't support publish with delay + await SendAsync(message, cancellationToken); + } +} diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducerFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducerFactory.cs index 65abc2ccd6..b172c86165 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducerFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducerFactory.cs @@ -57,7 +57,7 @@ public Dictionary Create() if (p.Topic is null) throw new ConfigurationException($"Missing topic on Publication"); - var producer = new SqsMessageProducer(_connection, p); + var producer = new SnsMessageProducer(_connection, p); if (producer.ConfirmTopicExists()) producers[p.Topic] = producer; else @@ -75,7 +75,7 @@ public async Task> CreateAsync() if (p.Topic is null) throw new ConfigurationException($"Missing topic on Publication"); - var producer = new SqsMessageProducer(_connection, p); + var producer = new SnsMessageProducer(_connection, p); if (await producer.ConfirmTopicExistsAsync()) producers[p.Topic] = producer; else diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageConsumer.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageConsumer.cs index 8ac29a1162..bd1dcef71b 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageConsumer.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageConsumer.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2024 Ian Cooper @@ -19,6 +20,7 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + #endregion using System; @@ -39,7 +41,7 @@ namespace Paramore.Brighter.MessagingGateway.AWSSQS /// public class SqsMessageConsumer : IAmAMessageConsumerSync, IAmAMessageConsumerAsync { - private static readonly ILogger s_logger= ApplicationLogging.CreateLogger(); + private static readonly ILogger s_logger = ApplicationLogging.CreateLogger(); private readonly AWSClientFactory _clientFactory; private readonly string _queueName; @@ -64,9 +66,9 @@ public SqsMessageConsumer(AWSMessagingGatewayConnection awsConnection, { if (string.IsNullOrEmpty(queueName)) throw new ConfigurationException("QueueName is mandatory"); - + _clientFactory = new AWSClientFactory(awsConnection); - _queueName = queueName!; + _queueName = queueName!; _batchSize = batchSize; _hasDlq = hasDLQ; _rawMessageDelivery = rawMessageDelivery; @@ -77,14 +79,20 @@ public SqsMessageConsumer(AWSMessagingGatewayConnection awsConnection, /// Sync over Async /// /// The message. - public void Acknowledge(Message message) => BrighterSynchronizationHelper.Run(async () => await AcknowledgeAsync(message)); + public void Acknowledge(Message message) + { + // TODO: Uncomment when the BrighterSynchronizationHelper is fixed, today the code isn't working because it's stuck is wired infinity loop + // BrighterSynchronizationHelper.Run(async () => await AcknowledgeAsync(message)); + AcknowledgeAsync(message).GetAwaiter().GetResult(); + } /// /// Acknowledges the specified message. /// /// The message. /// Cancels the ackowledge operation - public async Task AcknowledgeAsync(Message message, CancellationToken cancellationToken = default(CancellationToken)) + public async Task AcknowledgeAsync(Message message, + CancellationToken cancellationToken = default(CancellationToken)) { if (!message.Header.Bag.TryGetValue("ReceiptHandle", out object? value)) return; @@ -95,14 +103,19 @@ public SqsMessageConsumer(AWSMessagingGatewayConnection awsConnection, { using var client = _clientFactory.CreateSqsClient(); var urlResponse = await client.GetQueueUrlAsync(_queueName, cancellationToken); - await client.DeleteMessageAsync(new DeleteMessageRequest(urlResponse.QueueUrl, receiptHandle), cancellationToken); + await client.DeleteMessageAsync(new DeleteMessageRequest(urlResponse.QueueUrl, receiptHandle), + cancellationToken); - s_logger.LogInformation("SqsMessageConsumer: Deleted the message {Id} with receipt handle {ReceiptHandle} on the queue {URL}", message.Id, receiptHandle, + s_logger.LogInformation( + "SqsMessageConsumer: Deleted the message {Id} with receipt handle {ReceiptHandle} on the queue {URL}", + message.Id, receiptHandle, urlResponse.QueueUrl); } catch (Exception exception) { - s_logger.LogError(exception, "SqsMessageConsumer: Error during deleting the message {Id} with receipt handle {ReceiptHandle} on the queue {ChannelName}", message.Id, receiptHandle, _queueName); + s_logger.LogError(exception, + "SqsMessageConsumer: Error during deleting the message {Id} with receipt handle {ReceiptHandle} on the queue {ChannelName}", + message.Id, receiptHandle, _queueName); throw; } } @@ -112,7 +125,8 @@ public SqsMessageConsumer(AWSMessagingGatewayConnection awsConnection, /// Sync over async /// /// The message. - public void Reject(Message message) => BrighterSynchronizationHelper.Run(async () => await RejectAsync(message)); + public void Reject(Message message) => + BrighterSynchronizationHelper.Run(async () => await RejectAsync(message)); /// /// Rejects the specified message. @@ -138,9 +152,9 @@ public SqsMessageConsumer(AWSMessagingGatewayConnection awsConnection, if (_hasDlq) { await client.ChangeMessageVisibilityAsync( - new ChangeMessageVisibilityRequest(urlResponse.QueueUrl, receiptHandle, 0), + new ChangeMessageVisibilityRequest(urlResponse.QueueUrl, receiptHandle, 0), cancellationToken - ); + ); } else { @@ -149,7 +163,9 @@ await client.ChangeMessageVisibilityAsync( } catch (Exception exception) { - s_logger.LogError(exception, "SqsMessageConsumer: Error during rejecting the message {Id} with receipt handle {ReceiptHandle} on the queue {ChannelName}", message.Id, receiptHandle, _queueName); + s_logger.LogError(exception, + "SqsMessageConsumer: Error during rejecting the message {Id} with receipt handle {ReceiptHandle} on the queue {ChannelName}", + message.Id, receiptHandle, _queueName); throw; } } @@ -159,7 +175,7 @@ await client.ChangeMessageVisibilityAsync( /// Sync over Async /// public void Purge() => BrighterSynchronizationHelper.Run(async () => await PurgeAsync()); - + /// /// Purges the specified queue name. /// @@ -181,20 +197,26 @@ await client.ChangeMessageVisibilityAsync( throw; } } - - /// + + /// /// Receives the specified queue name. /// Sync over async /// /// The timeout. AWS uses whole seconds. Anything greater than 0 uses long-polling. - public Message[] Receive(TimeSpan? timeOut = null) => BrighterSynchronizationHelper.Run(async () => await ReceiveAsync(timeOut)); + public Message[] Receive(TimeSpan? timeOut = null) + { + // TODO: Uncomment when the BrighterSynchronizationHelper is fixed, today the code isn't working because it's stuck is wired infinity loop + // return BrighterSynchronizationHelper.Run(async () => await ReceiveAsync(timeOut)); + return ReceiveAsync(timeOut).GetAwaiter().GetResult(); + } /// /// Receives the specified queue name. /// /// The timeout. AWS uses whole seconds. Anything greater than 0 uses long-polling. /// Cancel the receive operation - public async Task ReceiveAsync(TimeSpan? timeOut = null, CancellationToken cancellationToken = default(CancellationToken)) + public async Task ReceiveAsync(TimeSpan? timeOut = null, + CancellationToken cancellationToken = default(CancellationToken)) { AmazonSQSClient? client = null; Amazon.SQS.Model.Message[] sqsMessages; @@ -204,13 +226,14 @@ await client.ChangeMessageVisibilityAsync( var urlResponse = await client.GetQueueUrlAsync(_queueName, cancellationToken); timeOut ??= TimeSpan.Zero; - s_logger.LogDebug("SqsMessageConsumer: Preparing to retrieve next message from queue {URL}", urlResponse.QueueUrl); + s_logger.LogDebug("SqsMessageConsumer: Preparing to retrieve next message from queue {URL}", + urlResponse.QueueUrl); var request = new ReceiveMessageRequest(urlResponse.QueueUrl) { MaxNumberOfMessages = _batchSize, WaitTimeSeconds = timeOut.Value.Seconds, - MessageAttributeNames = new List {"All"}, + MessageAttributeNames = new List { "All" }, }; var receiveResponse = await client.ReceiveMessageAsync(request, cancellationToken); @@ -229,7 +252,8 @@ await client.ChangeMessageVisibilityAsync( } catch (Exception e) { - s_logger.LogError(e, "SqsMessageConsumer: There was an error listening to queue {ChannelName} ", _queueName); + s_logger.LogError(e, "SqsMessageConsumer: There was an error listening to queue {ChannelName} ", + _queueName); throw; } finally @@ -239,23 +263,26 @@ await client.ChangeMessageVisibilityAsync( if (sqsMessages.Length == 0) { - return new[] {_noopMessage}; + return new[] { _noopMessage }; } var messages = new Message[sqsMessages.Length]; for (int i = 0; i < sqsMessages.Length; i++) { var message = SqsMessageCreatorFactory.Create(_rawMessageDelivery).CreateMessage(sqsMessages[i]); - s_logger.LogInformation("SqsMessageConsumer: Received message from queue {ChannelName}, message: {1}{Request}", - _queueName, Environment.NewLine, JsonSerializer.Serialize(message, JsonSerialisationOptions.Options)); + s_logger.LogInformation( + "SqsMessageConsumer: Received message from queue {ChannelName}, message: {1}{Request}", + _queueName, Environment.NewLine, + JsonSerializer.Serialize(message, JsonSerialisationOptions.Options)); messages[i] = message; } return messages; - } + } - public bool Requeue(Message message, TimeSpan? delay = null) => BrighterSynchronizationHelper.Run(async () => await RequeueAsync(message, delay)); + public bool Requeue(Message message, TimeSpan? delay = null) => + BrighterSynchronizationHelper.Run(async () => await RequeueAsync(message, delay)); /// /// Re-queues the specified message. @@ -269,7 +296,7 @@ public async Task RequeueAsync(Message message, TimeSpan? delay = null, { if (!message.Header.Bag.TryGetValue("ReceiptHandle", out object? value)) return false; - + delay ??= TimeSpan.Zero; var receiptHandle = value.ToString(); @@ -282,9 +309,9 @@ public async Task RequeueAsync(Message message, TimeSpan? delay = null, { var urlResponse = await client.GetQueueUrlAsync(_queueName, cancellationToken); await client.ChangeMessageVisibilityAsync( - new ChangeMessageVisibilityRequest(urlResponse.QueueUrl, receiptHandle, delay.Value.Seconds), + new ChangeMessageVisibilityRequest(urlResponse.QueueUrl, receiptHandle, delay.Value.Seconds), cancellationToken - ); + ); } s_logger.LogInformation("SqsMessageConsumer: re-queued the message {Id}", message.Id); @@ -293,7 +320,9 @@ await client.ChangeMessageVisibilityAsync( } catch (Exception exception) { - s_logger.LogError(exception, "SqsMessageConsumer: Error during re-queueing the message {Id} with receipt handle {ReceiptHandle} on the queue {ChannelName}", message.Id, receiptHandle, _queueName); + s_logger.LogError(exception, + "SqsMessageConsumer: Error during re-queueing the message {Id} with receipt handle {ReceiptHandle} on the queue {ChannelName}", + message.Id, receiptHandle, _queueName); return false; } } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs deleted file mode 100644 index 8c57e95c50..0000000000 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs +++ /dev/null @@ -1,174 +0,0 @@ -#region Licence - -/* The MIT License (MIT) -Copyright © 2022 Ian Cooper - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the “Software”), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. */ - -#endregion - -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Paramore.Brighter.Tasks; - -namespace Paramore.Brighter.MessagingGateway.AWSSQS -{ - /// - /// Class SqsMessageProducer. - /// - public class SqsMessageProducer : AWSMessagingGateway, IAmAMessageProducerSync, IAmAMessageProducerAsync - { - private readonly SnsPublication _publication; - private readonly AWSClientFactory _clientFactory; - - /// - /// The publication configuration for this producer - /// - public Publication Publication { get { return _publication; } } - - /// - /// The OTel Span we are writing Producer events too - /// - public Activity? Span { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// How do we connect to AWS in order to manage middleware - /// Configuration of a producer - public SqsMessageProducer(AWSMessagingGatewayConnection connection, SnsPublication publication) - : base(connection) - { - _publication = publication; - _clientFactory = new AWSClientFactory(connection); - - if (publication.TopicArn != null) - ChannelTopicArn = publication.TopicArn; - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() { } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public ValueTask DisposeAsync() - { - return new ValueTask(Task.CompletedTask); - } - - public bool ConfirmTopicExists(string? topic = null) => - BrighterSynchronizationHelper.Run(async () => await ConfirmTopicExistsAsync(topic)); - - public async Task ConfirmTopicExistsAsync(string? topic = null, - CancellationToken cancellationToken = default) - { - //Only do this on first send for a topic for efficiency; won't auto-recreate when goes missing at runtime as a result - if (!string.IsNullOrEmpty(ChannelTopicArn)) return !string.IsNullOrEmpty(ChannelTopicArn); - - RoutingKey? routingKey = null; - if (topic is null && _publication.Topic is not null) - routingKey = _publication.Topic; - else if (topic is not null) - routingKey = new RoutingKey(topic); - - if (routingKey is null) - throw new ConfigurationException("No topic specified for producer"); - - await EnsureTopicAsync( - routingKey, - _publication.FindTopicBy, - _publication.SnsAttributes, - _publication.MakeChannels, - _publication.SnsType, - _publication.Deduplication, - cancellationToken); - - return !string.IsNullOrEmpty(ChannelTopicArn); - } - - /// - /// Sends the specified message. - /// - /// The message. - /// Allows cancellation of the Send operation - public async Task SendAsync(Message message, CancellationToken cancellationToken = default) - { - s_logger.LogDebug( - "SQSMessageProducer: Publishing message with topic {Topic} and id {Id} and message: {Request}", - message.Header.Topic, message.Id, message.Body); - - await ConfirmTopicExistsAsync(message.Header.Topic, cancellationToken); - - if (string.IsNullOrEmpty(ChannelTopicArn)) - throw new InvalidOperationException( - $"Failed to publish message with topic {message.Header.Topic} and id {message.Id} and message: {message.Body} as the topic does not exist"); - - using var client = _clientFactory.CreateSnsClient(); - var publisher = new SqsMessagePublisher(ChannelTopicArn!, client, _publication.SnsType, _publication.Deduplication); - var messageId = await publisher.PublishAsync(message); - - if (messageId == null) - throw new InvalidOperationException( - $"Failed to publish message with topic {message.Header.Topic} and id {message.Id} and message: {message.Body}"); - - s_logger.LogDebug( - "SQSMessageProducer: Published message with topic {Topic}, Brighter messageId {MessageId} and SNS messageId {SNSMessageId}", - message.Header.Topic, message.Id, messageId); - } - - /// - /// Sends the specified message. - /// Sync over Async - /// - /// The message. - public void Send(Message message) => BrighterSynchronizationHelper.Run(async () => await SendAsync(message)); - - /// - /// Sends the specified message, with a delay. - /// - /// The message. - /// The sending delay - /// Task. - public void SendWithDelay(Message message, TimeSpan? delay = null) - { - //TODO: Delay should set a visibility timeout - Send(message); - } - - /// - /// Sends the specified message, with a delay - /// - /// The message - /// The sending delay - /// Cancels the send operation - /// - public async Task SendWithDelayAsync(Message message, TimeSpan? delay, - CancellationToken cancellationToken = default) - { - //TODO: Delay should set the visibility timeout - await SendAsync(message, cancellationToken); - } - } -} diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByArnConvention.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByArnConvention.cs index fa1888d2df..e1943619b4 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByArnConvention.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByArnConvention.cs @@ -66,7 +66,7 @@ public ValidateTopicByArnConvention(AWSCredentials credentials, RegionEndpoint r public override async Task<(bool, string? TopicArn)> ValidateAsync(string topic, CancellationToken cancellationToken = default) { var topicArn = await GetArnFromTopic(topic); - return await base.ValidateAsync(topicArn); + return await base.ValidateAsync(topicArn, cancellationToken); } /// diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByName.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByName.cs index 59cb493da7..026576dfbe 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByName.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByName.cs @@ -44,6 +44,7 @@ internal class ValidateTopicByName : IValidateTopic /// The AWS credentials. /// The AWS region. /// An optional action to configure the client. + /// The SNS Type. public ValidateTopicByName(AWSCredentials credentials, RegionEndpoint region, Action? clientConfigAction = null, SnsSqsType type = SnsSqsType.Standard) { var clientFactory = new AWSClientFactory(credentials, region, clientConfigAction); @@ -55,9 +56,11 @@ public ValidateTopicByName(AWSCredentials credentials, RegionEndpoint region, Ac /// Initializes a new instance of the class. /// /// The SNS client. - public ValidateTopicByName(AmazonSimpleNotificationServiceClient snsClient) + /// The SNS Type. + public ValidateTopicByName(AmazonSimpleNotificationServiceClient snsClient, SnsSqsType type = SnsSqsType.Standard) { _snsClient = snsClient; + _type = type; } /// diff --git a/src/Paramore.Brighter.Tranformers.AWS/AWSS3Connection.cs b/src/Paramore.Brighter.Tranformers.AWS/AWSS3Connection.cs index e0c256a944..2d43c15bf4 100644 --- a/src/Paramore.Brighter.Tranformers.AWS/AWSS3Connection.cs +++ b/src/Paramore.Brighter.Tranformers.AWS/AWSS3Connection.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2022 Ian Cooper @@ -19,32 +20,35 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - + #endregion +#nullable enable +using System; using Amazon; using Amazon.Runtime; -namespace Paramore.Brighter.Tranformers.AWS +namespace Paramore.Brighter.Tranformers.AWS; + +/// +/// Used to create an AWS Client +/// +public class AWSS3Connection { /// - /// Used to create an AWS Client + /// Constructs a credentials instance /// - public class AWSS3Connection + /// A credentials object for an AWS service + /// The AWS region to connect to + /// The AWS client configuration. + public AWSS3Connection(AWSCredentials credentials, RegionEndpoint region, Action? clientConfig = null) { - /// - /// Constructs a credentials instance - /// - /// A credentials object for an AWS service - /// The AWS region to connect to - public AWSS3Connection(AWSCredentials credentials, RegionEndpoint region) - { - Credentials = credentials; - Region = region; - } - - public AWSCredentials Credentials { get; } - public RegionEndpoint Region { get; } - + Credentials = credentials; + Region = region; + ClientConfig = clientConfig; } + + public AWSCredentials Credentials { get; } + public RegionEndpoint Region { get; } + public Action? ClientConfig { get; } } diff --git a/src/Paramore.Brighter.Tranformers.AWS/S3LuggageOptions.cs b/src/Paramore.Brighter.Tranformers.AWS/S3LuggageOptions.cs index 904133dd13..f7e37ddfdb 100644 --- a/src/Paramore.Brighter.Tranformers.AWS/S3LuggageOptions.cs +++ b/src/Paramore.Brighter.Tranformers.AWS/S3LuggageOptions.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2022 Ian Cooper @@ -19,7 +20,7 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - + #endregion using System.Collections.Generic; @@ -53,7 +54,7 @@ public S3LuggageOptions(IHttpClientFactory httpClientFactory) TimeToAbortFailedUploads = 1; TimeToDeleteGoodUploads = 7; } - + /// /// How should we control access to the bucket used by the Luggage Store /// @@ -66,8 +67,13 @@ public AWSS3Connection Connection { set { - Client = new AmazonS3Client(value.Credentials, value.Region); - StsClient = new AmazonSecurityTokenServiceClient(value.Credentials, value.Region); + var s3Config = new AmazonS3Config { RegionEndpoint = value.Region }; + value.ClientConfig?.Invoke(s3Config); + Client = new AmazonS3Client(value.Credentials, s3Config); + + var stsConfig = new AmazonSecurityTokenServiceConfig { RegionEndpoint = value.Region }; + value.ClientConfig?.Invoke(stsConfig); + StsClient = new AmazonSecurityTokenServiceClient(value.Credentials, stsConfig); } } @@ -75,24 +81,24 @@ public AWSS3Connection Connection /// The name of the bucket, which will need to be unique within the AWS region /// public string BucketName { get; set; } - + /// /// The AWS region to create the bucket in /// public S3Region BucketRegion { get; set; } - + /// /// Get the AWS client created from the credentials passed into /// public IAmazonS3 Client { get; private set; } - + /// /// An HTTP client factory. We use this to grab an HTTP client, so that we can check if the bucket exists. /// Not required if you choose a of Assume Exists. /// We obtain this from the ServiceProvider when constructing the luggage store. so you do not need to set it /// public IHttpClientFactory HttpClientFactory { get; private set; } - + /// /// What Store Creation Option do you want: /// 1: Create @@ -100,22 +106,22 @@ public AWSS3Connection Connection /// 3: Assume it exists /// public S3LuggageStoreCreation StoreCreation { get; set; } - + /// /// The Security Token Service created from the credentials. Used to obtain the account id of the user with those credentials /// public IAmazonSecurityTokenService StsClient { get; private set; } - + /// /// Tags for the bucket. Defaults to a Creator tag of "Brighter Luggage Store" /// public List Tags { get; set; } - + /// /// How long to keep aborted uploads before deleting them in days /// public int TimeToAbortFailedUploads { get; set; } - + /// /// How long to keep good uploads in days, before deleting them /// diff --git a/tests/Paramore.Brighter.AWS.Tests/Helpers/AWSClientFactory.cs b/tests/Paramore.Brighter.AWS.Tests/Helpers/AWSClientFactory.cs new file mode 100644 index 0000000000..ac321abff4 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/Helpers/AWSClientFactory.cs @@ -0,0 +1,104 @@ +#region Licence + +/* The MIT License (MIT) +Copyright © 2022 Ian Cooper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion + +using System; +using Amazon; +using Amazon.Runtime; +using Amazon.S3; +using Amazon.SecurityToken; +using Amazon.SimpleNotificationService; +using Amazon.SQS; +using Paramore.Brighter.MessagingGateway.AWSSQS; + +namespace Paramore.Brighter.AWS.Tests.Helpers; + +internal class AWSClientFactory +{ + private readonly AWSCredentials _credentials; + private readonly RegionEndpoint _region; + private readonly Action? _clientConfigAction; + + public AWSClientFactory(AWSMessagingGatewayConnection connection) + { + _credentials = connection.Credentials; + _region = connection.Region; + _clientConfigAction = connection.ClientConfigAction; + } + + public AWSClientFactory(AWSCredentials credentials, RegionEndpoint region, Action? clientConfigAction) + { + _credentials = credentials; + _region = region; + _clientConfigAction = clientConfigAction; + } + + public AmazonSimpleNotificationServiceClient CreateSnsClient() + { + var config = new AmazonSimpleNotificationServiceConfig { RegionEndpoint = _region }; + + if (_clientConfigAction != null) + { + _clientConfigAction(config); + } + + return new AmazonSimpleNotificationServiceClient(_credentials, config); + } + + public AmazonSQSClient CreateSqsClient() + { + var config = new AmazonSQSConfig { RegionEndpoint = _region }; + + if (_clientConfigAction != null) + { + _clientConfigAction(config); + } + + return new AmazonSQSClient(_credentials, config); + } + + public AmazonSecurityTokenServiceClient CreateStsClient() + { + var config = new AmazonSecurityTokenServiceConfig { RegionEndpoint = _region }; + + if (_clientConfigAction != null) + { + _clientConfigAction(config); + } + + return new AmazonSecurityTokenServiceClient(_credentials, config); + } + + public AmazonS3Client CreateS3Client() + { + var config = new AmazonS3Config { RegionEndpoint = _region }; + + if (_clientConfigAction != null) + { + _clientConfigAction(config); + } + + return new AmazonS3Client(_credentials, config); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages.cs index 6c0e56a7a7..11cb902069 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages.cs @@ -14,7 +14,7 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Fragile", "CI")] public class SQSBufferedConsumerTests : IDisposable, IAsyncDisposable { - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly SqsMessageConsumer _consumer; private readonly string _topicName; private readonly ChannelFactory _channelFactory; @@ -44,7 +44,7 @@ public SQSBufferedConsumerTests() //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel //just for the tests, so create a new consumer from the properties _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), BufferSize); - _messageProducer = new SqsMessageProducer(awsConnection, + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages_async.cs index d75a11b6af..0b40e19a22 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages_async.cs @@ -14,7 +14,7 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Fragile", "CI")] public class SQSBufferedConsumerTestsAsync : IDisposable, IAsyncDisposable { - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly SqsMessageConsumer _consumer; private readonly string _topicName; private readonly ChannelFactory _channelFactory; @@ -44,7 +44,7 @@ public SQSBufferedConsumerTestsAsync() //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel //just for the tests, so create a new consumer from the properties _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), BufferSize); - _messageProducer = new SqsMessageProducer(awsConnection, + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs index 8fda695e4a..2d27a0b526 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs @@ -14,7 +14,7 @@ public class CustomisingAwsClientConfigTests : IDisposable, IAsyncDisposable { private readonly Message _message; private readonly IAmAChannelSync _channel; - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly InterceptingDelegatingHandler _publishHttpHandler = new(); @@ -56,7 +56,7 @@ public CustomisingAwsClientConfigTests() config.HttpClientFactory = new InterceptingHttpClientFactory(_publishHttpHandler); }); - _messageProducer = new SqsMessageProducer(publishAwsConnection, + _messageProducer = new SnsMessageProducer(publishAwsConnection, new SnsPublication { Topic = new RoutingKey(topicName), MakeChannels = OnMissingChannel.Create }); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config_async.cs index 0d47364099..2adb6f8905 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config_async.cs @@ -14,7 +14,7 @@ public class CustomisingAwsClientConfigTestsAsync : IDisposable, IAsyncDisposabl { private readonly Message _message; private readonly IAmAChannelAsync _channel; - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly InterceptingDelegatingHandler _publishHttpHandler = new(); @@ -56,7 +56,7 @@ public CustomisingAwsClientConfigTestsAsync() config.HttpClientFactory = new InterceptingHttpClientFactory(_publishHttpHandler); }); - _messageProducer = new SqsMessageProducer(publishAwsConnection, new SnsPublication{Topic = new RoutingKey(topicName), MakeChannels = OnMissingChannel.Create}); + _messageProducer = new SnsMessageProducer(publishAwsConnection, new SnsPublication{Topic = new RoutingKey(topicName), MakeChannels = OnMissingChannel.Create}); } [Fact] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs index 36bce40beb..c3d9d6aa08 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs @@ -16,21 +16,21 @@ public class AWSAssumeInfrastructureTests : IDisposable, IAsyncDisposable { private readonly Message _message; private readonly SqsMessageConsumer _consumer; - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; public AWSAssumeInfrastructureTests() { _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - SqsSubscription subscription = new( + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, @@ -55,13 +55,13 @@ public AWSAssumeInfrastructureTests() //Now change the subscription to validate, just check what we made subscription = new( name: new SubscriptionName(channelName), - channelName: channel.Name, + channelName: new ChannelName(channelName), routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Assume ); - _messageProducer = new SqsMessageProducer(awsConnection, + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Assume }); _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume_async.cs index 5362394df8..7dfa0c5a94 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume_async.cs @@ -15,7 +15,7 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; public class AWSAssumeInfrastructureTestsAsync : IDisposable, IAsyncDisposable { private readonly Message _message; private readonly SqsMessageConsumer _consumer; - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; @@ -60,7 +60,7 @@ public AWSAssumeInfrastructureTestsAsync() makeChannels: OnMissingChannel.Assume ); - _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Assume}); + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Assume}); _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify.cs index 9b6b938204..18d4e601b0 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify.cs @@ -16,7 +16,7 @@ public class AWSValidateInfrastructureTests : IDisposable, IAsyncDisposable { private readonly Message _message; private readonly IAmAMessageConsumerSync _consumer; - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; @@ -62,7 +62,7 @@ public AWSValidateInfrastructureTests() makeChannels: OnMissingChannel.Validate ); - _messageProducer = new SqsMessageProducer( + _messageProducer = new SnsMessageProducer( awsConnection, new SnsPublication { diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_arn.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_arn.cs index ee3711aef5..737575329d 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_arn.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_arn.cs @@ -13,24 +13,25 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; -[Trait("Category", "AWS")] +[Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class AWSValidateInfrastructureByArnTests : IDisposable, IAsyncDisposable -{ private readonly Message _message; +public class AWSValidateInfrastructureByArnTests : IDisposable, IAsyncDisposable +{ + private readonly Message _message; private readonly IAmAMessageConsumerSync _consumer; - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; public AWSValidateInfrastructureByArnTests() { - _myCommand = new MyCommand{Value = "Test"}; + _myCommand = new MyCommand { Value = "Test" }; string correlationId = Guid.NewGuid().ToString(); string replyTo = "http:\\queueUrl"; string contentType = "text\\plain"; var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey($"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); - + SqsSubscription subscription = new( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), @@ -38,24 +39,24 @@ public AWSValidateInfrastructureByArnTests() messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Create ); - + _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); var awsConnection = GatewayFactory.CreateFactory(credentials, region); - + //We need to do this manually in a test - will create the channel from subscriber parameters //This doesn't look that different from our create tests - this is because we create using the channel factory in //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) _channelFactory = new ChannelFactory(awsConnection); var channel = _channelFactory.CreateSyncChannel(subscription); - var topicArn = FindTopicArn(credentials, region, routingKey.Value); + var topicArn = FindTopicArn(awsConnection, routingKey.Value); var routingKeyArn = new RoutingKey(topicArn); //Now change the subscription to validate, just check what we made @@ -67,9 +68,9 @@ public AWSValidateInfrastructureByArnTests() messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Validate ); - - _messageProducer = new SqsMessageProducer( - awsConnection, + + _messageProducer = new SnsMessageProducer( + awsConnection, new SnsPublication { Topic = routingKey, @@ -88,9 +89,9 @@ public async Task When_infrastructure_exists_can_verify() _messageProducer.Send(_message); await Task.Delay(1000); - + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); - + //Assert var message = messages.First(); message.Id.Should().Be(_myCommand.Id); @@ -98,7 +99,7 @@ public async Task When_infrastructure_exists_can_verify() //clear the queue _consumer.Acknowledge(message); } - + public void Dispose() { //Clean up resources that we have created @@ -115,12 +116,11 @@ public async ValueTask DisposeAsync() await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); await _messageProducer.DisposeAsync(); } - - private string FindTopicArn(AWSCredentials credentials, RegionEndpoint region, string topicName) + + private static string FindTopicArn(AWSMessagingGatewayConnection connection, string topicName) { - var snsClient = new AmazonSimpleNotificationServiceClient(credentials, region); + using var snsClient = new AWSClientFactory(connection).CreateSnsClient(); var topicResponse = snsClient.FindTopicAsync(topicName).GetAwaiter().GetResult(); return topicResponse.TopicArn; } - } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_convention.cs index 2b0d408cb5..4b0a0da7d2 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_convention.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_convention.cs @@ -16,7 +16,7 @@ public class AWSValidateInfrastructureByConventionTests : IDisposable, IAsyncDis { private readonly Message _message; private readonly IAmAMessageConsumerSync _consumer; - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; @@ -62,7 +62,7 @@ public AWSValidateInfrastructureByConventionTests() makeChannels: OnMissingChannel.Validate ); - _messageProducer = new SqsMessageProducer( + _messageProducer = new SnsMessageProducer( awsConnection, new SnsPublication { FindTopicBy = TopicFindBy.Convention, MakeChannels = OnMissingChannel.Validate } ); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_async.cs index 8c7779fbb1..f3478785de 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_async.cs @@ -16,7 +16,7 @@ public class AWSValidateInfrastructureTestsAsync : IDisposable, IAsyncDisposable { private readonly Message _message; private readonly IAmAMessageConsumerAsync _consumer; - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; @@ -58,7 +58,7 @@ public AWSValidateInfrastructureTestsAsync() makeChannels: OnMissingChannel.Validate ); - _messageProducer = new SqsMessageProducer( + _messageProducer = new SnsMessageProducer( awsConnection, new SnsPublication { diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_arn_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_arn_async.cs index 2ed5f8813b..2cb54562d8 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_arn_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_arn_async.cs @@ -19,7 +19,7 @@ public class AWSValidateInfrastructureByArnTestsAsync : IAsyncDisposable, IDispo { private readonly Message _message; private readonly IAmAMessageConsumerAsync _consumer; - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; @@ -52,7 +52,7 @@ public AWSValidateInfrastructureByArnTestsAsync() _channelFactory = new ChannelFactory(awsConnection); var channel = _channelFactory.CreateAsyncChannel(subscription); - var topicArn = FindTopicArn(credentials, region, routingKey.Value).Result; + var topicArn = FindTopicArn(awsConnection, routingKey.Value).Result; var routingKeyArn = new RoutingKey(topicArn); subscription = new( @@ -63,7 +63,7 @@ public AWSValidateInfrastructureByArnTestsAsync() makeChannels: OnMissingChannel.Validate ); - _messageProducer = new SqsMessageProducer( + _messageProducer = new SnsMessageProducer( awsConnection, new SnsPublication { @@ -91,13 +91,13 @@ public async Task When_infrastructure_exists_can_verify_async() await _consumer.AcknowledgeAsync(message); } - private async Task FindTopicArn(AWSCredentials credentials, RegionEndpoint region, string topicName) + private static async Task FindTopicArn(AWSMessagingGatewayConnection connection, string topicName) { - var snsClient = new AmazonSimpleNotificationServiceClient(credentials, region); + using var snsClient = new AWSClientFactory(connection).CreateSnsClient(); var topicResponse = await snsClient.FindTopicAsync(topicName); return topicResponse.TopicArn; } - + public void Dispose() { //Clean up resources that we have created @@ -106,7 +106,7 @@ public void Dispose() ((IAmAMessageConsumerSync)_consumer).Dispose(); _messageProducer.Dispose(); } - + public async ValueTask DisposeAsync() { await _channelFactory.DeleteTopicAsync(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_convention.cs index 0b9aa55123..6b712889fa 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_convention.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_convention.cs @@ -16,7 +16,7 @@ public class AWSValidateInfrastructureByConventionTestsAsync : IAsyncDisposable, { private readonly Message _message; private readonly IAmAMessageConsumerAsync _consumer; - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; @@ -58,7 +58,7 @@ public AWSValidateInfrastructureByConventionTestsAsync() makeChannels: OnMissingChannel.Validate ); - _messageProducer = new SqsMessageProducer( + _messageProducer = new SnsMessageProducer( awsConnection, new SnsPublication { diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway.cs index 3f57a612b2..48de945144 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway.cs @@ -14,7 +14,7 @@ public class SqsMessageProducerSendTests : IDisposable, IAsyncDisposable { private readonly Message _message; private readonly IAmAChannelSync _channel; - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; private readonly string _correlationId; @@ -51,7 +51,7 @@ public SqsMessageProducerSendTests() _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateSyncChannel(subscription); - _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication{Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create}); + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication{Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create}); } [Fact] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway_async.cs index 6263358d56..50c21e5063 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway_async.cs @@ -14,7 +14,7 @@ public class SqsMessageProducerSendAsyncTests : IAsyncDisposable, IDisposable { private readonly Message _message; private readonly IAmAChannelAsync _channel; - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; private readonly string _correlationId; @@ -51,7 +51,7 @@ public SqsMessageProducerSendAsyncTests() _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateAsyncChannel(subscription); - _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication { Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create }); + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create }); } [Fact] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws.cs index a4cc2797e9..4afac9d990 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws.cs @@ -32,7 +32,7 @@ public AWSAssumeQueuesTests() //create the topic, we want the queue to be the issue //We need to create the topic at least, to check the queues - var producer = new SqsMessageProducer(awsConnection, + var producer = new SnsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws_async.cs index 695b4fb611..fdb549dbf0 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws_async.cs @@ -32,7 +32,7 @@ public AWSAssumeQueuesTestsAsync() //create the topic, we want the queue to be the issue //We need to create the topic at least, to check the queues - var producer = new SqsMessageProducer(awsConnection, + var producer = new SnsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws.cs index e4e5ddd1e5..37319b3ec5 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws.cs @@ -32,7 +32,7 @@ public AWSValidateQueuesTests() _awsConnection = GatewayFactory.CreateFactory(); //We need to create the topic at least, to check the queues - var producer = new SqsMessageProducer(_awsConnection, + var producer = new SnsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws_async.cs index 377c827a03..9325cc2955 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws_async.cs @@ -31,7 +31,7 @@ public AWSValidateQueuesTestsAsync() _awsConnection = GatewayFactory.CreateFactory(); // We need to create the topic at least, to check the queues - var producer = new SqsMessageProducer(_awsConnection, + var producer = new SnsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled.cs index 199e79090e..c501cd0e82 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled.cs @@ -15,7 +15,7 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Fragile", "CI")] public class SqsRawMessageDeliveryTests : IDisposable, IAsyncDisposable { - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly IAmAChannelSync _channel; private readonly RoutingKey _routingKey; @@ -40,7 +40,7 @@ public SqsRawMessageDeliveryTests() messagePumpType: MessagePumpType.Reactor, rawMessageDelivery: false)); - _messageProducer = new SqsMessageProducer(awsConnection, + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled_async.cs index 4e1aef5330..eacc39f327 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled_async.cs @@ -15,7 +15,7 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Fragile", "CI")] public class SqsRawMessageDeliveryTestsAsync : IAsyncDisposable, IDisposable { - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly IAmAChannelAsync _channel; private readonly RoutingKey _routingKey; @@ -39,7 +39,7 @@ public SqsRawMessageDeliveryTestsAsync() makeChannels: OnMissingChannel.Create, rawMessageDelivery: false)); - _messageProducer = new SqsMessageProducer(awsConnection, + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue.cs index dad4b358b1..a0751e950d 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue.cs @@ -17,7 +17,7 @@ public class SqsMessageConsumerRequeueTests : IDisposable { private readonly Message _message; private readonly IAmAChannelSync _channel; - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; @@ -51,7 +51,7 @@ public SqsMessageConsumerRequeueTests() _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateSyncChannel(subscription); - _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); } [Fact] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue_async.cs index bf00294f3e..87de1aa60b 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue_async.cs @@ -17,7 +17,7 @@ public class SqsMessageConsumerRequeueTestsAsync : IDisposable, IAsyncDisposable { private readonly Message _message; private readonly IAmAChannelAsync _channel; - private readonly SqsMessageProducer _messageProducer; + private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; @@ -50,7 +50,7 @@ public SqsMessageConsumerRequeueTestsAsync() _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateAsyncChannel(subscription); - _messageProducer = new SqsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); } [Fact] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message.cs index 643b217ff3..3380a80f77 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message.cs @@ -49,7 +49,7 @@ public SqsMessageProducerRequeueTests() var awsConnection = GatewayFactory.CreateFactory(); - _sender = new SqsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); + _sender = new SnsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); //We need to do this manually in a test - will create the channel from subscriber parameters _channelFactory = new ChannelFactory(awsConnection); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message_async.cs index da6ac72b1c..a919bfa1d5 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message_async.cs @@ -50,7 +50,7 @@ public SqsMessageProducerRequeueTestsAsync() var awsConnection = GatewayFactory.CreateFactory(); - _sender = new SqsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + _sender = new SnsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateAsyncChannel(subscription); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq.cs index 0dc759b324..33b29d823a 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq.cs @@ -19,44 +19,44 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Fragile", "CI")] public class SqsMessageProducerDlqTests : IDisposable, IAsyncDisposable { - private readonly SqsMessageProducer _sender; + private readonly SnsMessageProducer _sender; private readonly IAmAChannelSync _channel; private readonly ChannelFactory _channelFactory; private readonly Message _message; private readonly AWSMessagingGatewayConnection _awsConnection; private readonly string _dlqChannelName; - public SqsMessageProducerDlqTests () + public SqsMessageProducerDlqTests() { - MyCommand myCommand = new MyCommand{Value = "Test"}; + MyCommand myCommand = new MyCommand { Value = "Test" }; string correlationId = Guid.NewGuid().ToString(); string replyTo = "http:\\queueUrl"; string contentType = "text\\plain"; var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _dlqChannelName =$"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _dlqChannelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); string topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - + SqsSubscription subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, redrivePolicy: new RedrivePolicy(_dlqChannelName, 2) ); - + _message = new Message( new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) myCommand, JsonSerialisationOptions.Options)) + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) ); - + //Must have credentials stored in the SDK Credentials store or shared credentials file _awsConnection = GatewayFactory.CreateFactory(); - - _sender = new SqsMessageProducer(_awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); - + + _sender = new SnsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + _sender.ConfirmTopicExistsAsync(topicName).Wait(); - + //We need to do this manually in a test - will create the channel from subscriber parameters _channelFactory = new ChannelFactory(_awsConnection); _channel = _channelFactory.CreateSyncChannel(subscription); @@ -64,53 +64,53 @@ public SqsMessageProducerDlqTests () [Fact] public void When_requeueing_redrives_to_the_queue() - { + { _sender.Send(_message); - var receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - _channel.Requeue(receivedMessage ); + var receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(receivedMessage); receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - _channel.Requeue(receivedMessage ); - + _channel.Requeue(receivedMessage); + //should force us into the dlq receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - _channel.Requeue(receivedMessage) ; + _channel.Requeue(receivedMessage); Task.Delay(5000); - + //inspect the dlq GetDLQCount(_dlqChannelName).Should().Be(1); } - public int GetDLQCount(string queueName) + private int GetDLQCount(string queueName) { - using var sqsClient = new AmazonSQSClient(_awsConnection.Credentials, _awsConnection.Region); + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest { QueueUrl = queueUrlResponse.QueueUrl, WaitTimeSeconds = 5, - MessageAttributeNames = new List { "All", "ApproximateReceiveCount" } + MessageAttributeNames = new List { "All", "ApproximateReceiveCount" } }).GetAwaiter().GetResult(); if (response.HttpStatusCode != HttpStatusCode.OK) { - throw new AmazonSQSException($"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + throw new AmazonSQSException( + $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); } - + return response.Messages.Count; } - + public void Dispose() { - _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteTopicAsync().Wait(); _channelFactory.DeleteQueueAsync().Wait(); } - + public async ValueTask DisposeAsync() { - await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteTopicAsync(); await _channelFactory.DeleteQueueAsync(); } - } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq_async.cs index 0f8ec875ac..3c0827cddb 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq_async.cs @@ -19,7 +19,7 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; [Trait("Fragile", "CI")] public class SqsMessageProducerDlqTestsAsync : IDisposable, IAsyncDisposable { - private readonly SqsMessageProducer _sender; + private readonly SnsMessageProducer _sender; private readonly IAmAChannelAsync _channel; private readonly ChannelFactory _channelFactory; private readonly Message _message; @@ -53,7 +53,7 @@ public SqsMessageProducerDlqTestsAsync() _awsConnection = GatewayFactory.CreateFactory(); - _sender = new SqsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + _sender = new SnsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); _sender.ConfirmTopicExistsAsync(topicName).Wait(); @@ -80,9 +80,9 @@ public async Task When_requeueing_redrives_to_the_queue_async() dlqCount.Should().Be(1); } - public async Task GetDLQCountAsync(string queueName) + private async Task GetDLQCountAsync(string queueName) { - using var sqsClient = new AmazonSQSClient(_awsConnection.Credentials, _awsConnection.Region); + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); var queueUrlResponse = await sqsClient.GetQueueUrlAsync(queueName); var response = await sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest { @@ -93,7 +93,8 @@ public async Task GetDLQCountAsync(string queueName) if (response.HttpStatusCode != HttpStatusCode.OK) { - throw new AmazonSQSException($"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + throw new AmazonSQSException( + $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); } return response.Messages.Count; diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs index 1392ccb051..f73f02f7f0 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs @@ -25,7 +25,7 @@ public class SnsReDrivePolicySDlqTests : IDisposable, IAsyncDisposable private readonly Message _message; private readonly string _dlqChannelName; private readonly IAmAChannelSync _channel; - private readonly SqsMessageProducer _sender; + private readonly SnsMessageProducer _sender; private readonly AWSMessagingGatewayConnection _awsConnection; private readonly SqsSubscription _subscription; private readonly ChannelFactory _channelFactory; @@ -69,13 +69,11 @@ public SnsReDrivePolicySDlqTests() _awsConnection = GatewayFactory.CreateFactory(); //how do we send to the queue - _sender = new SqsMessageProducer( - _awsConnection, - new SnsPublication - { - Topic = routingKey, - RequestType = typeof(MyDeferredCommand), - MakeChannels = OnMissingChannel.Create + _sender = new SnsMessageProducer( + _awsConnection, + new SnsPublication + { + Topic = routingKey, RequestType = typeof(MyDeferredCommand), MakeChannels = OnMissingChannel.Create } ); @@ -102,20 +100,20 @@ public SnsReDrivePolicySDlqTests() var messageMapperRegistry = new MessageMapperRegistry( new SimpleMessageMapperFactory(_ => new MyDeferredCommandMessageMapper()), null - ); + ); messageMapperRegistry.Register(); - + //pump messages from a channel to a handler - in essence we are building our own dispatcher in this test - _messagePump = new Reactor(provider, messageMapperRegistry, - null, new InMemoryRequestContextFactory(), _channel) + _messagePump = new Reactor(provider, messageMapperRegistry, + null, new InMemoryRequestContextFactory(), _channel) { Channel = _channel, TimeOut = TimeSpan.FromMilliseconds(5000), RequeueCount = 3 }; } - public int GetDLQCount(string queueName) + private int GetDLQCount(string queueName) { - using var sqsClient = new AmazonSQSClient(_awsConnection.Credentials, _awsConnection.Region); + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest { @@ -127,7 +125,8 @@ public int GetDLQCount(string queueName) if (response.HttpStatusCode != HttpStatusCode.OK) { - throw new AmazonSQSException($"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + throw new AmazonSQSException( + $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); } return response.Messages.Count; @@ -150,13 +149,13 @@ public async Task When_throwing_defer_action_respect_redrive() //wait for the pump to stop once it gets a quit message await Task.WhenAll(task); - + await Task.Delay(5000); //inspect the dlq GetDLQCount(_dlqChannelName).Should().Be(1); } - + public void Dispose() { _channelFactory.DeleteTopicAsync().Wait(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive_async.cs index 2585807160..fefd1a60a8 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive_async.cs @@ -25,7 +25,7 @@ public class SnsReDrivePolicySDlqTestsAsync : IDisposable, IAsyncDisposable private readonly Message _message; private readonly string _dlqChannelName; private readonly IAmAChannelAsync _channel; - private readonly SqsMessageProducer _sender; + private readonly SnsMessageProducer _sender; private readonly AWSMessagingGatewayConnection _awsConnection; private readonly SqsSubscription _subscription; private readonly ChannelFactory _channelFactory; @@ -59,7 +59,7 @@ public SnsReDrivePolicySDlqTestsAsync() _awsConnection = GatewayFactory.CreateFactory(); - _sender = new SqsMessageProducer( + _sender = new SnsMessageProducer( _awsConnection, new SnsPublication { @@ -100,7 +100,7 @@ public SnsReDrivePolicySDlqTestsAsync() public async Task GetDLQCountAsync(string queueName) { - using var sqsClient = new AmazonSQSClient(_awsConnection.Credentials, _awsConnection.Region); + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); var queueUrlResponse = await sqsClient.GetQueueUrlAsync(queueName); var response = await sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest { diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs index 684be1d5b6..24a274b4fc 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs @@ -29,7 +29,7 @@ public AWSValidateMissingTopicTests() public void When_topic_missing_verify_throws(SnsSqsType type, string partitionKey) { //arrange - var producer = new SqsMessageProducer(_awsConnection, + var producer = new SnsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Validate, diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws_async.cs index 701d0b95f7..91cd06fb68 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws_async.cs @@ -28,7 +28,7 @@ public AWSValidateMissingTopicTestsAsync() public async Task When_topic_missing_verify_throws_async() { // arrange - var producer = new SqsMessageProducer(_awsConnection, + var producer = new SnsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Validate diff --git a/tests/Paramore.Brighter.AWS.Tests/Transformers/When_creating_luggagestore_missing_parameters.cs b/tests/Paramore.Brighter.AWS.Tests/Transformers/When_creating_luggagestore_missing_parameters.cs index 55721627f6..dc1dd5e49f 100644 --- a/tests/Paramore.Brighter.AWS.Tests/Transformers/When_creating_luggagestore_missing_parameters.cs +++ b/tests/Paramore.Brighter.AWS.Tests/Transformers/When_creating_luggagestore_missing_parameters.cs @@ -25,10 +25,10 @@ public class S3LuggageUploadMissingParametersTests public S3LuggageUploadMissingParametersTests() { //arrange - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var factory = new AWSClientFactory(GatewayFactory.CreateFactory()); - _client = new AmazonS3Client(credentials, region); - _stsClient = new AmazonSecurityTokenServiceClient(credentials, region); + _client = factory.CreateS3Client(); + _stsClient = factory.CreateStsClient(); var services = new ServiceCollection(); services.AddHttpClient(); diff --git a/tests/Paramore.Brighter.AWS.Tests/Transformers/When_unwrapping_a_large_message.cs b/tests/Paramore.Brighter.AWS.Tests/Transformers/When_unwrapping_a_large_message.cs index 2fe2e8c5cd..e8643ac374 100644 --- a/tests/Paramore.Brighter.AWS.Tests/Transformers/When_unwrapping_a_large_message.cs +++ b/tests/Paramore.Brighter.AWS.Tests/Transformers/When_unwrapping_a_large_message.cs @@ -19,7 +19,7 @@ namespace Paramore.Brighter.AWS.Tests.Transformers { - [Trait("Category", "AWS")] + [Trait("Category", "AWS")] [Trait("Fragile", "CI")] public class LargeMessagePaylodUnwrapTests : IDisposable { @@ -37,13 +37,13 @@ public LargeMessagePaylodUnwrapTests() new SimpleMessageMapperFactory(_ => new MyLargeCommandMessageMapper()), null ); - + mapperRegistry.Register(); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - _client = new AmazonS3Client(credentials, region); - AmazonSecurityTokenServiceClient stsClient = new(credentials, region); + var factory = new AWSClientFactory(GatewayFactory.CreateFactory()); + + _client = factory.CreateS3Client(); + var stsClient = factory.CreateStsClient(); var services = new ServiceCollection(); services.AddHttpClient(); @@ -51,7 +51,7 @@ public LargeMessagePaylodUnwrapTests() IHttpClientFactory httpClientFactory = provider.GetService(); _bucketName = $"brightertestbucket-{Guid.NewGuid()}"; - + _luggageStore = S3LuggageStore .CreateAsync( client: _client, @@ -61,7 +61,7 @@ public LargeMessagePaylodUnwrapTests() stsClient: stsClient, #pragma warning disable CS0618 // although obsolete, the region string on the replacement is wrong for our purpose bucketRegion: S3Region.EUW1, -#pragma warning restore CS0618 +#pragma warning restore CS0618 tags: new List { new Tag { Key = "BrighterTests", Value = "S3LuggageUploadTests" } }, acl: S3CannedACL.Private, abortFailedUploadsAfterDays: 1, @@ -69,23 +69,25 @@ public LargeMessagePaylodUnwrapTests() .GetAwaiter() .GetResult(); - var messageTransformerFactory = new SimpleMessageTransformerFactoryAsync(_ => new ClaimCheckTransformerAsync(_luggageStore)); + var messageTransformerFactory = + new SimpleMessageTransformerFactoryAsync(_ => new ClaimCheckTransformerAsync(_luggageStore)); _pipelineBuilder = new TransformPipelineBuilderAsync(mapperRegistry, messageTransformerFactory); } - + [Fact] public async Task When_unwrapping_a_large_message() { //arrange await Task.Delay(3000); //allow bucket definition to propagate - + //store our luggage and get the claim check var contents = DataGenerator.CreateString(6000); var myCommand = new MyLargeCommand(1) { Value = contents }; - var commandAsJson = JsonSerializer.Serialize(myCommand, new JsonSerializerOptions(JsonSerializerDefaults.General)); - - var stream = new MemoryStream(); + var commandAsJson = + JsonSerializer.Serialize(myCommand, new JsonSerializerOptions(JsonSerializerDefaults.General)); + + var stream = new MemoryStream(); var writer = new StreamWriter(stream); await writer.WriteAsync(commandAsJson); await writer.FlushAsync(); @@ -94,12 +96,13 @@ public async Task When_unwrapping_a_large_message() //pretend we ran through the claim check myCommand.Value = $"Claim Check {id}"; - + //set the headers, so that we have a claim check listed var message = new Message( - new MessageHeader(myCommand.Id, new RoutingKey("MyLargeCommand"), MessageType.MT_COMMAND, + new MessageHeader(myCommand.Id, new RoutingKey("MyLargeCommand"), MessageType.MT_COMMAND, timeStamp: DateTime.UtcNow), - new MessageBody(JsonSerializer.Serialize(myCommand, new JsonSerializerOptions(JsonSerializerDefaults.General))) + new MessageBody(JsonSerializer.Serialize(myCommand, + new JsonSerializerOptions(JsonSerializerDefaults.General))) ); message.Header.Bag[ClaimCheckTransformerAsync.CLAIM_CHECK] = id; @@ -107,7 +110,7 @@ public async Task When_unwrapping_a_large_message() //act var transformPipeline = _pipelineBuilder.BuildUnwrapPipeline(); var transformedMessage = await transformPipeline.UnwrapAsync(message, new RequestContext()); - + //assert //contents should be from storage transformedMessage.Value.Should().Be(contents); diff --git a/tests/Paramore.Brighter.AWS.Tests/Transformers/When_uploading_luggage_to_S3.cs b/tests/Paramore.Brighter.AWS.Tests/Transformers/When_uploading_luggage_to_S3.cs index dab8b8c7d1..b33494b163 100644 --- a/tests/Paramore.Brighter.AWS.Tests/Transformers/When_uploading_luggage_to_S3.cs +++ b/tests/Paramore.Brighter.AWS.Tests/Transformers/When_uploading_luggage_to_S3.cs @@ -31,10 +31,9 @@ public class S3LuggageUploadTests : IDisposable public S3LuggageUploadTests() { //arrange - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - - _client = new AmazonS3Client(credentials, region); - _stsClient = new AmazonSecurityTokenServiceClient(credentials, region); + var factory = new AWSClientFactory(GatewayFactory.CreateFactory()); + _client = factory.CreateS3Client(); + _stsClient = factory.CreateStsClient(); var services = new ServiceCollection(); services.AddHttpClient(); diff --git a/tests/Paramore.Brighter.AWS.Tests/Transformers/When_validating_a_luggage_store_exists.cs b/tests/Paramore.Brighter.AWS.Tests/Transformers/When_validating_a_luggage_store_exists.cs index f1cec1cdd7..5efa46e871 100644 --- a/tests/Paramore.Brighter.AWS.Tests/Transformers/When_validating_a_luggage_store_exists.cs +++ b/tests/Paramore.Brighter.AWS.Tests/Transformers/When_validating_a_luggage_store_exists.cs @@ -32,10 +32,10 @@ public class S3LuggageStoreExistsTests public S3LuggageStoreExistsTests() { //arrange - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - - _client = new AmazonS3Client(credentials, region); - _stsClient = new AmazonSecurityTokenServiceClient(credentials, region); + var factory = new AWSClientFactory(GatewayFactory.CreateFactory()); + _client = factory.CreateS3Client(); + _stsClient = factory.CreateStsClient(); + var services = new ServiceCollection(); services.AddHttpClient(); diff --git a/tests/Paramore.Brighter.AWS.Tests/Transformers/When_wrapping_a_large_message.cs b/tests/Paramore.Brighter.AWS.Tests/Transformers/When_wrapping_a_large_message.cs index 03450a5fd3..88c65abaa3 100644 --- a/tests/Paramore.Brighter.AWS.Tests/Transformers/When_wrapping_a_large_message.cs +++ b/tests/Paramore.Brighter.AWS.Tests/Transformers/When_wrapping_a_large_message.cs @@ -42,10 +42,10 @@ public LargeMessagePayloadWrapTests() _myCommand = new MyLargeCommand(6000); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var factory = new AWSClientFactory(GatewayFactory.CreateFactory()); - _client = new AmazonS3Client(credentials, region); - AmazonSecurityTokenServiceClient stsClient = new(credentials, region); + _client = factory.CreateS3Client(); + var stsClient = factory.CreateStsClient(); var services = new ServiceCollection(); services.AddHttpClient(); From e449da866394d0cf6bea3eb64578902508a2b657 Mon Sep 17 00:00:00 2001 From: Rafael Andrade Date: Thu, 2 Jan 2025 11:17:00 +0000 Subject: [PATCH 08/18] GH-1294 Fixes http test --- .../Helpers/InterceptingDelegatingHandler.cs | 21 +++++---- .../Helpers/InterceptingHttpClientFactory.cs | 19 +++----- .../When_customising_aws_client_config.cs | 24 +++++----- ...hen_customising_aws_client_config_async.cs | 47 ++++++++++--------- .../When_wrapping_a_large_message.cs | 2 +- 5 files changed, 56 insertions(+), 57 deletions(-) diff --git a/tests/Paramore.Brighter.AWS.Tests/Helpers/InterceptingDelegatingHandler.cs b/tests/Paramore.Brighter.AWS.Tests/Helpers/InterceptingDelegatingHandler.cs index 01fca0d7ad..255f61d76a 100644 --- a/tests/Paramore.Brighter.AWS.Tests/Helpers/InterceptingDelegatingHandler.cs +++ b/tests/Paramore.Brighter.AWS.Tests/Helpers/InterceptingDelegatingHandler.cs @@ -1,18 +1,21 @@ -using System.Net.Http; +using System.Collections.Concurrent; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; -namespace Paramore.Brighter.AWS.Tests.Helpers +namespace Paramore.Brighter.AWS.Tests.Helpers; + +internal class InterceptingDelegatingHandler(string tag) : DelegatingHandler { - internal class InterceptingDelegatingHandler : DelegatingHandler - { - public int RequestCount { get; private set; } + public static ConcurrentDictionary RequestCount { get; } = new(); - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (!RequestCount.TryAdd(tag, 1)) { - RequestCount++; - - return await base.SendAsync(request, cancellationToken); + RequestCount[tag] += 1; } + + return await base.SendAsync(request, cancellationToken); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/Helpers/InterceptingHttpClientFactory.cs b/tests/Paramore.Brighter.AWS.Tests/Helpers/InterceptingHttpClientFactory.cs index c30cab2e40..83ba274dfe 100644 --- a/tests/Paramore.Brighter.AWS.Tests/Helpers/InterceptingHttpClientFactory.cs +++ b/tests/Paramore.Brighter.AWS.Tests/Helpers/InterceptingHttpClientFactory.cs @@ -1,20 +1,13 @@ using System.Net.Http; using Amazon.Runtime; -namespace Paramore.Brighter.AWS.Tests.Helpers +namespace Paramore.Brighter.AWS.Tests.Helpers; + +internal class InterceptingHttpClientFactory(InterceptingDelegatingHandler handler) : HttpClientFactory { - internal class InterceptingHttpClientFactory : HttpClientFactory + public override HttpClient CreateHttpClient(IClientConfig clientConfig) { - private readonly InterceptingDelegatingHandler _handler; - - public InterceptingHttpClientFactory(InterceptingDelegatingHandler handler) - { - _handler = handler; - } - - public override HttpClient CreateHttpClient(IClientConfig clientConfig) - { - return new HttpClient(_handler); - } + handler.InnerHandler ??= new HttpClientHandler(); + return new HttpClient(handler); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs index 2d27a0b526..c23c2d70d1 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs @@ -17,20 +17,17 @@ public class CustomisingAwsClientConfigTests : IDisposable, IAsyncDisposable private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; - private readonly InterceptingDelegatingHandler _publishHttpHandler = new(); - private readonly InterceptingDelegatingHandler _subscribeHttpHandler = new(); - public CustomisingAwsClientConfigTests() { + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; MyCommand myCommand = new() { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - SqsSubscription subscription = new( + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), messagePumpType: MessagePumpType.Reactor, @@ -45,7 +42,7 @@ public CustomisingAwsClientConfigTests() var subscribeAwsConnection = GatewayFactory.CreateFactory(config => { - config.HttpClientFactory = new InterceptingHttpClientFactory(_subscribeHttpHandler); + config.HttpClientFactory = new InterceptingHttpClientFactory(new InterceptingDelegatingHandler("sync_sub")); }); _channelFactory = new ChannelFactory(subscribeAwsConnection); @@ -53,7 +50,7 @@ public CustomisingAwsClientConfigTests() var publishAwsConnection = GatewayFactory.CreateFactory(config => { - config.HttpClientFactory = new InterceptingHttpClientFactory(_publishHttpHandler); + config.HttpClientFactory = new InterceptingHttpClientFactory(new InterceptingDelegatingHandler("sync_pub")); }); _messageProducer = new SnsMessageProducer(publishAwsConnection, @@ -74,8 +71,11 @@ public async Task When_customising_aws_client_config() _channel.Acknowledge(message); //publish_and_subscribe_should_use_custom_http_client_factory - _publishHttpHandler.RequestCount.Should().BeGreaterThan(0); - _subscribeHttpHandler.RequestCount.Should().BeGreaterThan(0); + InterceptingDelegatingHandler.RequestCount.Should().ContainKey("sync_sub"); + InterceptingDelegatingHandler.RequestCount["sync_sub"].Should().BeGreaterThan(0); + + InterceptingDelegatingHandler.RequestCount.Should().ContainKey("sync_pub"); + InterceptingDelegatingHandler.RequestCount["sync_pub"].Should().BeGreaterThan(0); } public void Dispose() diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config_async.cs index 2adb6f8905..99ad58b5cd 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config_async.cs @@ -17,46 +17,46 @@ public class CustomisingAwsClientConfigTestsAsync : IDisposable, IAsyncDisposabl private readonly SnsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; - private readonly InterceptingDelegatingHandler _publishHttpHandler = new(); - private readonly InterceptingDelegatingHandler _subscribeHttpHandler = new(); - public CustomisingAwsClientConfigTestsAsync() { - MyCommand myCommand = new() {Value = "Test"}; + MyCommand myCommand = new() { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( + + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), messagePumpType: MessagePumpType.Proactor, routingKey: routingKey ); - + _message = new Message( new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) myCommand, JsonSerialisationOptions.Options)) + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) ); var subscribeAwsConnection = GatewayFactory.CreateFactory(config => { - config.HttpClientFactory = new InterceptingHttpClientFactory(_subscribeHttpHandler); + config.HttpClientFactory = + new InterceptingHttpClientFactory(new InterceptingDelegatingHandler("async_sub")); }); - + _channelFactory = new ChannelFactory(subscribeAwsConnection); _channel = _channelFactory.CreateAsyncChannel(subscription); - var publishAwsConnection = GatewayFactory.CreateFactory(config => + var publishAwsConnection = GatewayFactory.CreateFactory(config => { - config.HttpClientFactory = new InterceptingHttpClientFactory(_publishHttpHandler); + config.HttpClientFactory = + new InterceptingHttpClientFactory(new InterceptingDelegatingHandler("async_pub")); }); - _messageProducer = new SnsMessageProducer(publishAwsConnection, new SnsPublication{Topic = new RoutingKey(topicName), MakeChannels = OnMissingChannel.Create}); + _messageProducer = new SnsMessageProducer(publishAwsConnection, + new SnsPublication { Topic = new RoutingKey(topicName), MakeChannels = OnMissingChannel.Create }); } [Fact] @@ -64,17 +64,20 @@ public async Task When_customising_aws_client_config() { //arrange await _messageProducer.SendAsync(_message); - + await Task.Delay(1000); - - var message =await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - + + var message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + //clear the queue await _channel.AcknowledgeAsync(message); //publish_and_subscribe_should_use_custom_http_client_factory - _publishHttpHandler.RequestCount.Should().BeGreaterThan(0); - _subscribeHttpHandler.RequestCount.Should().BeGreaterThan(0); + InterceptingDelegatingHandler.RequestCount.Should().ContainKey("async_sub"); + InterceptingDelegatingHandler.RequestCount["async_sub"].Should().BeGreaterThan(0); + + InterceptingDelegatingHandler.RequestCount.Should().ContainKey("async_pub"); + InterceptingDelegatingHandler.RequestCount["async_pub"].Should().BeGreaterThan(0); } public void Dispose() diff --git a/tests/Paramore.Brighter.AWS.Tests/Transformers/When_wrapping_a_large_message.cs b/tests/Paramore.Brighter.AWS.Tests/Transformers/When_wrapping_a_large_message.cs index 88c65abaa3..fa0ab9db5b 100644 --- a/tests/Paramore.Brighter.AWS.Tests/Transformers/When_wrapping_a_large_message.cs +++ b/tests/Paramore.Brighter.AWS.Tests/Transformers/When_wrapping_a_large_message.cs @@ -64,7 +64,7 @@ public LargeMessagePayloadWrapTests() #pragma warning disable CS0618 // It is obsolete, but we want the string value here not the replacement one bucketRegion: S3Region.EUW1, #pragma warning restore CS0618 - tags: new List { new Tag { Key = "BrighterTests", Value = "S3LuggageUploadTests" } }, + tags: [new Tag { Key = "BrighterTests", Value = "S3LuggageUploadTests" }], acl: S3CannedACL.Private, abortFailedUploadsAfterDays: 1, deleteGoodUploadsAfterDays: 1) From 527e14a7b1c6731c783094659a9734f2133ea5d5 Mon Sep 17 00:00:00 2001 From: Rafael Andrade Date: Thu, 2 Jan 2025 15:23:03 +0000 Subject: [PATCH 09/18] Add Fifo test --- .../AWSNameExtensions.cs | 85 ++++++--- .../ChannelFactory.cs | 16 +- .../SqsMessageConsumerFactory.cs | 2 +- .../SqsSubscription.cs | 4 +- ...essage_consumer_reads_multiple_messages.cs | 158 ++++++++++++++++ ..._consumer_reads_multiple_messages_async.cs | 153 ++++++++++++++++ .../When_infastructure_exists_can_assume.cs | 105 +++++++++++ ...n_infastructure_exists_can_assume_async.cs | 102 +++++++++++ .../When_infastructure_exists_can_verify.cs | 116 ++++++++++++ ..._infastructure_exists_can_verify_by_arn.cs | 128 +++++++++++++ ...ructure_exists_can_verify_by_convention.cs | 107 +++++++++++ ..._infrastructure_exists_can_verify_async.cs | 106 +++++++++++ ...tructure_exists_can_verify_by_arn_async.cs | 116 ++++++++++++ ...ructure_exists_can_verify_by_convention.cs | 104 +++++++++++ ...ing_a_message_via_the_messaging_gateway.cs | 110 ++++++++++++ ...message_via_the_messaging_gateway_async.cs | 109 ++++++++++++ .../Fifo/When_queues_missing_assume_throws.cs | 67 +++++++ ...When_queues_missing_assume_throws_async.cs | 66 +++++++ .../Fifo/When_queues_missing_verify_throws.cs | 62 +++++++ ...When_queues_missing_verify_throws_async.cs | 55 ++++++ .../When_raw_message_delivery_disabled.cs | 94 ++++++++++ ...hen_raw_message_delivery_disabled_async.cs | 93 ++++++++++ ..._a_message_through_gateway_with_requeue.cs | 87 +++++++++ ...sage_through_gateway_with_requeue_async.cs | 86 +++++++++ .../Fifo/When_requeueing_a_message.cs | 83 +++++++++ .../Fifo/When_requeueing_a_message_async.cs | 82 +++++++++ .../When_requeueing_redrives_to_the_dlq.cs | 114 ++++++++++++ ...en_requeueing_redrives_to_the_dlq_async.cs | 112 ++++++++++++ ...n_throwing_defer_action_respect_redrive.cs | 168 ++++++++++++++++++ ...wing_defer_action_respect_redrive_async.cs | 149 ++++++++++++++++ .../Fifo/When_topic_missing_verify_throws.cs | 42 +++++ .../When_topic_missing_verify_throws_async.cs | 41 +++++ 32 files changed, 2883 insertions(+), 39 deletions(-) create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_arn_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_convention.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws_async.cs diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSNameExtensions.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSNameExtensions.cs index 222521766c..249bd6612b 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSNameExtensions.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSNameExtensions.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2022 Ian Cooper @@ -19,53 +20,79 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + #endregion -namespace Paramore.Brighter.MessagingGateway.AWSSQS +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +public static class AWSNameExtensions { - public static class AWSNameExtensions + public static ChannelName ToValidSQSQueueName(this ChannelName? channelName, bool isFifo = false) { - public static ChannelName ToValidSQSQueueName(this ChannelName? channelName, bool isFifo = false) + if (channelName is null) { - if (channelName is null) - return new ChannelName(string.Empty); - - //SQS only allows 80 characters alphanumeric, hyphens, and underscores, but we might use a period in a - //default typename strategy - var name = channelName.Value; - name = name.Replace(".", "_"); - if (name.Length > 80) - name = name.Substring(0, 80); + return new ChannelName(string.Empty); + } - if (isFifo) + //SQS only allows 80 characters alphanumeric, hyphens, and underscores, but we might use a period in a + //default typename strategy + var name = channelName.Value; + if (isFifo) + { + if (name.EndsWith(".fifo")) { - name = name + ".fifo"; + name = name.Substring(0, name.Length - 5); } - return new ChannelName(name); + name = name.Replace(".", "_"); + if (name.Length > 75) + { + name = name.Substring(0, 75); + } + + name += ".fifo"; } + else + { + name = name.Replace(".", "_"); + if (name.Length > 80) + { + name = name.Substring(0, 80); + } + } + + return new ChannelName(name); + } - public static RoutingKey ToValidSNSTopicName(this RoutingKey routingKey) + public static RoutingKey ToValidSNSTopicName(this RoutingKey routingKey, bool isFifo = false) + { + //SNS only topic names are limited to 256 characters. Alphanumeric characters plus hyphens (-) and + //underscores (_) are allowed. Topic names must be unique within an AWS account. + var topic = routingKey.Value; + if (isFifo) { - //SNS only topic names are limited to 256 characters. Alphanumeric characters plus hyphens (-) and - //underscores (_) are allowed. Topic names must be unique within an AWS account. - var topic = routingKey.Value; + if (topic.EndsWith(".fifo")) + { + topic = topic.Substring(0, topic.Length - 5); + } + topic = topic.Replace(".", "_"); - if (topic.Length > 256) - topic = topic.Substring(0, 256); + if(topic.Length > 251) + { + topic = topic.Substring(0, 251); + } - return new RoutingKey(topic); + topic += ".fifo"; } - - public static string ToValidSNSTopicName(this string topic) + else { - //SNS only topic names are limited to 256 characters. Alphanumeric characters plus hyphens (-) and - //underscores (_) are allowed. Topic names must be unique within an AWS account. topic = topic.Replace(".", "_"); if (topic.Length > 256) + { topic = topic.Substring(0, 256); - - return topic; + } } - } + + return new RoutingKey(topic); + } } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs index d7df8293c2..1e0d9e5c3e 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs @@ -123,8 +123,8 @@ await EnsureTopicAsync(_subscription.RoutingKey, await EnsureQueueAsync(); return new ChannelAsync( - subscription.ChannelName.ToValidSQSQueueName(), - subscription.RoutingKey.ToValidSNSTopicName(), + subscription.ChannelName.ToValidSQSQueueName(_subscription.SqsType == SnsSqsType.Fifo), + subscription.RoutingKey.ToValidSNSTopicName(_subscription.SqsType == SnsSqsType.Fifo), _messageConsumerFactory.CreateAsync(subscription), subscription.BufferSize ); @@ -143,7 +143,7 @@ public async Task DeleteQueueAsync() using var sqsClient = new AWSClientFactory(AwsConnection).CreateSqsClient(); (bool exists, string? queueUrl) queueExists = - await QueueExistsAsync(sqsClient, _subscription.ChannelName.ToValidSQSQueueName()); + await QueueExistsAsync(sqsClient, _subscription.ChannelName.ToValidSQSQueueName(_subscription.SqsType == SnsSqsType.Fifo)); if (queueExists.exists && queueExists.queueUrl != null) { @@ -205,8 +205,8 @@ await EnsureTopicAsync(_subscription.RoutingKey, await EnsureQueueAsync(); return new Channel( - subscription.ChannelName.ToValidSQSQueueName(), - subscription.RoutingKey.ToValidSNSTopicName(), + subscription.ChannelName.ToValidSQSQueueName(_subscription.SqsType == SnsSqsType.Fifo), + subscription.RoutingKey.ToValidSNSTopicName(_subscription.SqsType == SnsSqsType.Fifo), _messageConsumerFactory.Create(subscription), subscription.BufferSize ); @@ -224,8 +224,8 @@ private async Task EnsureQueueAsync() return; using var sqsClient = new AWSClientFactory(AwsConnection).CreateSqsClient(); - var queueName = _subscription.ChannelName.ToValidSQSQueueName(); - var topicName = _subscription.RoutingKey.ToValidSNSTopicName(); + var queueName = _subscription.ChannelName.ToValidSQSQueueName(_subscription.SqsType == SnsSqsType.Fifo); + var topicName = _subscription.RoutingKey.ToValidSNSTopicName(_subscription.SqsType == SnsSqsType.Fifo); (bool exists, _) = await QueueExistsAsync(sqsClient, queueName); if (!exists) @@ -257,7 +257,9 @@ private async Task EnsureQueueAsync() private async Task CreateQueueAsync(AmazonSQSClient sqsClient) { if (_subscription is null) + { throw new InvalidOperationException("ChannelFactory: Subscription cannot be null"); + } s_logger.LogDebug("Queue does not exist, creating queue: {ChannelName} subscribed to {Topic} on {Region}", _subscription.ChannelName.Value, _subscription.RoutingKey.Value, AwsConnection.Region); diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageConsumerFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageConsumerFactory.cs index 08468f5df2..e1219579a9 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageConsumerFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageConsumerFactory.cs @@ -61,7 +61,7 @@ private SqsMessageConsumer CreateImpl(Subscription subscription) return new SqsMessageConsumer( awsConnection: _awsConnection, - queueName: subscription.ChannelName.ToValidSQSQueueName(), + queueName: subscription.ChannelName.ToValidSQSQueueName(sqsSubscription.SqsType == SnsSqsType.Fifo), batchSize: subscription.BufferSize, hasDLQ: sqsSubscription.RedrivePolicy == null, rawMessageDelivery: sqsSubscription.RawMessageDelivery diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs index d9e1f217b6..40a08cc947 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs @@ -166,7 +166,7 @@ public SqsSubscription( TimeSpan? emptyChannelDelay = null, TimeSpan? channelFailureDelay = null, SnsSqsType sqsType = SnsSqsType.Standard, - bool contentBasedDeduplication = false, + bool contentBasedDeduplication = true, DeduplicationScope? deduplicationScope = null, int? fifoThroughputLimit = null ) @@ -254,7 +254,7 @@ public SqsSubscription( TimeSpan? emptyChannelDelay = null, TimeSpan? channelFailureDelay = null, SnsSqsType sqsType = SnsSqsType.Standard, - bool contentBasedDeduplication = false, + bool contentBasedDeduplication = true, DeduplicationScope? deduplicationScope = null, int? fifoThroughputLimit = null ) diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs new file mode 100644 index 0000000000..a3b63980c7 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SQSBufferedConsumerTests : IDisposable, IAsyncDisposable +{ + private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageConsumer _consumer; + private readonly string _topicName; + private readonly ChannelFactory _channelFactory; + private const string ContentType = "text\\plain"; + private const int BufferSize = 3; + private const int MessageCount = 4; + + public SQSBufferedConsumerTests() + { + var awsConnection = GatewayFactory.CreateFactory(); + _channelFactory = new ChannelFactory(awsConnection); + + var channelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _topicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + + //we need the channel to create the queues and notifications + var routingKey = new RoutingKey(_topicName); + + var channel = _channelFactory.CreateSyncChannel(new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + bufferSize: BufferSize, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + contentBasedDeduplication: true, + deduplicationScope: DeduplicationScope.MessageGroup, + fifoThroughputLimit: 1 + )); + + //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel + //just for the tests, so create a new consumer from the properties + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(true), BufferSize); + _messageProducer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo, Deduplication = true + }); + } + + [Fact] + public async Task When_a_message_consumer_reads_multiple_messages() + { + var routingKey = new RoutingKey(_topicName); + + var messageGroupIdOne = $"MessageGroup{Guid.NewGuid():N}"; + var messageOne = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdOne), + new MessageBody("test content one") + ); + + var messageTwo = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdOne), + new MessageBody("test content two") + ); + + + var messageGroupIdTwo = $"MessageGroup{Guid.NewGuid():N}"; + var deduplicationId = $"DeduplicationId{Guid.NewGuid():N}"; + + var messageThree = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdTwo) + { + Bag = { [HeaderNames.DeduplicationId] = deduplicationId } + }, + new MessageBody("test content three") + ); + + var messageFour = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdTwo) + { + Bag = { [HeaderNames.DeduplicationId] = deduplicationId } + }, + new MessageBody("test content four") + ); + + var messageFive = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdTwo), + new MessageBody("test content four") + ); + + //send MESSAGE_COUNT messages + _messageProducer.Send(messageOne); + _messageProducer.Send(messageTwo); + _messageProducer.Send(messageThree); + _messageProducer.Send(messageFour); + _messageProducer.Send(messageFive); + + + int iteration = 0; + var messagesReceived = new List(); + var messagesReceivedCount = messagesReceived.Count; + do + { + iteration++; + var outstandingMessageCount = MessageCount - messagesReceivedCount; + + //retrieve messages + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(10000)); + + messages.Length.Should().BeLessOrEqualTo(outstandingMessageCount); + + //should not receive more than buffer in one hit + messages.Length.Should().BeLessOrEqualTo(BufferSize); + + var moreMessages = messages.Where(m => m.Header.MessageType == MessageType.MT_COMMAND); + foreach (var message in moreMessages) + { + messagesReceived.Add(message); + _consumer.Acknowledge(message); + } + + messagesReceivedCount = messagesReceived.Count; + + await Task.Delay(1000); + } while ((iteration <= 5) && (messagesReceivedCount < MessageCount)); + + + messagesReceivedCount.Should().Be(4); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageProducerAsync)_messageProducer).DisposeAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages_async.cs new file mode 100644 index 0000000000..f7d9e3a442 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages_async.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SQSBufferedConsumerTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageConsumer _consumer; + private readonly string _topicName; + private readonly ChannelFactory _channelFactory; + private const string ContentType = "text\\plain"; + private const int BufferSize = 3; + private const int MessageCount = 4; + + public SQSBufferedConsumerTestsAsync() + { + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + var channelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _topicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + + //we need the channel to create the queues and notifications + var routingKey = new RoutingKey(_topicName); + + var channel = _channelFactory.CreateAsyncChannelAsync(new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + bufferSize: BufferSize, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + contentBasedDeduplication: true, + deduplicationScope: DeduplicationScope.MessageGroup, + fifoThroughputLimit: 1 + )).GetAwaiter().GetResult(); + + //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel + //just for the tests, so create a new consumer from the properties + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(true), BufferSize); + _messageProducer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo, Deduplication = true + }); + } + + [Fact] + public async Task When_a_message_consumer_reads_multiple_messages_async() + { + var routingKey = new RoutingKey(_topicName); + + var messageGroupIdOne = $"MessageGroup{Guid.NewGuid():N}"; + var messageOne = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdOne), + new MessageBody("test content one") + ); + + var messageTwo = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdOne), + new MessageBody("test content two") + ); + + var messageGroupIdTwo = $"MessageGroup{Guid.NewGuid():N}"; + var deduplicationId = $"DeduplicationId{Guid.NewGuid():N}"; + var messageThree = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdTwo) + { + Bag = { [HeaderNames.DeduplicationId] = deduplicationId } + }, + new MessageBody("test content three") + ); + + var messageFour = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdTwo) + { + Bag = { [HeaderNames.DeduplicationId] = deduplicationId } + }, + new MessageBody("test content four") + ); + + var messageFive = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdTwo), + new MessageBody("test content four") + ); + + //send MESSAGE_COUNT messages + await _messageProducer.SendAsync(messageOne); + await _messageProducer.SendAsync(messageTwo); + await _messageProducer.SendAsync(messageThree); + await _messageProducer.SendAsync(messageFour); + await _messageProducer.SendAsync(messageFive); + + int iteration = 0; + var messagesReceived = new List(); + var messagesReceivedCount = messagesReceived.Count; + do + { + iteration++; + var outstandingMessageCount = MessageCount - messagesReceivedCount; + + //retrieve messages + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(10000)); + + messages.Length.Should().BeLessOrEqualTo(outstandingMessageCount); + + //should not receive more than buffer in one hit + messages.Length.Should().BeLessOrEqualTo(BufferSize); + + var moreMessages = messages.Where(m => m.Header.MessageType == MessageType.MT_COMMAND); + foreach (var message in moreMessages) + { + messagesReceived.Add(message); + await _consumer.AcknowledgeAsync(message); + } + + messagesReceivedCount = messagesReceived.Count; + + await Task.Delay(1000); + } while ((iteration <= 5) && (messagesReceivedCount < MessageCount)); + + messagesReceivedCount.Should().Be(4); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _messageProducer.DisposeAsync(); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().GetAwaiter().GetResult(); + _channelFactory.DeleteQueueAsync().GetAwaiter().GetResult(); + _messageProducer.DisposeAsync().GetAwaiter().GetResult(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs new file mode 100644 index 0000000000..754d93a735 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs @@ -0,0 +1,105 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSAssumeInfrastructureTests : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly SqsMessageConsumer _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSAssumeInfrastructureTests() + { + _myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + contentBasedDeduplication: true); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Assume, + sqsType: SnsSqsType.Fifo + ); + + _messageProducer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Assume, + SnsType = SnsSqsType.Fifo + }); + + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(true)); + } + + [Fact] + public void When_infastructure_exists_can_assume() + { + //arrange + _messageProducer.Send(_message); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume_async.cs new file mode 100644 index 0000000000..768c658d56 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume_async.cs @@ -0,0 +1,102 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSAssumeInfrastructureTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly SqsMessageConsumer _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSAssumeInfrastructureTestsAsync() + { + _myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + contentBasedDeduplication: true + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateAsyncChannel(subscription); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Assume, + sqsType: SnsSqsType.Fifo + ); + + _messageProducer = new SnsMessageProducer(awsConnection, + new SnsPublication { MakeChannels = OnMissingChannel.Assume, SnsType = SnsSqsType.Fifo }); + + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(true)); + } + + [Fact] + public async Task When_infastructure_exists_can_assume() + { + //arrange + await _messageProducer.SendAsync(_message); + + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + await _consumer.AcknowledgeAsync(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs new file mode 100644 index 0000000000..5d57fbce6f --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs @@ -0,0 +1,116 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureTests : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumerSync _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureTests() + { + _myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + findTopicBy: TopicFindBy.Name, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo + ); + + _messageProducer = new SnsMessageProducer( + awsConnection, + new SnsPublication + { + FindTopicBy = TopicFindBy.Name, + MakeChannels = OnMissingChannel.Validate, + Topic = new RoutingKey(topicName), + SnsType = SnsSqsType.Fifo + } + ); + + _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify() + { + //arrange + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _consumer.Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); + await _messageProducer.DisposeAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs new file mode 100644 index 0000000000..e0a5717149 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs @@ -0,0 +1,128 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureByArnTests : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumerSync _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureByArnTests() + { + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + + _myCommand = new MyCommand { Value = "Test" }; + var correlationId = Guid.NewGuid().ToString(); + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey($"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + var topicArn= FindTopicArn(awsConnection, routingKey.ToValidSNSTopicName(true)); + var routingKeyArn = new RoutingKey(topicArn); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKeyArn, + findTopicBy: TopicFindBy.Arn, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo + ); + + _messageProducer = new SnsMessageProducer( + awsConnection, + new SnsPublication + { + Topic = routingKey, + TopicArn = topicArn, + FindTopicBy = TopicFindBy.Arn, + MakeChannels = OnMissingChannel.Validate, + SnsType = SnsSqsType.Fifo + }); + + _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify() + { + //arrange + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _consumer.Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); + await _messageProducer.DisposeAsync(); + } + + private static string FindTopicArn(AWSMessagingGatewayConnection connection, string topicName) + { + using var snsClient = new AWSClientFactory(connection).CreateSnsClient(); + var topicResponse = snsClient.FindTopicAsync(topicName).GetAwaiter().GetResult(); + return topicResponse.TopicArn; + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs new file mode 100644 index 0000000000..64250242aa --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs @@ -0,0 +1,107 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureByConventionTests : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumerSync _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureByConventionTests() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + //Now change the subscription to validate, just check what we made - will make the SNS Arn to prevent ListTopics call + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + findTopicBy: TopicFindBy.Convention, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Validate + ); + + _messageProducer = new SnsMessageProducer( + awsConnection, + new SnsPublication { FindTopicBy = TopicFindBy.Convention, MakeChannels = OnMissingChannel.Validate } + ); + + _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify() + { + //arrange + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _consumer.Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); + await _messageProducer.DisposeAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_async.cs new file mode 100644 index 0000000000..714c0b5512 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_async.cs @@ -0,0 +1,106 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo +{ + [Trait("Category", "AWS")] + [Trait("Fragile", "CI")] + public class AWSValidateInfrastructureTestsAsync : IDisposable, IAsyncDisposable + { + private readonly Message _message; + private readonly IAmAMessageConsumerAsync _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureTestsAsync() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateAsyncChannel(subscription); + + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + findTopicBy: TopicFindBy.Name, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Validate + ); + + _messageProducer = new SnsMessageProducer( + awsConnection, + new SnsPublication + { + FindTopicBy = TopicFindBy.Name, + MakeChannels = OnMissingChannel.Validate, + Topic = new RoutingKey(topicName) + } + ); + + _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify_async() + { + await _messageProducer.SendAsync(_message); + + await Task.Delay(1000); + + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + await _consumer.AcknowledgeAsync(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + ((IAmAMessageConsumerSync)_consumer).Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _consumer.DisposeAsync(); + await _messageProducer.DisposeAsync(); + } + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_arn_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_arn_async.cs new file mode 100644 index 0000000000..f2ff4e08c4 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_arn_async.cs @@ -0,0 +1,116 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureByArnTestsAsync : IAsyncDisposable, IDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumerAsync _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureByArnTestsAsync() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey($"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var awsConnection = GatewayFactory.CreateFactory(credentials, region); + + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateAsyncChannel(subscription); + + var topicArn = FindTopicArn(awsConnection, routingKey.Value).Result; + var routingKeyArn = new RoutingKey(topicArn); + + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKeyArn, + findTopicBy: TopicFindBy.Arn, + makeChannels: OnMissingChannel.Validate + ); + + _messageProducer = new SnsMessageProducer( + awsConnection, + new SnsPublication + { + Topic = routingKey, + TopicArn = topicArn, + FindTopicBy = TopicFindBy.Arn, + MakeChannels = OnMissingChannel.Validate + }); + + _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify_async() + { + await _messageProducer.SendAsync(_message); + + await Task.Delay(1000); + + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + await _consumer.AcknowledgeAsync(message); + } + + private static async Task FindTopicArn(AWSMessagingGatewayConnection connection, string topicName) + { + using var snsClient = new AWSClientFactory(connection).CreateSnsClient(); + var topicResponse = await snsClient.FindTopicAsync(topicName); + return topicResponse.TopicArn; + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + ((IAmAMessageConsumerSync)_consumer).Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _consumer.DisposeAsync(); + await _messageProducer.DisposeAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_convention.cs new file mode 100644 index 0000000000..c730ca6f21 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_convention.cs @@ -0,0 +1,104 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureByConventionTestsAsync : IAsyncDisposable, IDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumerAsync _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureByConventionTestsAsync() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateAsyncChannel(subscription); + + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + findTopicBy: TopicFindBy.Convention, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Validate + ); + + _messageProducer = new SnsMessageProducer( + awsConnection, + new SnsPublication + { + FindTopicBy = TopicFindBy.Convention, + MakeChannels = OnMissingChannel.Validate + } + ); + + _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify_async() + { + await _messageProducer.SendAsync(_message); + + await Task.Delay(1000); + + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + await _consumer.AcknowledgeAsync(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + ((IAmAMessageConsumerSync)_consumer).Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _consumer.DisposeAsync(); + await _messageProducer.DisposeAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs new file mode 100644 index 0000000000..83616bb8d4 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs @@ -0,0 +1,110 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +public class SqsMessageProducerSendTests : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly IAmAChannelSync _channel; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + private readonly string _correlationId; + private readonly string _replyTo; + private readonly string _contentType; + private readonly string _topicName; + + public SqsMessageProducerSendTests() + { + _myCommand = new MyCommand{Value = "Test"}; + _correlationId = Guid.NewGuid().ToString(); + _replyTo = "http:\\queueUrl"; + _contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(_topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + rawMessageDelivery: false + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: _correlationId, + replyTo: new RoutingKey(_replyTo), contentType: _contentType), + new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); + + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication{Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create}); + } + + [Fact] + public async Task When_posting_a_message_via_the_producer() + { + //arrange + _message.Header.Subject = "test subject"; + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + //clear the queue + _channel.Acknowledge(message); + + //should_send_the_message_to_aws_sqs + message.Header.MessageType.Should().Be(MessageType.MT_COMMAND); + + message.Id.Should().Be(_myCommand.Id); + message.Redelivered.Should().BeFalse(); + message.Header.MessageId.Should().Be(_myCommand.Id); + message.Header.Topic.Value.Should().Contain(_topicName); + message.Header.CorrelationId.Should().Be(_correlationId); + message.Header.ReplyTo.Should().Be(_replyTo); + message.Header.ContentType.Should().Be(_contentType); + message.Header.HandledCount.Should().Be(0); + message.Header.Subject.Should().Be(_message.Header.Subject); + //allow for clock drift in the following test, more important to have a contemporary timestamp than anything + message.Header.TimeStamp.Should().BeAfter(RoundToSeconds(DateTime.UtcNow.AddMinutes(-1))); + message.Header.Delayed.Should().Be(TimeSpan.Zero); + //{"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} + message.Body.Value.Should().Be(_message.Body.Value); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _messageProducer.DisposeAsync(); + } + + private static DateTime RoundToSeconds(DateTime dateTime) + { + return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind); + } + +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway_async.cs new file mode 100644 index 0000000000..d06dde7a2b --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway_async.cs @@ -0,0 +1,109 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +public class SqsMessageProducerSendAsyncTests : IAsyncDisposable, IDisposable +{ + private readonly Message _message; + private readonly IAmAChannelAsync _channel; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + private readonly string _correlationId; + private readonly string _replyTo; + private readonly string _contentType; + private readonly string _topicName; + + public SqsMessageProducerSendAsyncTests() + { + _myCommand = new MyCommand { Value = "Test" }; + _correlationId = Guid.NewGuid().ToString(); + _replyTo = "http:\\queueUrl"; + _contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(_topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + rawMessageDelivery: false + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: _correlationId, + replyTo: new RoutingKey(_replyTo), contentType: _contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateAsyncChannel(subscription); + + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create }); + } + + [Fact] + public async Task When_posting_a_message_via_the_producer_async() + { + // arrange + _message.Header.Subject = "test subject"; + await _messageProducer.SendAsync(_message); + + await Task.Delay(1000); + + var message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + // clear the queue + await _channel.AcknowledgeAsync(message); + + // should_send_the_message_to_aws_sqs + message.Header.MessageType.Should().Be(MessageType.MT_COMMAND); + + message.Id.Should().Be(_myCommand.Id); + message.Redelivered.Should().BeFalse(); + message.Header.MessageId.Should().Be(_myCommand.Id); + message.Header.Topic.Value.Should().Contain(_topicName); + message.Header.CorrelationId.Should().Be(_correlationId); + message.Header.ReplyTo.Should().Be(_replyTo); + message.Header.ContentType.Should().Be(_contentType); + message.Header.HandledCount.Should().Be(0); + message.Header.Subject.Should().Be(_message.Header.Subject); + // allow for clock drift in the following test, more important to have a contemporary timestamp than anything + message.Header.TimeStamp.Should().BeAfter(RoundToSeconds(DateTime.UtcNow.AddMinutes(-1))); + message.Header.Delayed.Should().Be(TimeSpan.Zero); + // {"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} + message.Body.Value.Should().Be(_message.Body.Value); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _messageProducer.DisposeAsync(); + } + + private static DateTime RoundToSeconds(DateTime dateTime) + { + return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs new file mode 100644 index 0000000000..537b3728ca --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs @@ -0,0 +1,67 @@ +using System; +using System.Threading.Tasks; +using Amazon.SQS.Model; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +public class AWSAssumeQueuesTests : IDisposable, IAsyncDisposable +{ + private readonly ChannelFactory _channelFactory; + private readonly SqsMessageConsumer _consumer; + + public AWSAssumeQueuesTests() + { + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Assume + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //create the topic, we want the queue to be the issue + //We need to create the topic at least, to check the queues + var producer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create + }); + + producer.ConfirmTopicExistsAsync(topicName).Wait(); + + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + //We need to create the topic at least, to check the queues + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); + } + + [Fact] + public void When_queues_missing_assume_throws() + { + //we will try to get the queue url, and fail because it does not exist + Assert.Throws(() => _consumer.Receive(TimeSpan.FromMilliseconds(1000))); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + } + +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws_async.cs new file mode 100644 index 0000000000..c4ddbe33aa --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws_async.cs @@ -0,0 +1,66 @@ +using System; +using System.Threading.Tasks; +using Amazon.SQS.Model; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +public class AWSAssumeQueuesTestsAsync : IAsyncDisposable, IDisposable +{ + private readonly ChannelFactory _channelFactory; + private readonly IAmAMessageConsumerAsync _consumer; + + public AWSAssumeQueuesTestsAsync() + { + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + makeChannels: OnMissingChannel.Assume, + messagePumpType: MessagePumpType.Proactor + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //create the topic, we want the queue to be the issue + //We need to create the topic at least, to check the queues + var producer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create + }); + + producer.ConfirmTopicExistsAsync(topicName).Wait(); + + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateAsyncChannel(subscription); + + //We need to create the topic at least, to check the queues + _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); + } + + [Fact] + public async Task When_queues_missing_assume_throws_async() + { + //we will try to get the queue url, and fail because it does not exist + await Assert.ThrowsAsync(async () => await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(1000))); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs new file mode 100644 index 0000000000..4e93c294fc --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading.Tasks; +using Amazon.SQS.Model; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +public class AWSValidateQueuesTests : IDisposable, IAsyncDisposable +{ + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; + private ChannelFactory _channelFactory; + + public AWSValidateQueuesTests() + { + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + _subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Validate + ); + + _awsConnection = GatewayFactory.CreateFactory(); + + //We need to create the topic at least, to check the queues + var producer = new SnsMessageProducer(_awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create + }); + producer.ConfirmTopicExistsAsync(topicName).Wait(); + + } + + [Fact] + public void When_queues_missing_verify_throws() + { + //We have no queues so we should throw + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(_awsConnection); + Assert.Throws(() => _channelFactory.CreateSyncChannel(_subscription)); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws_async.cs new file mode 100644 index 0000000000..29f9b82662 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws_async.cs @@ -0,0 +1,55 @@ +using System; +using System.Threading.Tasks; +using Amazon.SQS.Model; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +public class AWSValidateQueuesTestsAsync : IAsyncDisposable +{ + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; + private ChannelFactory _channelFactory; + + public AWSValidateQueuesTestsAsync() + { + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + _subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + makeChannels: OnMissingChannel.Validate + ); + + _awsConnection = GatewayFactory.CreateFactory(); + + // We need to create the topic at least, to check the queues + var producer = new SnsMessageProducer(_awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create + }); + producer.ConfirmTopicExistsAsync(topicName).Wait(); + } + + [Fact] + public async Task When_queues_missing_verify_throws_async() + { + // We have no queues so we should throw + // We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(_awsConnection); + await Assert.ThrowsAsync(async () => await _channelFactory.CreateAsyncChannelAsync(_subscription)); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs new file mode 100644 index 0000000000..021910ece3 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsRawMessageDeliveryTests : IDisposable, IAsyncDisposable +{ + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly IAmAChannelSync _channel; + private readonly RoutingKey _routingKey; + + public SqsRawMessageDeliveryTests() + { + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + var channelName = $"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey($"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); + + var bufferSize = 10; + + //Set rawMessageDelivery to false + _channel = _channelFactory.CreateSyncChannel(new SqsSubscription( + name: new SubscriptionName(channelName), + channelName:new ChannelName(channelName), + routingKey:_routingKey, + bufferSize: bufferSize, + makeChannels: OnMissingChannel.Create, + messagePumpType: MessagePumpType.Reactor, + rawMessageDelivery: false)); + + _messageProducer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create + }); + } + + [Fact] + public void When_raw_message_delivery_disabled() + { + //arrange + var messageHeader = new MessageHeader( + Guid.NewGuid().ToString(), + _routingKey, + MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), + replyTo: RoutingKey.Empty, + contentType: "text\\plain"); + + var customHeaderItem = new KeyValuePair("custom-header-item", "custom-header-item-value"); + messageHeader.Bag.Add(customHeaderItem.Key, customHeaderItem.Value); + + var messageToSent = new Message(messageHeader, new MessageBody("test content one")); + + //act + _messageProducer.Send(messageToSent); + + var messageReceived = _channel.Receive(TimeSpan.FromMilliseconds(10000)); + + _channel.Acknowledge(messageReceived); + + //assert + messageReceived.Id.Should().Be(messageToSent.Id); + messageReceived.Header.Topic.Should().Be(messageToSent.Header.Topic); + messageReceived.Header.MessageType.Should().Be(messageToSent.Header.MessageType); + messageReceived.Header.CorrelationId.Should().Be(messageToSent.Header.CorrelationId); + messageReceived.Header.ReplyTo.Should().Be(messageToSent.Header.ReplyTo); + messageReceived.Header.ContentType.Should().Be(messageToSent.Header.ContentType); + messageReceived.Header.Bag.Should().ContainKey(customHeaderItem.Key).And.ContainValue(customHeaderItem.Value); + messageReceived.Body.Value.Should().Be(messageToSent.Body.Value); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled_async.cs new file mode 100644 index 0000000000..3821ed3960 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled_async.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsRawMessageDeliveryTestsAsync : IAsyncDisposable, IDisposable +{ + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly IAmAChannelAsync _channel; + private readonly RoutingKey _routingKey; + + public SqsRawMessageDeliveryTestsAsync() + { + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + var channelName = $"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey($"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); + + var bufferSize = 10; + + // Set rawMessageDelivery to false + _channel = _channelFactory.CreateAsyncChannel(new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: _routingKey, + bufferSize: bufferSize, + makeChannels: OnMissingChannel.Create, + rawMessageDelivery: false)); + + _messageProducer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create + }); + } + + [Fact] + public async Task When_raw_message_delivery_disabled_async() + { + // Arrange + var messageHeader = new MessageHeader( + Guid.NewGuid().ToString(), + _routingKey, + MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), + replyTo: RoutingKey.Empty, + contentType: "text\\plain"); + + var customHeaderItem = new KeyValuePair("custom-header-item", "custom-header-item-value"); + messageHeader.Bag.Add(customHeaderItem.Key, customHeaderItem.Value); + + var messageToSend = new Message(messageHeader, new MessageBody("test content one")); + + // Act + await _messageProducer.SendAsync(messageToSend); + + var messageReceived = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(10000)); + + await _channel.AcknowledgeAsync(messageReceived); + + // Assert + messageReceived.Id.Should().Be(messageToSend.Id); + messageReceived.Header.Topic.Should().Be(messageToSend.Header.Topic); + messageReceived.Header.MessageType.Should().Be(messageToSend.Header.MessageType); + messageReceived.Header.CorrelationId.Should().Be(messageToSend.Header.CorrelationId); + messageReceived.Header.ReplyTo.Should().Be(messageToSend.Header.ReplyTo); + messageReceived.Header.ContentType.Should().Be(messageToSend.Header.ContentType); + messageReceived.Header.Bag.Should().ContainKey(customHeaderItem.Key).And.ContainValue(customHeaderItem.Value); + messageReceived.Body.Value.Should().Be(messageToSend.Body.Value); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs new file mode 100644 index 0000000000..a331f51324 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs @@ -0,0 +1,87 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageConsumerRequeueTests : IDisposable +{ + private readonly Message _message; + private readonly IAmAChannelSync _channel; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public SqsMessageConsumerRequeueTests() + { + _myCommand = new MyCommand{Value = "Test"}; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + messagePumpType: MessagePumpType.Reactor, + routingKey: routingKey + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + ); + + //Must have credentials stored in the SDK Credentials store or shared credentials file + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); + + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); + } + + [Fact] + public void When_rejecting_a_message_through_gateway_with_requeue() + { + _messageProducer.Send(_message); + + var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + _channel.Reject(message); + + //Let the timeout change + Task.Delay(TimeSpan.FromMilliseconds(3000)); + + //should requeue_the_message + message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + //clear the queue + _channel.Acknowledge(message); + + message.Id.Should().Be(_myCommand.Id); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue_async.cs new file mode 100644 index 0000000000..dbc28850bc --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue_async.cs @@ -0,0 +1,86 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageConsumerRequeueTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly IAmAChannelAsync _channel; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public SqsMessageConsumerRequeueTestsAsync() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateAsyncChannel(subscription); + + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + } + + [Fact] + public async Task When_rejecting_a_message_through_gateway_with_requeue_async() + { + await _messageProducer.SendAsync(_message); + + var message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + await _channel.RejectAsync(message); + + // Let the timeout change + await Task.Delay(TimeSpan.FromMilliseconds(3000)); + + // should requeue_the_message + message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + // clear the queue + await _channel.AcknowledgeAsync(message); + + message.Id.Should().Be(_myCommand.Id); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs new file mode 100644 index 0000000000..fbf8a2b12b --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs @@ -0,0 +1,83 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.Runtime.CredentialManagement; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +public class SqsMessageProducerRequeueTests : IDisposable, IAsyncDisposable +{ + private readonly IAmAMessageProducerSync _sender; + private Message _requeuedMessage; + private Message _receivedMessage; + private readonly IAmAChannelSync _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; + + public SqsMessageProducerRequeueTests() + { + MyCommand myCommand = new MyCommand{Value = "Test"}; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object) myCommand, JsonSerialisationOptions.Options)) + ); + + //Must have credentials stored in the SDK Credentials store or shared credentials file + new CredentialProfileStoreChain(); + + var awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SnsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); + + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); + } + + [Fact] + public void When_requeueing_a_message() + { + _sender.Send(_message); + _receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(_receivedMessage); + + _requeuedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + //clear the queue + _channel.Acknowledge(_requeuedMessage ); + + _requeuedMessage.Body.Value.Should().Be(_receivedMessage.Body.Value); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message_async.cs new file mode 100644 index 0000000000..d470d2c8df --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message_async.cs @@ -0,0 +1,82 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.Runtime.CredentialManagement; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +public class SqsMessageProducerRequeueTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly IAmAMessageProducerAsync _sender; + private Message _requeuedMessage; + private Message _receivedMessage; + private readonly IAmAChannelAsync _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; + + public SqsMessageProducerRequeueTestsAsync() + { + MyCommand myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + new CredentialProfileStoreChain(); + + var awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SnsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateAsyncChannel(subscription); + } + + [Fact] + public async Task When_requeueing_a_message_async() + { + await _sender.SendAsync(_message); + _receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + await _channel.RequeueAsync(_receivedMessage); + + _requeuedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + await _channel.AcknowledgeAsync(_requeuedMessage); + + _requeuedMessage.Body.Value.Should().Be(_receivedMessage.Body.Value); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs new file mode 100644 index 0000000000..ffac75afca --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.SQS; +using Amazon.SQS.Model; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageProducerDlqTests : IDisposable, IAsyncDisposable +{ + private readonly SnsMessageProducer _sender; + private readonly IAmAChannelSync _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly string _dlqChannelName; + + public SqsMessageProducerDlqTests() + { + MyCommand myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _dlqChannelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + redrivePolicy: new RedrivePolicy(_dlqChannelName, 2) + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + //Must have credentials stored in the SDK Credentials store or shared credentials file + _awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SnsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + + _sender.ConfirmTopicExistsAsync(topicName).Wait(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); + } + + [Fact] + public void When_requeueing_redrives_to_the_queue() + { + _sender.Send(_message); + var receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(receivedMessage); + + receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(receivedMessage); + + //should force us into the dlq + receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(receivedMessage); + + Task.Delay(5000); + + //inspect the dlq + GetDLQCount(_dlqChannelName).Should().Be(1); + } + + private int GetDLQCount(string queueName) + { + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); + var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); + var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest + { + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageAttributeNames = new List { "All", "ApproximateReceiveCount" } + }).GetAwaiter().GetResult(); + + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new AmazonSQSException( + $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + } + + return response.Messages.Count; + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq_async.cs new file mode 100644 index 0000000000..aa532f3aca --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq_async.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.SQS; +using Amazon.SQS.Model; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageProducerDlqTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly SnsMessageProducer _sender; + private readonly IAmAChannelAsync _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly string _dlqChannelName; + + public SqsMessageProducerDlqTestsAsync() + { + MyCommand myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _dlqChannelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + redrivePolicy: new RedrivePolicy(_dlqChannelName, 2) + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + _awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SnsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + + _sender.ConfirmTopicExistsAsync(topicName).Wait(); + + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateAsyncChannel(subscription); + } + + [Fact] + public async Task When_requeueing_redrives_to_the_queue_async() + { + await _sender.SendAsync(_message); + var receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + await _channel.RequeueAsync(receivedMessage); + + receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + await _channel.RequeueAsync(receivedMessage); + + receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + await _channel.RequeueAsync(receivedMessage); + + await Task.Delay(5000); + + int dlqCount = await GetDLQCountAsync(_dlqChannelName); + dlqCount.Should().Be(1); + } + + private async Task GetDLQCountAsync(string queueName) + { + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); + var queueUrlResponse = await sqsClient.GetQueueUrlAsync(queueName); + var response = await sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest + { + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageAttributeNames = new List { "All", "ApproximateReceiveCount" } + }); + + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new AmazonSQSException( + $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + } + + return response.Messages.Count; + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs new file mode 100644 index 0000000000..5025b7aded --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.SQS; +using Amazon.SQS.Model; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Paramore.Brighter.ServiceActivator; +using Polly.Registry; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SnsReDrivePolicySDlqTests : IDisposable, IAsyncDisposable +{ + private readonly IAmAMessagePump _messagePump; + private readonly Message _message; + private readonly string _dlqChannelName; + private readonly IAmAChannelSync _channel; + private readonly SnsMessageProducer _sender; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; + private readonly ChannelFactory _channelFactory; + + public SnsReDrivePolicySDlqTests() + { + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _dlqChannelName = $"Redrive-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + //how are we consuming + _subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + //don't block the redrive policy from owning retry management + requeueCount: -1, + //delay before requeuing + requeueDelay: TimeSpan.FromMilliseconds(50), + messagePumpType: MessagePumpType.Reactor, + //we want our SNS subscription to manage requeue limits using the DLQ for 'too many requeues' + redrivePolicy: new RedrivePolicy + ( + deadLetterQueueName: new ChannelName(_dlqChannelName), + maxReceiveCount: 2 + )); + + //what do we send + var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + //Must have credentials stored in the SDK Credentials store or shared credentials file + _awsConnection = GatewayFactory.CreateFactory(); + + //how do we send to the queue + _sender = new SnsMessageProducer( + _awsConnection, + new SnsPublication + { + Topic = routingKey, RequestType = typeof(MyDeferredCommand), MakeChannels = OnMissingChannel.Create + } + ); + + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateSyncChannel(_subscription); + + //how do we handle a command + IHandleRequests handler = new MyDeferredCommandHandler(); + + //hook up routing for the command processor + var subscriberRegistry = new SubscriberRegistry(); + subscriberRegistry.Register(); + + //once we read, how do we dispatch to a handler. N.B. we don't use this for reading here + IAmACommandProcessor commandProcessor = new CommandProcessor( + subscriberRegistry: subscriberRegistry, + handlerFactory: new QuickHandlerFactory(() => handler), + requestContextFactory: new InMemoryRequestContextFactory(), + policyRegistry: new PolicyRegistry() + ); + var provider = new CommandProcessorProvider(commandProcessor); + + var messageMapperRegistry = new MessageMapperRegistry( + new SimpleMessageMapperFactory(_ => new MyDeferredCommandMessageMapper()), + null + ); + messageMapperRegistry.Register(); + + //pump messages from a channel to a handler - in essence we are building our own dispatcher in this test + _messagePump = new Reactor(provider, messageMapperRegistry, + null, new InMemoryRequestContextFactory(), _channel) + { + Channel = _channel, TimeOut = TimeSpan.FromMilliseconds(5000), RequeueCount = 3 + }; + } + + private int GetDLQCount(string queueName) + { + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); + var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); + var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest + { + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageSystemAttributeNames = ["ApproximateReceiveCount"], + MessageAttributeNames = new List { "All" } + }).GetAwaiter().GetResult(); + + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new AmazonSQSException( + $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + } + + return response.Messages.Count; + } + + + [Fact] + public async Task When_throwing_defer_action_respect_redrive() + { + //put something on an SNS topic, which will be delivered to our SQS queue + _sender.Send(_message); + + //start a message pump, let it process messages + var task = Task.Factory.StartNew(() => _messagePump.Run(), TaskCreationOptions.LongRunning); + await Task.Delay(5000); + + //send a quit message to the pump to terminate it + var quitMessage = MessageFactory.CreateQuitMessage(_subscription.RoutingKey); + _channel.Enqueue(quitMessage); + + //wait for the pump to stop once it gets a quit message + await Task.WhenAll(task); + + await Task.Delay(5000); + + //inspect the dlq + GetDLQCount(_dlqChannelName).Should().Be(1); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive_async.cs new file mode 100644 index 0000000000..25132a8e02 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive_async.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.SQS; +using Amazon.SQS.Model; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Paramore.Brighter.ServiceActivator; +using Polly.Registry; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SnsReDrivePolicySDlqTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly IAmAMessagePump _messagePump; + private readonly Message _message; + private readonly string _dlqChannelName; + private readonly IAmAChannelAsync _channel; + private readonly SnsMessageProducer _sender; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; + private readonly ChannelFactory _channelFactory; + + public SnsReDrivePolicySDlqTestsAsync() + { + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _dlqChannelName = $"Redrive-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + _subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + requeueCount: -1, + requeueDelay: TimeSpan.FromMilliseconds(50), + messagePumpType: MessagePumpType.Proactor, + redrivePolicy: new RedrivePolicy(new ChannelName(_dlqChannelName), 2) + ); + + var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + _awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SnsMessageProducer( + _awsConnection, + new SnsPublication + { + Topic = routingKey, + RequestType = typeof(MyDeferredCommand), + MakeChannels = OnMissingChannel.Create + } + ); + + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateAsyncChannel(_subscription); + + IHandleRequestsAsync handler = new MyDeferredCommandHandlerAsync(); + + var subscriberRegistry = new SubscriberRegistry(); + subscriberRegistry.RegisterAsync(); + + IAmACommandProcessor commandProcessor = new CommandProcessor( + subscriberRegistry: subscriberRegistry, + handlerFactory: new QuickHandlerFactoryAsync(() => handler), + requestContextFactory: new InMemoryRequestContextFactory(), + policyRegistry: new PolicyRegistry() + ); + var provider = new CommandProcessorProvider(commandProcessor); + + var messageMapperRegistry = new MessageMapperRegistry( + new SimpleMessageMapperFactory(_ => new MyDeferredCommandMessageMapper()), + null + ); + messageMapperRegistry.Register(); + + _messagePump = new Proactor(provider, messageMapperRegistry, + new EmptyMessageTransformerFactoryAsync(), new InMemoryRequestContextFactory(), _channel) + { + Channel = _channel, TimeOut = TimeSpan.FromMilliseconds(5000), RequeueCount = 3 + }; + } + + public async Task GetDLQCountAsync(string queueName) + { + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); + var queueUrlResponse = await sqsClient.GetQueueUrlAsync(queueName); + var response = await sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest + { + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageSystemAttributeNames = new List { "ApproximateReceiveCount" }, + MessageAttributeNames = new List { "All" } + }); + + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new AmazonSQSException($"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + } + + return response.Messages.Count; + } + + [Fact(Skip = "Failing async tests caused by task scheduler issues")] + public async Task When_throwing_defer_action_respect_redrive_async() + { + await _sender.SendAsync(_message); + + var task = Task.Factory.StartNew(() => _messagePump.Run(), TaskCreationOptions.LongRunning); + await Task.Delay(5000); + + var quitMessage = MessageFactory.CreateQuitMessage(_subscription.RoutingKey); + _channel.Enqueue(quitMessage); + + await Task.WhenAll(task); + + await Task.Delay(5000); + + int dlqCount = await GetDLQCountAsync(_dlqChannelName); + dlqCount.Should().Be(1); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs new file mode 100644 index 0000000000..76b286a948 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs @@ -0,0 +1,42 @@ +using System; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +public class AWSValidateMissingTopicTests +{ + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly RoutingKey _routingKey; + + public AWSValidateMissingTopicTests() + { + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey(topicName); + + _awsConnection = GatewayFactory.CreateFactory(); + + //Because we don't use channel factory to create the infrastructure -it won't exist + } + + [Theory] + [InlineData(SnsSqsType.Standard, null)] + [InlineData(SnsSqsType.Fifo, "123")] + public void When_topic_missing_verify_throws(SnsSqsType type, string partitionKey) + { + //arrange + var producer = new SnsMessageProducer(_awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Validate, + SnsType = type + }); + + //act && assert + Assert.Throws(() => producer.Send(new Message( + new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text") { PartitionKey = partitionKey}, + new MessageBody("Test")))); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws_async.cs new file mode 100644 index 0000000000..cae29a2f54 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws_async.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading.Tasks; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +public class AWSValidateMissingTopicTestsAsync +{ + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly RoutingKey _routingKey; + + public AWSValidateMissingTopicTestsAsync() + { + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey(topicName); + + _awsConnection = GatewayFactory.CreateFactory(); + + // Because we don't use channel factory to create the infrastructure - it won't exist + } + + [Fact] + public async Task When_topic_missing_verify_throws_async() + { + // arrange + var producer = new SnsMessageProducer(_awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Validate + }); + + // act & assert + await Assert.ThrowsAsync(async () => + await producer.SendAsync(new Message( + new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text"), + new MessageBody("Test")))); + } +} From 11ffdefb2ed4a4e17e4b0eeab7ff0dec1a137b13 Mon Sep 17 00:00:00 2001 From: Rafael Andrade Date: Fri, 3 Jan 2025 08:45:26 +0000 Subject: [PATCH 10/18] GH-1294 fixes DLQ, Get System Attribute & Unit tests --- .../ChannelFactory.cs | 48 +- .../HeaderNames.cs | 28 +- .../SnsMessageProducer.cs | 7 +- .../SqsInlineMessageCreator.cs | 425 +++++++++--------- .../SqsMessageConsumer.cs | 5 +- .../SqsMessageCreator.cs | 364 ++++++++------- .../SqsMessagePublisher.cs | 51 ++- .../When_infastructure_exists_can_assume.cs | 3 +- ...n_infastructure_exists_can_assume_async.cs | 4 +- ...ructure_exists_can_verify_by_convention.cs | 26 +- ..._infrastructure_exists_can_verify_async.cs | 185 ++++---- ...tructure_exists_can_verify_by_arn_async.cs | 25 +- ...ructure_exists_can_verify_by_convention.cs | 22 +- ...ing_a_message_via_the_messaging_gateway.cs | 31 +- ...message_via_the_messaging_gateway_async.cs | 31 +- .../Fifo/When_queues_missing_assume_throws.cs | 6 +- ...When_queues_missing_assume_throws_async.cs | 6 +- .../Fifo/When_queues_missing_verify_throws.cs | 6 +- ...When_queues_missing_verify_throws_async.cs | 6 +- .../When_raw_message_delivery_disabled.cs | 48 +- ...hen_raw_message_delivery_disabled_async.cs | 21 +- ..._a_message_through_gateway_with_requeue.cs | 21 +- ...sage_through_gateway_with_requeue_async.cs | 23 +- .../Fifo/When_requeueing_a_message.cs | 21 +- .../Fifo/When_requeueing_a_message_async.cs | 24 +- .../When_requeueing_redrives_to_the_dlq.cs | 30 +- ...en_requeueing_redrives_to_the_dlq_async.cs | 28 +- ...n_throwing_defer_action_respect_redrive.cs | 30 +- ...wing_defer_action_respect_redrive_async.cs | 26 +- .../Fifo/When_topic_missing_verify_throws.cs | 13 +- .../When_topic_missing_verify_throws_async.cs | 8 +- .../When_topic_missing_verify_throws.cs | 9 +- 32 files changed, 878 insertions(+), 703 deletions(-) diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs index 1ab5133e23..ba9361547e 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs @@ -73,8 +73,9 @@ public ChannelFactory(AWSMessagingGatewayConnection awsConnection) /// An SqsSubscription, the subscription parameter to create the channel with. /// An instance of . /// Thrown when the subscription is not an SqsSubscription. - public IAmAChannelSync CreateSyncChannel(Subscription subscription) => BrighterAsyncContext.Run(async () => await CreateSyncChannelAsync(subscription)); - + public IAmAChannelSync CreateSyncChannel(Subscription subscription) => + BrighterAsyncContext.Run(async () => await CreateSyncChannelAsync(subscription)); + /// /// Creates the input channel. /// @@ -84,7 +85,8 @@ public ChannelFactory(AWSMessagingGatewayConnection awsConnection) /// An SqsSubscription, the subscription parameter to create the channel with. /// An instance of . /// Thrown when the subscription is not an SqsSubscription. - public IAmAChannelAsync CreateAsyncChannel(Subscription subscription) => BrighterAsyncContext.Run(async () => await CreateAsyncChannelAsync(subscription)); + public IAmAChannelAsync CreateAsyncChannel(Subscription subscription) => + BrighterAsyncContext.Run(async () => await CreateAsyncChannelAsync(subscription)); /// /// Creates the input channel. @@ -122,7 +124,7 @@ await EnsureTopicAsync(_subscription.RoutingKey, return channel; } - + /// /// Deletes the queue. /// @@ -133,7 +135,8 @@ public async Task DeleteQueueAsync() using var sqsClient = new AWSClientFactory(AwsConnection).CreateSqsClient(); (bool exists, string? queueUrl) queueExists = - await QueueExistsAsync(sqsClient, _subscription.ChannelName.ToValidSQSQueueName(_subscription.SqsType == SnsSqsType.Fifo)); + await QueueExistsAsync(sqsClient, + _subscription.ChannelName.ToValidSQSQueueName(_subscription.SqsType == SnsSqsType.Fifo)); if (queueExists.exists && queueExists.queueUrl != null) { @@ -157,7 +160,7 @@ public async Task DeleteTopicAsync() { if (_subscription == null) return; - + if (ChannelTopicArn == null) return; @@ -176,7 +179,7 @@ public async Task DeleteTopicAsync() } } } - + private async Task CreateSyncChannelAsync(Subscription subscription) { var channel = await _retryPolicy.ExecuteAsync(async () => @@ -204,7 +207,7 @@ await EnsureTopicAsync(_subscription.RoutingKey, return channel; } - + private async Task EnsureQueueAsync() { if (_subscription is null) @@ -358,14 +361,35 @@ private async Task CreateQueueAsync(AmazonSQSClient sqsClient) private async Task CreateDLQAsync(AmazonSQSClient sqsClient) { if (_subscription is null) + { throw new InvalidOperationException("ChannelFactory: Subscription cannot be null"); + } if (_subscription.RedrivePolicy == null) + { throw new InvalidOperationException("ChannelFactory: RedrivePolicy cannot be null when creating a DLQ"); + } try { - var request = new CreateQueueRequest(_subscription.RedrivePolicy.DeadlLetterQueueName.Value); + var queue = _subscription.RedrivePolicy.DeadlLetterQueueName.Value; + var attributes = new Dictionary(); + if (_subscription.SqsType == SnsSqsType.Fifo) + { + if (!queue.EndsWith(".fifo")) + { + queue += ".fifo"; + } + + attributes.Add(QueueAttributeName.FifoQueue, "true"); + if (_subscription.ContentBasedDeduplication) + { + attributes.Add(QueueAttributeName.ContentBasedDeduplication, "true"); + } + } + + var request = new CreateQueueRequest(queue) { Attributes = attributes }; + var createDeadLetterQueueResponse = await sqsClient.CreateQueueAsync(request); var queueUrl = createDeadLetterQueueResponse.QueueUrl; @@ -375,11 +399,14 @@ private async Task CreateDLQAsync(AmazonSQSClient sqsClient) { QueueUrl = queueUrl, AttributeNames = ["QueueArn"] }; + var attributesResponse = await sqsClient.GetQueueAttributesAsync(attributesRequest); if (attributesResponse.HttpStatusCode != HttpStatusCode.OK) + { throw new InvalidOperationException( $"Could not find ARN of DLQ, status: {attributesResponse.HttpStatusCode}"); + } _dlqARN = attributesResponse.QueueARN; } @@ -461,7 +488,7 @@ private async Task SubscribeToTopicAsync(AmazonSQSClient sqsClient, AmazonSimple { if (string.IsNullOrEmpty(channelName)) return (false, null); - + bool exists = false; string? queueUrl = null; try @@ -482,6 +509,7 @@ private async Task SubscribeToTopicAsync(AmazonSQSClient sqsClient, AmazonSimple exists = false; return true; } + return false; }); } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/HeaderNames.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/HeaderNames.cs index 71488f567a..79bfd5c492 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/HeaderNames.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/HeaderNames.cs @@ -21,20 +21,18 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #endregion -namespace Paramore.Brighter.MessagingGateway.AWSSQS +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +public static class HeaderNames { - public static class HeaderNames - { - public static readonly string Id = "id"; - public static string Topic = "topic"; - public static string ContentType = "content-type"; - public static readonly string CorrelationId = "correlation-id"; - public static readonly string HandledCount = "handled-count"; - public static readonly string MessageType = "message-type"; - public static readonly string Timestamp = "timestamp"; - public static readonly string ReplyTo = "reply-to"; - public static string Bag = "bag"; - public const string MessageGroupId = "MessageGroupId"; - public const string DeduplicationId = "MessageDeduplicationId"; - } + public static readonly string Id = "id"; + public static string Topic = "topic"; + public static string ContentType = "content-type"; + public static readonly string CorrelationId = "correlation-id"; + public static readonly string HandledCount = "handled-count"; + public static readonly string MessageType = "message-type"; + public static readonly string Timestamp = "timestamp"; + public static readonly string ReplyTo = "reply-to"; + public static string Bag = "bag"; + public const string DeduplicationId = "messageDeduplicationId"; } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs index 546566ca0e..a7341722a2 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs @@ -142,12 +142,7 @@ public async Task SendAsync(Message message, CancellationToken cancellationToken /// Sync over Async /// /// The message. - public void Send(Message message) - { - // TODO: Uncomment when the BrighterSynchronizationHelper is fixed, today the code isn't working because it's stuck is wired infinity loop - // BrighterSynchronizationHelper.Run(async () => await SendWithDelayAsync(message, delay)); - SendAsync(message).GetAwaiter().GetResult(); - } + public void Send(Message message) => BrighterAsyncContext.Run(async () => await SendAsync(message)); /// /// Sends the specified message, with a delay. diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs index 528326c67e..7a8abb20d6 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2022 Ian Cooper @@ -19,298 +20,294 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + #endregion using System; using System.Collections.Generic; using System.Text.Json; +using Amazon.SQS; using Microsoft.Extensions.Logging; using Paramore.Brighter.Logging; -namespace Paramore.Brighter.MessagingGateway.AWSSQS +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +internal class SqsInlineMessageCreator : SqsMessageCreatorBase, ISqsMessageCreator { - internal class SqsInlineMessageCreator : SqsMessageCreatorBase, ISqsMessageCreator - { - private static readonly ILogger s_logger = ApplicationLogging.CreateLogger(); + private static readonly ILogger s_logger = ApplicationLogging.CreateLogger(); - private Dictionary _messageAttributes = new Dictionary(); + private Dictionary _messageAttributes = new(); - public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) + public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) + { + var topic = HeaderResult.Empty(); + var messageId = HeaderResult.Empty(); + + Message message; + try { - var topic = HeaderResult.Empty(); - var messageId = HeaderResult.Empty(); - var contentType = HeaderResult.Empty(); - var correlationId = HeaderResult.Empty(); - var handledCount = HeaderResult.Empty(); - var messageType = HeaderResult.Empty(); - var timeStamp = HeaderResult.Empty(); - var receiptHandle = HeaderResult.Empty(); - var replyTo = HeaderResult.Empty(); - var subject = HeaderResult.Empty(); - - Message message; - try + var jsonDocument = JsonDocument.Parse(sqsMessage.Body); + _messageAttributes = ReadMessageAttributes(jsonDocument); + + topic = ReadTopic(); + messageId = ReadMessageId(); + var contentType = ReadContentType(); + var correlationId = ReadCorrelationId(); + var handledCount = ReadHandledCount(); + var messageType = ReadMessageType(); + var timeStamp = ReadTimestamp(); + var replyTo = ReadReplyTo(); + var subject = ReadMessageSubject(jsonDocument); + var receiptHandle = ReadReceiptHandle(sqsMessage); + var partitionKey = ReadPartitionKey(sqsMessage); + var deduplicationId = ReadDeduplicationId(sqsMessage); + + //TODO:CLOUD_EVENTS parse from headers + + var messageHeader = new MessageHeader( + messageId: messageId.Result ?? string.Empty, + topic: topic.Result ?? RoutingKey.Empty, + messageType: messageType.Result, + source: null, + type: "", + timeStamp: timeStamp.Success ? timeStamp.Result : DateTime.UtcNow, + correlationId: correlationId.Success ? correlationId.Result : string.Empty, + replyTo: replyTo.Result is not null ? new RoutingKey(replyTo.Result) : RoutingKey.Empty, + contentType: contentType.Result ?? "plain/text", + handledCount: handledCount.Result, + dataSchema: null, + subject: subject.Result, + delayed: TimeSpan.Zero, + partitionKey: partitionKey.Result ?? string.Empty + ); + + message = new Message(messageHeader, ReadMessageBody(jsonDocument)); + + //deserialize the bag + var bag = ReadMessageBag(); + foreach (var key in bag.Keys) { - var jsonDocument = JsonDocument.Parse(sqsMessage.Body); - _messageAttributes = ReadMessageAttributes(jsonDocument); - - topic = ReadTopic(); - messageId = ReadMessageId(); - contentType = ReadContentType(); - correlationId = ReadCorrelationId(); - handledCount = ReadHandledCount(); - messageType = ReadMessageType(); - timeStamp = ReadTimestamp(); - replyTo = ReadReplyTo(); - subject = ReadMessageSubject(jsonDocument); - receiptHandle = ReadReceiptHandle(sqsMessage); - var partitionKey = ReadPartitionKey(); - var deduplicationId = ReadMessageDeduplicationId(); - - //TODO:CLOUD_EVENTS parse from headers - - var messageHeader = new MessageHeader( - messageId: messageId.Result ?? string.Empty, - topic: topic.Result ?? RoutingKey.Empty, - messageType: messageType.Result, - source: null, - type: "", - timeStamp: timeStamp.Success ? timeStamp.Result : DateTime.UtcNow, - correlationId: correlationId.Success ? correlationId.Result : string.Empty, - replyTo: replyTo.Result is not null ? new RoutingKey(replyTo.Result) : RoutingKey.Empty, - contentType: contentType.Result ?? "plain/text", - handledCount: handledCount.Result, - dataSchema: null, - subject: subject.Result, - delayed: TimeSpan.Zero, - partitionKey: partitionKey.Result ?? string.Empty - ); - - message = new Message(messageHeader, ReadMessageBody(jsonDocument)); - - //deserialize the bag - var bag = ReadMessageBag(); - foreach (var key in bag.Keys) - { - message.Header.Bag.Add(key, bag[key]); - } - - if (deduplicationId.Success) - { - message.Header.Bag.Add(HeaderNames.DeduplicationId, deduplicationId); - } + message.Header.Bag.Add(key, bag[key]); + } - if (receiptHandle.Success) - message.Header.Bag.Add("ReceiptHandle", sqsMessage.ReceiptHandle); + if (deduplicationId.Success) + { + message.Header.Bag[HeaderNames.DeduplicationId] = deduplicationId.Result; } - catch (Exception e) + + if (receiptHandle.Success) { - s_logger.LogWarning(e, "Failed to create message from Aws Sqs message"); - message = FailureMessage(topic, messageId); + message.Header.Bag.Add("ReceiptHandle", sqsMessage.ReceiptHandle); } - - return message; } - - private static Dictionary ReadMessageAttributes(JsonDocument jsonDocument) + catch (Exception e) { - var messageAttributes = new Dictionary(); + s_logger.LogWarning(e, "Failed to create message from Aws Sqs message"); + message = FailureMessage(topic, messageId); + } - try - { - if (jsonDocument.RootElement.TryGetProperty("MessageAttributes", out var attributes)) - { - messageAttributes = JsonSerializer.Deserialize>( - attributes.GetRawText(), - JsonSerialisationOptions.Options); - } - } - catch (Exception ex) - { - s_logger.LogWarning($"Failed while deserializing Sqs Message body, ex: {ex}"); - } + return message; + } - return messageAttributes ?? new Dictionary(); - } + private static Dictionary ReadMessageAttributes(JsonDocument jsonDocument) + { + var messageAttributes = new Dictionary(); - private HeaderResult ReadContentType() + try { - if (_messageAttributes.TryGetValue(HeaderNames.ContentType, out var contentType)) + if (jsonDocument.RootElement.TryGetProperty("MessageAttributes", out var attributes)) { - return new HeaderResult(contentType.GetValueInString(), true); + messageAttributes = JsonSerializer.Deserialize>( + attributes.GetRawText(), + JsonSerialisationOptions.Options); } - - return new HeaderResult(string.Empty, true); } - - private Dictionary ReadMessageBag() + catch (Exception ex) { - if (_messageAttributes.TryGetValue(HeaderNames.Bag, out var headerBag)) - { - try - { - var json = headerBag.GetValueInString(); - if (string.IsNullOrEmpty(json)) - return new Dictionary(); - - var bag = JsonSerializer.Deserialize>( - json!, - JsonSerialisationOptions.Options); - - return bag ?? new Dictionary(); - } - catch (Exception) - { - //suppress any errors in deserialization - } - } - - return new Dictionary(); + s_logger.LogWarning($"Failed while deserializing Sqs Message body, ex: {ex}"); } - private HeaderResult ReadReplyTo() - { - if (_messageAttributes.TryGetValue(HeaderNames.ReplyTo, out var replyTo)) - { - return new HeaderResult(replyTo.GetValueInString(), true); - } + return messageAttributes ?? new Dictionary(); + } - return new HeaderResult(string.Empty, true); + private HeaderResult ReadContentType() + { + if (_messageAttributes.TryGetValue(HeaderNames.ContentType, out var contentType)) + { + return new HeaderResult(contentType.GetValueInString(), true); } - private HeaderResult ReadTimestamp() + return new HeaderResult(string.Empty, true); + } + + private Dictionary ReadMessageBag() + { + if (_messageAttributes.TryGetValue(HeaderNames.Bag, out var headerBag)) { - if (_messageAttributes.TryGetValue(HeaderNames.Timestamp, out var timeStamp)) + try { - if (DateTime.TryParse(timeStamp.GetValueInString(), out var value)) + var json = headerBag.GetValueInString(); + if (string.IsNullOrEmpty(json)) { - return new HeaderResult(value, true); + return new Dictionary(); } - } - return new HeaderResult(DateTime.UtcNow, true); - } + var bag = JsonSerializer.Deserialize>(json!, + JsonSerialisationOptions.Options); - private HeaderResult ReadMessageType() - { - if (_messageAttributes.TryGetValue(HeaderNames.MessageType, out var messageType)) + return bag ?? new Dictionary(); + } + catch (Exception) { - if (Enum.TryParse(messageType.GetValueInString(), out MessageType value)) - { - return new HeaderResult(value, true); - } + //suppress any errors in deserialization } + } - return new HeaderResult(MessageType.MT_EVENT, true); + return new Dictionary(); + } + + private HeaderResult ReadReplyTo() + { + if (_messageAttributes.TryGetValue(HeaderNames.ReplyTo, out var replyTo)) + { + return new HeaderResult(replyTo.GetValueInString(), true); } - private HeaderResult ReadHandledCount() + return new HeaderResult(string.Empty, true); + } + + private HeaderResult ReadTimestamp() + { + if (_messageAttributes.TryGetValue(HeaderNames.Timestamp, out var timeStamp)) { - if (_messageAttributes.TryGetValue(HeaderNames.HandledCount, out var handledCount)) + if (DateTime.TryParse(timeStamp.GetValueInString(), out var value)) { - if (int.TryParse(handledCount.GetValueInString(), out var value)) - { - return new HeaderResult(value, true); - } + return new HeaderResult(value, true); } - - return new HeaderResult(0, true); } - private HeaderResult ReadCorrelationId() + return new HeaderResult(DateTime.UtcNow, true); + } + + private HeaderResult ReadMessageType() + { + if (_messageAttributes.TryGetValue(HeaderNames.MessageType, out var messageType)) { - if (_messageAttributes.TryGetValue(HeaderNames.CorrelationId, out var correlationId)) + if (Enum.TryParse(messageType.GetValueInString(), out MessageType value)) { - return new HeaderResult(correlationId.GetValueInString(), true); + return new HeaderResult(value, true); } - - return new HeaderResult(string.Empty, true); } - private HeaderResult ReadMessageId() + return new HeaderResult(MessageType.MT_EVENT, true); + } + + private HeaderResult ReadHandledCount() + { + if (_messageAttributes.TryGetValue(HeaderNames.HandledCount, out var handledCount)) { - if (_messageAttributes.TryGetValue(HeaderNames.Id, out var messageId)) + if (int.TryParse(handledCount.GetValueInString(), out var value)) { - return new HeaderResult(messageId.GetValueInString(), true); + return new HeaderResult(value, true); } - - return new HeaderResult(string.Empty, true); } - private HeaderResult ReadTopic() + return new HeaderResult(0, true); + } + + private HeaderResult ReadCorrelationId() + { + if (_messageAttributes.TryGetValue(HeaderNames.CorrelationId, out var correlationId)) { - if (_messageAttributes.TryGetValue(HeaderNames.Topic, out var topicArn)) - { - //we have an arn, and we want the topic - var s = topicArn.GetValueInString(); - if (string.IsNullOrEmpty(s)) - return new HeaderResult(RoutingKey.Empty, true); - - var arnElements = s!.Split(':'); - var topic = arnElements[(int)ARNAmazonSNS.TopicName]; - - return new HeaderResult(new RoutingKey(topic), true); - } + return new HeaderResult(correlationId.GetValueInString(), true); + } + + return new HeaderResult(string.Empty, true); + } - return new HeaderResult(RoutingKey.Empty, true); + private HeaderResult ReadMessageId() + { + if (_messageAttributes.TryGetValue(HeaderNames.Id, out var messageId)) + { + return new HeaderResult(messageId.GetValueInString(), true); } - private static HeaderResult ReadMessageSubject(JsonDocument jsonDocument) + return new HeaderResult(string.Empty, true); + } + + private HeaderResult ReadTopic() + { + if (_messageAttributes.TryGetValue(HeaderNames.Topic, out var topicArn)) { - try - { - if (jsonDocument.RootElement.TryGetProperty("Subject", out var value)) - { - return new HeaderResult(value.GetString(), true); - } - } - catch (Exception ex) - { - s_logger.LogWarning($"Failed to parse Sqs Message Body to valid Json Document, ex: {ex}"); - } + //we have an arn, and we want the topic + var s = topicArn.GetValueInString(); + if (string.IsNullOrEmpty(s)) + return new HeaderResult(RoutingKey.Empty, true); + + var arnElements = s!.Split(':'); + var topic = arnElements[(int)ARNAmazonSNS.TopicName]; - return new HeaderResult(null, true); + return new HeaderResult(new RoutingKey(topic), true); } - private static MessageBody ReadMessageBody(JsonDocument jsonDocument) + return new HeaderResult(RoutingKey.Empty, true); + } + + private static HeaderResult ReadMessageSubject(JsonDocument jsonDocument) + { + try { - try + if (jsonDocument.RootElement.TryGetProperty("Subject", out var value)) { - if (jsonDocument.RootElement.TryGetProperty("Message", out var value)) - { - return new MessageBody(value.GetString()); - } + return new HeaderResult(value.GetString(), true); } - catch (Exception ex) - { - s_logger.LogWarning($"Failed to parse Sqs Message Body to valid Json Document, ex: {ex}"); - } - - return new MessageBody(string.Empty); } + catch (Exception ex) + { + s_logger.LogWarning($"Failed to parse Sqs Message Body to valid Json Document, ex: {ex}"); + } + + return new HeaderResult(null, true); + } - private HeaderResult ReadPartitionKey() + private static MessageBody ReadMessageBody(JsonDocument jsonDocument) + { + try { - if (_messageAttributes.TryGetValue(HeaderNames.MessageGroupId, out var value)) + if (jsonDocument.RootElement.TryGetProperty("Message", out var value)) { - //we have an arn, and we want the topic - var messageGroupId = value.GetValueInString(); - return new HeaderResult(messageGroupId, true); + return new MessageBody(value.GetString()); } - - return new HeaderResult(string.Empty, true); + } + catch (Exception ex) + { + s_logger.LogWarning($"Failed to parse Sqs Message Body to valid Json Document, ex: {ex}"); } - private HeaderResult ReadMessageDeduplicationId() + return new MessageBody(string.Empty); + } + + private static HeaderResult ReadPartitionKey(Amazon.SQS.Model.Message sqsMessage) + { + if (sqsMessage.Attributes.TryGetValue(MessageSystemAttributeName.MessageGroupId, out var value)) { - if (_messageAttributes.TryGetValue(HeaderNames.DeduplicationId, out var value)) - { - //we have an arn, and we want the topic - var deduplicationId = value.GetValueInString(); - return new HeaderResult(deduplicationId, true); - } + //we have an arn, and we want the topic + var messageGroupId = value; + return new HeaderResult(messageGroupId, true); + } + + return new HeaderResult(string.Empty, false); + } - return new HeaderResult(string.Empty, true); + private static HeaderResult ReadDeduplicationId(Amazon.SQS.Model.Message sqsMessage) + { + if (sqsMessage.Attributes.TryGetValue(MessageSystemAttributeName.MessageDeduplicationId, out var value)) + { + //we have an arn, and we want the topic + var messageGroupId = value; + return new HeaderResult(messageGroupId, true); } + + return new HeaderResult(string.Empty, false); } } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageConsumer.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageConsumer.cs index 63dc0a95c9..161bed36d7 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageConsumer.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageConsumer.cs @@ -222,7 +222,8 @@ public async Task ReceiveAsync(TimeSpan? timeOut = null, { MaxNumberOfMessages = _batchSize, WaitTimeSeconds = timeOut.Value.Seconds, - MessageAttributeNames = new List { "All" }, + MessageAttributeNames = ["All"], + MessageSystemAttributeNames = ["All"] }; var receiveResponse = await client.ReceiveMessageAsync(request, cancellationToken); @@ -252,7 +253,7 @@ public async Task ReceiveAsync(TimeSpan? timeOut = null, if (sqsMessages.Length == 0) { - return new[] { _noopMessage }; + return [_noopMessage]; } var messages = new Message[sqsMessages.Length]; diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs index e91a2502ed..8e8275928d 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs @@ -26,246 +26,244 @@ THE SOFTWARE. */ using System; using System.Collections.Generic; using System.Text.Json; +using Amazon.SQS; using Amazon.SQS.Model; using Microsoft.Extensions.Logging; using Paramore.Brighter.Logging; using Paramore.Brighter.Transforms.Transformers; -namespace Paramore.Brighter.MessagingGateway.AWSSQS +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +//arn:aws:sns:us-east-1:123456789012:my_corporate_topic:02034b43-fefa-4e07-a5eb-3be56f8c54ce +//https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-arns +internal enum ARNAmazonSNS { - //arn:aws:sns:us-east-1:123456789012:my_corporate_topic:02034b43-fefa-4e07-a5eb-3be56f8c54ce - //https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-arns - internal enum ARNAmazonSNS - { - Arn = 0, - Aws = 1, - Sns = 2, - Region = 3, - AccountId = 4, - TopicName = 5, - SubscriptionId = 6 - } + Arn = 0, + Aws = 1, + Sns = 2, + Region = 3, + AccountId = 4, + TopicName = 5, + SubscriptionId = 6 +} + +internal class SqsMessageCreator : SqsMessageCreatorBase, ISqsMessageCreator +{ + private static readonly ILogger s_logger = ApplicationLogging.CreateLogger(); - internal class SqsMessageCreator : SqsMessageCreatorBase, ISqsMessageCreator + public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) { - private static readonly ILogger s_logger = ApplicationLogging.CreateLogger(); + var topic = HeaderResult.Empty(); + var messageId = HeaderResult.Empty(); + + //TODO:CLOUD_EVENTS parse from headers - public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) + Message message; + try { - var topic = HeaderResult.Empty(); - var messageId = HeaderResult.Empty(); - var contentType = HeaderResult.Empty(); - var correlationId = HeaderResult.Empty(); - var handledCount = HeaderResult.Empty(); - var messageType = HeaderResult.Empty(); - var timeStamp = HeaderResult.Empty(); - var receiptHandle = HeaderResult.Empty(); - var replyTo = HeaderResult.Empty(); - - //TODO:CLOUD_EVENTS parse from headers - - Message message; - try + topic = ReadTopic(sqsMessage); + messageId = ReadMessageId(sqsMessage); + var contentType = ReadContentType(sqsMessage); + var correlationId = ReadCorrelationId(sqsMessage); + var handledCount = ReadHandledCount(sqsMessage); + var messageType = ReadMessageType(sqsMessage); + var timeStamp = ReadTimestamp(sqsMessage); + var replyTo = ReadReplyTo(sqsMessage); + var receiptHandle = ReadReceiptHandle(sqsMessage); + var partitionKey = ReadPartitionKey(sqsMessage); + var deduplicationId = ReadDeduplicationId(sqsMessage); + + var bodyType = (contentType.Success ? contentType.Result : "plain/text"); + + var messageHeader = new MessageHeader( + messageId: messageId.Result ?? string.Empty, + topic: topic.Result ?? RoutingKey.Empty, + messageType.Result, + source: null, + type: string.Empty, + timeStamp: timeStamp.Success ? timeStamp.Result : DateTime.UtcNow, + correlationId: correlationId.Success ? correlationId.Result : string.Empty, + replyTo: replyTo.Success ? new RoutingKey(replyTo.Result!) : RoutingKey.Empty, + contentType: bodyType!, + handledCount: handledCount.Result, + dataSchema: null, + subject: null, + delayed: TimeSpan.Zero, + partitionKey: partitionKey.Success ? partitionKey.Result : string.Empty + ); + + message = new Message(messageHeader, ReadMessageBody(sqsMessage, bodyType!)); + + //deserialize the bag + var bag = ReadMessageBag(sqsMessage); + foreach (var key in bag.Keys) { - topic = ReadTopic(sqsMessage); - messageId = ReadMessageId(sqsMessage); - contentType = ReadContentType(sqsMessage); - correlationId = ReadCorrelationId(sqsMessage); - handledCount = ReadHandledCount(sqsMessage); - messageType = ReadMessageType(sqsMessage); - timeStamp = ReadTimestamp(sqsMessage); - replyTo = ReadReplyTo(sqsMessage); - receiptHandle = ReadReceiptHandle(sqsMessage); - var partitionKey = ReadPartitionKey(sqsMessage); - var deduplicationId = ReadMessageDeduplicationId(sqsMessage); - - var bodyType = (contentType.Success ? contentType.Result : "plain/text"); - - var messageHeader = new MessageHeader( - messageId: messageId.Result ?? string.Empty, - topic: topic.Result ?? RoutingKey.Empty, - messageType.Result, - source: null, - type: string.Empty, - timeStamp: timeStamp.Success ? timeStamp.Result : DateTime.UtcNow, - correlationId: correlationId.Success ? correlationId.Result : string.Empty, - replyTo: replyTo.Success ? new RoutingKey(replyTo.Result!) : RoutingKey.Empty, - contentType: bodyType!, - handledCount: handledCount.Result, - dataSchema: null, - subject: null, - delayed: TimeSpan.Zero, - partitionKey: partitionKey.Success ? partitionKey.Result : string.Empty - ); - - message = new Message(messageHeader, ReadMessageBody(sqsMessage, bodyType!)); - - //deserialize the bag - var bag = ReadMessageBag(sqsMessage); - foreach (var key in bag.Keys) - { - message.Header.Bag.Add(key, bag[key]); - } - - if (deduplicationId.Success) - { - bag.Add(HeaderNames.DeduplicationId, deduplicationId.Result); - } - - if (receiptHandle.Success) - message.Header.Bag.Add("ReceiptHandle", receiptHandle.Result); + message.Header.Bag.Add(key, bag[key]); } - catch (Exception e) + + if (deduplicationId.Success) { - s_logger.LogWarning(e, "Failed to create message from amqp message"); - message = FailureMessage(topic, messageId); + message.Header.Bag[HeaderNames.DeduplicationId] = deduplicationId.Result; } - - return message; - } - private static MessageBody ReadMessageBody(Amazon.SQS.Model.Message sqsMessage, string contentType) + if (receiptHandle.Success) + { + message.Header.Bag.Add("ReceiptHandle", receiptHandle.Result); + } + } + catch (Exception e) { - if(contentType == CompressPayloadTransformerAsync.GZIP - || contentType == CompressPayloadTransformerAsync.DEFLATE - || contentType == CompressPayloadTransformerAsync.BROTLI) - return new MessageBody(sqsMessage.Body, contentType, CharacterEncoding.Base64); - - return new MessageBody(sqsMessage.Body, contentType); + s_logger.LogWarning(e, "Failed to create message from amqp message"); + message = FailureMessage(topic, messageId); } - private static Dictionary ReadMessageBag(Amazon.SQS.Model.Message sqsMessage) - { - if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.Bag, out MessageAttributeValue? value)) - { - try - { - var bag = JsonSerializer.Deserialize>(value.StringValue, JsonSerialisationOptions.Options); - if (bag != null) - return bag; - } - catch (Exception) - { - //we weill just suppress conversion errors, and return an empty bag - } - } + return message; + } - return new Dictionary(); - } + private static MessageBody ReadMessageBody(Amazon.SQS.Model.Message sqsMessage, string contentType) + { + if (contentType == CompressPayloadTransformerAsync.GZIP + || contentType == CompressPayloadTransformerAsync.DEFLATE + || contentType == CompressPayloadTransformerAsync.BROTLI) + return new MessageBody(sqsMessage.Body, contentType, CharacterEncoding.Base64); - private static HeaderResult ReadReplyTo(Amazon.SQS.Model.Message sqsMessage) + return new MessageBody(sqsMessage.Body, contentType); + } + + private static Dictionary ReadMessageBag(Amazon.SQS.Model.Message sqsMessage) + { + if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.Bag, out MessageAttributeValue? value)) { - if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.ReplyTo, out MessageAttributeValue? value)) + try { - return new HeaderResult(value.StringValue, true); + var bag = JsonSerializer.Deserialize>(value.StringValue, + JsonSerialisationOptions.Options); + if (bag != null) + return bag; } - - return new HeaderResult(string.Empty, true); - } - - private static HeaderResult ReadTimestamp(Amazon.SQS.Model.Message sqsMessage) - { - if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.Timestamp, out MessageAttributeValue? value)) + catch (Exception) { - if (DateTime.TryParse(value.StringValue, out DateTime timestamp)) - { - return new HeaderResult(timestamp, true); - } + //we weill just suppress conversion errors, and return an empty bag } - - return new HeaderResult(DateTime.UtcNow, true); } - private static HeaderResult ReadMessageType(Amazon.SQS.Model.Message sqsMessage) - { - if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.MessageType, out MessageAttributeValue? value)) - { - if (Enum.TryParse(value.StringValue, out MessageType messageType)) - { - return new HeaderResult(messageType, true); - } - } + return new Dictionary(); + } - return new HeaderResult(MessageType.MT_EVENT, true); + private static HeaderResult ReadReplyTo(Amazon.SQS.Model.Message sqsMessage) + { + if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.ReplyTo, out MessageAttributeValue? value)) + { + return new HeaderResult(value.StringValue, true); } - private static HeaderResult ReadHandledCount(Amazon.SQS.Model.Message sqsMessage) + return new HeaderResult(string.Empty, true); + } + + private static HeaderResult ReadTimestamp(Amazon.SQS.Model.Message sqsMessage) + { + if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.Timestamp, out MessageAttributeValue? value)) { - if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.HandledCount, out MessageAttributeValue? value)) + if (DateTime.TryParse(value.StringValue, out DateTime timestamp)) { - if (int.TryParse(value.StringValue, out int handledCount)) - { - return new HeaderResult(handledCount, true); - } + return new HeaderResult(timestamp, true); } - - return new HeaderResult(0, true); } - private static HeaderResult ReadCorrelationId(Amazon.SQS.Model.Message sqsMessage) + return new HeaderResult(DateTime.UtcNow, true); + } + + private static HeaderResult ReadMessageType(Amazon.SQS.Model.Message sqsMessage) + { + if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.MessageType, out MessageAttributeValue? value)) { - if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.CorrelationId, out MessageAttributeValue? correlationId)) + if (Enum.TryParse(value.StringValue, out MessageType messageType)) { - return new HeaderResult(correlationId.StringValue, true); + return new HeaderResult(messageType, true); } - - return new HeaderResult(string.Empty, true); } - private static HeaderResult ReadContentType(Amazon.SQS.Model.Message sqsMessage) + return new HeaderResult(MessageType.MT_EVENT, true); + } + + private static HeaderResult ReadHandledCount(Amazon.SQS.Model.Message sqsMessage) + { + if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.HandledCount, out MessageAttributeValue? value)) { - if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.ContentType, out MessageAttributeValue? value)) + if (int.TryParse(value.StringValue, out int handledCount)) { - return new HeaderResult(value.StringValue, true); + return new HeaderResult(handledCount, true); } - - return new HeaderResult(string.Empty, true); } - private static HeaderResult ReadMessageId(Amazon.SQS.Model.Message sqsMessage) + return new HeaderResult(0, true); + } + + private static HeaderResult ReadCorrelationId(Amazon.SQS.Model.Message sqsMessage) + { + if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.CorrelationId, + out MessageAttributeValue? correlationId)) { - if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.Id, out MessageAttributeValue? value)) - { - return new HeaderResult(value.StringValue, true); - } - return new HeaderResult(string.Empty, true); + return new HeaderResult(correlationId.StringValue, true); } - private HeaderResult ReadTopic(Amazon.SQS.Model.Message sqsMessage) - { - if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.Topic, out MessageAttributeValue? value)) - { - //we have an arn, and we want the topic - var arnElements = value.StringValue.Split(':'); - var topic = arnElements[(int)ARNAmazonSNS.TopicName]; - return new HeaderResult(new RoutingKey(topic), true); - } + return new HeaderResult(string.Empty, true); + } - return new HeaderResult(RoutingKey.Empty, true); + private static HeaderResult ReadContentType(Amazon.SQS.Model.Message sqsMessage) + { + if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.ContentType, out MessageAttributeValue? value)) + { + return new HeaderResult(value.StringValue, true); } - private static HeaderResult ReadPartitionKey(Amazon.SQS.Model.Message sqsMessage) + return new HeaderResult(string.Empty, true); + } + + private static HeaderResult ReadMessageId(Amazon.SQS.Model.Message sqsMessage) + { + if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.Id, out MessageAttributeValue? value)) { - if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.MessageGroupId, out var value)) - { - //we have an arn, and we want the topic - var messageGroupId = value.StringValue; - return new HeaderResult(messageGroupId, true); - } + return new HeaderResult(value.StringValue, true); + } + + return new HeaderResult(string.Empty, true); + } - return new HeaderResult(string.Empty, true); + private HeaderResult ReadTopic(Amazon.SQS.Model.Message sqsMessage) + { + if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.Topic, out MessageAttributeValue? value)) + { + //we have an arn, and we want the topic + var arnElements = value.StringValue.Split(':'); + var topic = arnElements[(int)ARNAmazonSNS.TopicName]; + return new HeaderResult(new RoutingKey(topic), true); } - private static HeaderResult ReadMessageDeduplicationId(Amazon.SQS.Model.Message sqsMessage) + return new HeaderResult(RoutingKey.Empty, true); + } + + private static HeaderResult ReadPartitionKey(Amazon.SQS.Model.Message sqsMessage) + { + if (sqsMessage.Attributes.TryGetValue(MessageSystemAttributeName.MessageGroupId, out var value)) { - if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.DeduplicationId , out var value)) - { - //we have an arn, and we want the topic - var deduplicationId = value.StringValue; - return new HeaderResult(deduplicationId, true); - } + //we have an arn, and we want the topic + var messageGroupId = value; + return new HeaderResult(messageGroupId, true); + } - return new HeaderResult(string.Empty, true); + return new HeaderResult(null, false); + } + + private static HeaderResult ReadDeduplicationId(Amazon.SQS.Model.Message sqsMessage) + { + if (sqsMessage.Attributes.TryGetValue(MessageSystemAttributeName.MessageDeduplicationId, out var value)) + { + //we have an arn, and we want the topic + var messageGroupId = value; + return new HeaderResult(messageGroupId, true); } + + return new HeaderResult(null, false); } } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessagePublisher.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessagePublisher.cs index d1d0a4c1ff..efb6bac964 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessagePublisher.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessagePublisher.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2022 Ian Cooper @@ -19,6 +20,7 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + #endregion using System; @@ -37,7 +39,8 @@ public class SqsMessagePublisher private readonly SnsSqsType _snsSqsType; private readonly bool _deduplication; - public SqsMessagePublisher(string topicArn, AmazonSimpleNotificationServiceClient client, SnsSqsType snsSqsType, bool deduplication) + public SqsMessagePublisher(string topicArn, AmazonSimpleNotificationServiceClient client, SnsSqsType snsSqsType, + bool deduplication) { _topicArn = topicArn; _client = client; @@ -49,6 +52,25 @@ public SqsMessagePublisher(string topicArn, AmazonSimpleNotificationServiceClien { var messageString = message.Body.Value; var publishRequest = new PublishRequest(_topicArn, messageString, message.Header.Subject); + + var messageAttributes = new Dictionary + { + [HeaderNames.Id] = + new() { StringValue = Convert.ToString(message.Header.MessageId), DataType = "String" }, + [HeaderNames.Topic] = new() { StringValue = _topicArn, DataType = "String" }, + [HeaderNames.ContentType] = new() { StringValue = message.Header.ContentType, DataType = "String" }, + [HeaderNames.CorrelationId] = + new() { StringValue = Convert.ToString(message.Header.CorrelationId), DataType = "String" }, + [HeaderNames.HandledCount] = + new() { StringValue = Convert.ToString(message.Header.HandledCount), DataType = "String" }, + [HeaderNames.MessageType] = + new() { StringValue = message.Header.MessageType.ToString(), DataType = "String" }, + [HeaderNames.Timestamp] = new() + { + StringValue = Convert.ToString(message.Header.TimeStamp), DataType = "String" + } + }; + if (_snsSqsType == SnsSqsType.Fifo) { publishRequest.MessageGroupId = message.Header.PartitionKey; @@ -58,25 +80,24 @@ public SqsMessagePublisher(string topicArn, AmazonSimpleNotificationServiceClien } } - var messageAttributes = new Dictionary(); - messageAttributes.Add(HeaderNames.Id, new MessageAttributeValue{StringValue = Convert.ToString(message.Header.MessageId), DataType = "String"}); - messageAttributes.Add(HeaderNames.Topic, new MessageAttributeValue{StringValue = _topicArn, DataType = "String"}); - messageAttributes.Add(HeaderNames.ContentType, new MessageAttributeValue {StringValue = message.Header.ContentType, DataType = "String"}); - messageAttributes.Add(HeaderNames.CorrelationId, new MessageAttributeValue{StringValue = Convert.ToString(message.Header.CorrelationId), DataType = "String"}); - messageAttributes.Add(HeaderNames.HandledCount, new MessageAttributeValue {StringValue = Convert.ToString(message.Header.HandledCount), DataType = "String"}); - messageAttributes.Add(HeaderNames.MessageType, new MessageAttributeValue{StringValue = message.Header.MessageType.ToString(), DataType = "String"}); - messageAttributes.Add(HeaderNames.Timestamp, new MessageAttributeValue{StringValue = Convert.ToString(message.Header.TimeStamp), DataType = "String"}); if (!string.IsNullOrEmpty(message.Header.ReplyTo)) - messageAttributes.Add(HeaderNames.ReplyTo, new MessageAttributeValue{StringValue = Convert.ToString(message.Header.ReplyTo), DataType = "String"}); - + { + messageAttributes.Add(HeaderNames.ReplyTo, + new MessageAttributeValue + { + StringValue = Convert.ToString(message.Header.ReplyTo), DataType = "String" + }); + } + + //we can set up to 10 attributes; we have set 6 above, so use a single JSON object as the bag var bagJson = JsonSerializer.Serialize(message.Header.Bag, JsonSerialisationOptions.Options); - - messageAttributes.Add(HeaderNames.Bag, new MessageAttributeValue{StringValue = Convert.ToString(bagJson), DataType = "String"}); + messageAttributes[HeaderNames.Bag] = new() { StringValue = Convert.ToString(bagJson), DataType = "String" }; publishRequest.MessageAttributes = messageAttributes; - + var response = await _client.PublishAsync(publishRequest); - if (response.HttpStatusCode == System.Net.HttpStatusCode.OK || response.HttpStatusCode == System.Net.HttpStatusCode.Created || response.HttpStatusCode == System.Net.HttpStatusCode.Accepted) + if (response.HttpStatusCode is System.Net.HttpStatusCode.OK or System.Net.HttpStatusCode.Created + or System.Net.HttpStatusCode.Accepted) { return response.MessageId; } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs index 754d93a735..271307879e 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs @@ -37,8 +37,7 @@ public AWSAssumeInfrastructureTests() routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Create, - sqsType: SnsSqsType.Fifo, - contentBasedDeduplication: true); + sqsType: SnsSqsType.Fifo); _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume_async.cs index 768c658d56..c39d558dc7 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume_async.cs @@ -37,9 +37,7 @@ public AWSAssumeInfrastructureTestsAsync() routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, makeChannels: OnMissingChannel.Create, - sqsType: SnsSqsType.Fifo, - contentBasedDeduplication: true - ); + sqsType: SnsSqsType.Fifo); _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs index 64250242aa..90231abdd8 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs @@ -23,24 +23,26 @@ public class AWSValidateInfrastructureByConventionTests : IDisposable, IAsyncDis public AWSValidateInfrastructureByConventionTests() { _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; var routingKey = new RoutingKey(topicName); - SqsSubscription subscription = new( + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Create + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo ); _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); @@ -59,12 +61,18 @@ public AWSValidateInfrastructureByConventionTests() routingKey: routingKey, findTopicBy: TopicFindBy.Convention, messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Validate + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo ); _messageProducer = new SnsMessageProducer( awsConnection, - new SnsPublication { FindTopicBy = TopicFindBy.Convention, MakeChannels = OnMissingChannel.Validate } + new SnsPublication + { + FindTopicBy = TopicFindBy.Convention, + MakeChannels = OnMissingChannel.Validate, + SnsType = SnsSqsType.Fifo + } ); _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_async.cs index 714c0b5512..e75019302b 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_async.cs @@ -8,99 +8,102 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureTestsAsync : IDisposable, IAsyncDisposable { - [Trait("Category", "AWS")] - [Trait("Fragile", "CI")] - public class AWSValidateInfrastructureTestsAsync : IDisposable, IAsyncDisposable + private readonly Message _message; + private readonly IAmAMessageConsumerAsync _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureTestsAsync() { - private readonly Message _message; - private readonly IAmAMessageConsumerAsync _consumer; - private readonly SnsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly MyCommand _myCommand; - - public AWSValidateInfrastructureTestsAsync() - { - _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Create - ); - - _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) - ); - - var awsConnection = GatewayFactory.CreateFactory(); - - _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateAsyncChannel(subscription); - - subscription = new( - name: new SubscriptionName(channelName), - channelName: channel.Name, - routingKey: routingKey, - findTopicBy: TopicFindBy.Name, - messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Validate - ); - - _messageProducer = new SnsMessageProducer( - awsConnection, - new SnsPublication - { - FindTopicBy = TopicFindBy.Name, - MakeChannels = OnMissingChannel.Validate, - Topic = new RoutingKey(topicName) - } - ); - - _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); - } - - [Fact] - public async Task When_infrastructure_exists_can_verify_async() - { - await _messageProducer.SendAsync(_message); - - await Task.Delay(1000); - - var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - - var message = messages.First(); - message.Id.Should().Be(_myCommand.Id); - - await _consumer.AcknowledgeAsync(message); - } + _myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateAsyncChannel(subscription); + + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + findTopicBy: TopicFindBy.Name, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo + ); + + _messageProducer = new SnsMessageProducer( + awsConnection, + new SnsPublication + { + FindTopicBy = TopicFindBy.Name, + MakeChannels = OnMissingChannel.Validate, + Topic = new RoutingKey(topicName), + SnsType = SnsSqsType.Fifo + } + ); + + _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify_async() + { + await _messageProducer.SendAsync(_message); + + await Task.Delay(1000); + + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + await _consumer.AcknowledgeAsync(message); + } - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - ((IAmAMessageConsumerSync)_consumer).Dispose(); - _messageProducer.Dispose(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - await _consumer.DisposeAsync(); - await _messageProducer.DisposeAsync(); - } + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + ((IAmAMessageConsumerSync)_consumer).Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _consumer.DisposeAsync(); + await _messageProducer.DisposeAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_arn_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_arn_async.cs index f2ff4e08c4..60faf421d5 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_arn_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_arn_async.cs @@ -25,33 +25,34 @@ public class AWSValidateInfrastructureByArnTestsAsync : IAsyncDisposable, IDispo public AWSValidateInfrastructureByArnTestsAsync() { _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; var routingKey = new RoutingKey($"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); - SqsSubscription subscription = new( + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Create + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo ); _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = GatewayFactory.CreateFactory(credentials, region); + var awsConnection = GatewayFactory.CreateFactory(); _channelFactory = new ChannelFactory(awsConnection); var channel = _channelFactory.CreateAsyncChannel(subscription); - var topicArn = FindTopicArn(awsConnection, routingKey.Value).Result; + var topicArn = FindTopicArn(awsConnection, routingKey.ToValidSNSTopicName(true)).Result; var routingKeyArn = new RoutingKey(topicArn); subscription = new( @@ -59,7 +60,8 @@ public AWSValidateInfrastructureByArnTestsAsync() channelName: channel.Name, routingKey: routingKeyArn, findTopicBy: TopicFindBy.Arn, - makeChannels: OnMissingChannel.Validate + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo ); _messageProducer = new SnsMessageProducer( @@ -69,7 +71,8 @@ public AWSValidateInfrastructureByArnTestsAsync() Topic = routingKey, TopicArn = topicArn, FindTopicBy = TopicFindBy.Arn, - MakeChannels = OnMissingChannel.Validate + MakeChannels = OnMissingChannel.Validate, + SnsType = SnsSqsType.Fifo }); _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_convention.cs index c730ca6f21..e29ebcef46 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_convention.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_convention.cs @@ -23,24 +23,26 @@ public class AWSValidateInfrastructureByConventionTestsAsync : IAsyncDisposable, public AWSValidateInfrastructureByConventionTestsAsync() { _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; var routingKey = new RoutingKey(topicName); - SqsSubscription subscription = new( + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Create + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo ); _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); @@ -55,7 +57,8 @@ public AWSValidateInfrastructureByConventionTestsAsync() routingKey: routingKey, findTopicBy: TopicFindBy.Convention, messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Validate + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo ); _messageProducer = new SnsMessageProducer( @@ -63,7 +66,8 @@ public AWSValidateInfrastructureByConventionTestsAsync() new SnsPublication { FindTopicBy = TopicFindBy.Convention, - MakeChannels = OnMissingChannel.Validate + MakeChannels = OnMissingChannel.Validate, + SnsType = SnsSqsType.Fifo } ); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs index 83616bb8d4..195f34dd9f 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs @@ -21,6 +21,8 @@ public class SqsMessageProducerSendTests : IDisposable, IAsyncDisposable private readonly string _replyTo; private readonly string _contentType; private readonly string _topicName; + private readonly string _messageGroupId; + private readonly string _deduplicationId; public SqsMessageProducerSendTests() { @@ -28,21 +30,30 @@ public SqsMessageProducerSendTests() _correlationId = Guid.NewGuid().ToString(); _replyTo = "http:\\queueUrl"; _contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); _topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + _deduplicationId = $"DeduplicationId{Guid.NewGuid():N}"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(_topicName); - SqsSubscription subscription = new( + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, - rawMessageDelivery: false + rawMessageDelivery: false, + sqsType: SnsSqsType.Fifo ); _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: _correlationId, - replyTo: new RoutingKey(_replyTo), contentType: _contentType), + replyTo: new RoutingKey(_replyTo), contentType: _contentType, partitionKey: _messageGroupId) + { + Bag = + { + [HeaderNames.DeduplicationId] = _deduplicationId + } + }, new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) ); @@ -51,7 +62,13 @@ public SqsMessageProducerSendTests() _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateSyncChannel(subscription); - _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication{Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create}); + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication + { + Topic = new RoutingKey(_topicName), + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo, + Deduplication = true + }); } [Fact] @@ -85,6 +102,10 @@ public async Task When_posting_a_message_via_the_producer() message.Header.Delayed.Should().Be(TimeSpan.Zero); //{"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} message.Body.Value.Should().Be(_message.Body.Value); + + message.Header.PartitionKey.Should().Be(_messageGroupId); + message.Header.Bag.Should().ContainKey(HeaderNames.DeduplicationId); + message.Header.Bag[HeaderNames.DeduplicationId].Should().Be(_deduplicationId); } public void Dispose() diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway_async.cs index d06dde7a2b..0958d46a44 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway_async.cs @@ -21,6 +21,8 @@ public class SqsMessageProducerSendAsyncTests : IAsyncDisposable, IDisposable private readonly string _replyTo; private readonly string _contentType; private readonly string _topicName; + private readonly string _messageGroupId; + private readonly string _deduplicationId; public SqsMessageProducerSendAsyncTests() { @@ -28,21 +30,27 @@ public SqsMessageProducerSendAsyncTests() _correlationId = Guid.NewGuid().ToString(); _replyTo = "http:\\queueUrl"; _contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + _deduplicationId = $"DeduplicationId{Guid.NewGuid():N}"; _topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(_topicName); - SqsSubscription subscription = new( + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, - rawMessageDelivery: false + rawMessageDelivery: false, + sqsType: SnsSqsType.Fifo ); _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: _correlationId, - replyTo: new RoutingKey(_replyTo), contentType: _contentType), + replyTo: new RoutingKey(_replyTo), contentType: _contentType, partitionKey: _messageGroupId) + { + Bag = { [HeaderNames.DeduplicationId] = _deduplicationId } + }, new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); @@ -51,7 +59,14 @@ public SqsMessageProducerSendAsyncTests() _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateAsyncChannel(subscription); - _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create }); + _messageProducer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + Topic = new RoutingKey(_topicName), + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo, + Deduplication = true + }); } [Fact] @@ -85,8 +100,12 @@ public async Task When_posting_a_message_via_the_producer_async() message.Header.Delayed.Should().Be(TimeSpan.Zero); // {"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} message.Body.Value.Should().Be(_message.Body.Value); + + message.Header.PartitionKey.Should().Be(_messageGroupId); + message.Header.Bag.Should().ContainKey(HeaderNames.DeduplicationId); + message.Header.Bag[HeaderNames.DeduplicationId].Should().Be(_deduplicationId); } - + public void Dispose() { //Clean up resources that we have created diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs index 537b3728ca..ae3bf19364 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs @@ -25,7 +25,8 @@ public AWSAssumeQueuesTests() channelName: new ChannelName(channelName), routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Assume + makeChannels: OnMissingChannel.Assume, + sqsType: SnsSqsType.Fifo ); var awsConnection = GatewayFactory.CreateFactory(); @@ -35,7 +36,8 @@ public AWSAssumeQueuesTests() var producer = new SnsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo }); producer.ConfirmTopicExistsAsync(topicName).Wait(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws_async.cs index c4ddbe33aa..2b3211ff75 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws_async.cs @@ -25,7 +25,8 @@ public AWSAssumeQueuesTestsAsync() channelName: new ChannelName(channelName), routingKey: routingKey, makeChannels: OnMissingChannel.Assume, - messagePumpType: MessagePumpType.Proactor + messagePumpType: MessagePumpType.Proactor, + sqsType: SnsSqsType.Fifo ); var awsConnection = GatewayFactory.CreateFactory(); @@ -35,7 +36,8 @@ public AWSAssumeQueuesTestsAsync() var producer = new SnsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo }); producer.ConfirmTopicExistsAsync(topicName).Wait(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs index 4e93c294fc..dc329e6d64 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs @@ -26,7 +26,8 @@ public AWSValidateQueuesTests() channelName: new ChannelName(channelName), routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Validate + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo ); _awsConnection = GatewayFactory.CreateFactory(); @@ -35,7 +36,8 @@ public AWSValidateQueuesTests() var producer = new SnsMessageProducer(_awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo }); producer.ConfirmTopicExistsAsync(topicName).Wait(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws_async.cs index 29f9b82662..94087f0986 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws_async.cs @@ -25,7 +25,8 @@ public AWSValidateQueuesTestsAsync() name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, - makeChannels: OnMissingChannel.Validate + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo ); _awsConnection = GatewayFactory.CreateFactory(); @@ -34,7 +35,8 @@ public AWSValidateQueuesTestsAsync() var producer = new SnsMessageProducer(_awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo }); producer.ConfirmTopicExistsAsync(topicName).Wait(); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs index 021910ece3..d46619cf3c 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs @@ -9,7 +9,7 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; -[Trait("Category", "AWS")] +[Trait("Category", "AWS")] [Trait("Fragile", "CI")] public class SqsRawMessageDeliveryTests : IDisposable, IAsyncDisposable { @@ -31,17 +31,20 @@ public SqsRawMessageDeliveryTests() //Set rawMessageDelivery to false _channel = _channelFactory.CreateSyncChannel(new SqsSubscription( name: new SubscriptionName(channelName), - channelName:new ChannelName(channelName), - routingKey:_routingKey, + channelName: new ChannelName(channelName), + routingKey: _routingKey, bufferSize: bufferSize, makeChannels: OnMissingChannel.Create, messagePumpType: MessagePumpType.Reactor, - rawMessageDelivery: false)); + rawMessageDelivery: false, + sqsType: SnsSqsType.Fifo)); - _messageProducer = new SnsMessageProducer(awsConnection, + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo, + Deduplication = true }); } @@ -49,13 +52,22 @@ public SqsRawMessageDeliveryTests() public void When_raw_message_delivery_disabled() { //arrange + var messageGroupId = $"MessageGroupId{Guid.NewGuid():N}"; + var deduplicationId = $"DeduplicationId{Guid.NewGuid():N}"; var messageHeader = new MessageHeader( - Guid.NewGuid().ToString(), - _routingKey, - MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), - replyTo: RoutingKey.Empty, - contentType: "text\\plain"); + Guid.NewGuid().ToString(), + _routingKey, + MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), + replyTo: RoutingKey.Empty, + contentType: "text\\plain", + partitionKey: messageGroupId) + { + Bag = + { + [HeaderNames.DeduplicationId] = deduplicationId + } + }; var customHeaderItem = new KeyValuePair("custom-header-item", "custom-header-item-value"); messageHeader.Bag.Add(customHeaderItem.Key, customHeaderItem.Value); @@ -71,24 +83,28 @@ public void When_raw_message_delivery_disabled() //assert messageReceived.Id.Should().Be(messageToSent.Id); - messageReceived.Header.Topic.Should().Be(messageToSent.Header.Topic); + messageReceived.Header.Topic.Should().Be(messageToSent.Header.Topic.ToValidSNSTopicName(true)); messageReceived.Header.MessageType.Should().Be(messageToSent.Header.MessageType); messageReceived.Header.CorrelationId.Should().Be(messageToSent.Header.CorrelationId); messageReceived.Header.ReplyTo.Should().Be(messageToSent.Header.ReplyTo); messageReceived.Header.ContentType.Should().Be(messageToSent.Header.ContentType); messageReceived.Header.Bag.Should().ContainKey(customHeaderItem.Key).And.ContainValue(customHeaderItem.Value); messageReceived.Body.Value.Should().Be(messageToSent.Body.Value); + + messageReceived.Header.PartitionKey.Should().Be(messageGroupId); + messageReceived.Header.Bag.Should().ContainKey(HeaderNames.DeduplicationId); + messageReceived.Header.Bag[HeaderNames.DeduplicationId].Should().Be(deduplicationId); } public void Dispose() { - _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteTopicAsync().Wait(); _channelFactory.DeleteQueueAsync().Wait(); } - + public async ValueTask DisposeAsync() { - await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteTopicAsync(); await _channelFactory.DeleteQueueAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled_async.cs index 3821ed3960..84ac3c8ce0 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled_async.cs @@ -26,7 +26,7 @@ public SqsRawMessageDeliveryTestsAsync() var channelName = $"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45); _routingKey = new RoutingKey($"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); - var bufferSize = 10; + const int bufferSize = 10; // Set rawMessageDelivery to false _channel = _channelFactory.CreateAsyncChannel(new SqsSubscription( @@ -35,12 +35,13 @@ public SqsRawMessageDeliveryTestsAsync() routingKey: _routingKey, bufferSize: bufferSize, makeChannels: OnMissingChannel.Create, - rawMessageDelivery: false)); + rawMessageDelivery: false, + sqsType: SnsSqsType.Fifo)); _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create + MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo, Deduplication = true }); } @@ -48,13 +49,16 @@ public SqsRawMessageDeliveryTestsAsync() public async Task When_raw_message_delivery_disabled_async() { // Arrange + var messageGroupId = $"MessageGroupId{Guid.NewGuid():N}"; + var deduplicationId = $"DeduplicationId{Guid.NewGuid():N}"; var messageHeader = new MessageHeader( Guid.NewGuid().ToString(), _routingKey, MessageType.MT_COMMAND, correlationId: Guid.NewGuid().ToString(), replyTo: RoutingKey.Empty, - contentType: "text\\plain"); + contentType: "text\\plain", + partitionKey: messageGroupId) { Bag = { [HeaderNames.DeduplicationId] = deduplicationId } }; var customHeaderItem = new KeyValuePair("custom-header-item", "custom-header-item-value"); messageHeader.Bag.Add(customHeaderItem.Key, customHeaderItem.Value); @@ -70,18 +74,21 @@ public async Task When_raw_message_delivery_disabled_async() // Assert messageReceived.Id.Should().Be(messageToSend.Id); - messageReceived.Header.Topic.Should().Be(messageToSend.Header.Topic); + messageReceived.Header.Topic.Should().Be(messageToSend.Header.Topic.ToValidSNSTopicName(true)); messageReceived.Header.MessageType.Should().Be(messageToSend.Header.MessageType); messageReceived.Header.CorrelationId.Should().Be(messageToSend.Header.CorrelationId); messageReceived.Header.ReplyTo.Should().Be(messageToSend.Header.ReplyTo); messageReceived.Header.ContentType.Should().Be(messageToSend.Header.ContentType); messageReceived.Header.Bag.Should().ContainKey(customHeaderItem.Key).And.ContainValue(customHeaderItem.Value); messageReceived.Body.Value.Should().Be(messageToSend.Body.Value); + messageReceived.Header.PartitionKey.Should().Be(messageGroupId); + messageReceived.Header.Bag.Should().ContainKey(HeaderNames.DeduplicationId); + messageReceived.Header.Bag[HeaderNames.DeduplicationId].Should().Be(deduplicationId); } - + public void Dispose() { - _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteTopicAsync().Wait(); _channelFactory.DeleteQueueAsync().Wait(); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs index a331f51324..0ba3481f32 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs @@ -22,23 +22,25 @@ public class SqsMessageConsumerRequeueTests : IDisposable public SqsMessageConsumerRequeueTests() { _myCommand = new MyCommand{Value = "Test"}; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); var channelName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); SqsSubscription subscription = new( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), messagePumpType: MessagePumpType.Reactor, - routingKey: routingKey + routingKey: routingKey, + sqsType: SnsSqsType.Fifo ); _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) ); @@ -49,7 +51,12 @@ public SqsMessageConsumerRequeueTests() _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateSyncChannel(subscription); - _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); + _messageProducer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo + }); } [Fact] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue_async.cs index dbc28850bc..040e6e364a 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue_async.cs @@ -22,24 +22,26 @@ public class SqsMessageConsumerRequeueTestsAsync : IDisposable, IAsyncDisposable public SqsMessageConsumerRequeueTestsAsync() { _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); var channelName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - SqsSubscription subscription = new( + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Create + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo ); _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); @@ -48,7 +50,12 @@ public SqsMessageConsumerRequeueTestsAsync() _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateAsyncChannel(subscription); - _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + _messageProducer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo + }); } [Fact] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs index fbf8a2b12b..9a37c5a721 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs @@ -23,11 +23,12 @@ public class SqsMessageProducerRequeueTests : IDisposable, IAsyncDisposable public SqsMessageProducerRequeueTests() { MyCommand myCommand = new MyCommand{Value = "Test"}; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); var channelName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; var routingKey = new RoutingKey(topicName); var subscription = new SqsSubscription( @@ -38,16 +39,20 @@ public SqsMessageProducerRequeueTests() _message = new Message( new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), new MessageBody(JsonSerializer.Serialize((object) myCommand, JsonSerialisationOptions.Options)) ); //Must have credentials stored in the SDK Credentials store or shared credentials file - new CredentialProfileStoreChain(); - var awsConnection = GatewayFactory.CreateFactory(); - _sender = new SnsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); + _sender = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo, + Deduplication = true + }); //We need to do this manually in a test - will create the channel from subscriber parameters _channelFactory = new ChannelFactory(awsConnection); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message_async.cs index d470d2c8df..2eb04097cd 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message_async.cs @@ -23,11 +23,12 @@ public class SqsMessageProducerRequeueTestsAsync : IDisposable, IAsyncDisposable public SqsMessageProducerRequeueTestsAsync() { MyCommand myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); var channelName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; var routingKey = new RoutingKey(topicName); var subscription = new SqsSubscription( @@ -35,20 +36,25 @@ public SqsMessageProducerRequeueTestsAsync() channelName: new ChannelName(channelName), routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Create + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo ); _message = new Message( new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) ); - new CredentialProfileStoreChain(); - var awsConnection = GatewayFactory.CreateFactory(); - _sender = new SnsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + _sender = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo, + Deduplication = true + }); _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateAsyncChannel(subscription); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs index ffac75afca..ee02080d57 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs @@ -27,31 +27,39 @@ public class SqsMessageProducerDlqTests : IDisposable, IAsyncDisposable public SqsMessageProducerDlqTests() { MyCommand myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; _dlqChannelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var correlationId = Guid.NewGuid().ToString(); + var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; var routingKey = new RoutingKey(topicName); - SqsSubscription subscription = new SqsSubscription( + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, - redrivePolicy: new RedrivePolicy(_dlqChannelName, 2) + redrivePolicy: new RedrivePolicy(_dlqChannelName, 2), + sqsType: SnsSqsType.Fifo ); _message = new Message( new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) ); //Must have credentials stored in the SDK Credentials store or shared credentials file _awsConnection = GatewayFactory.CreateFactory(); - _sender = new SnsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + _sender = new SnsMessageProducer(_awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo, + Deduplication = true + }); _sender.ConfirmTopicExistsAsync(topicName).Wait(); @@ -77,7 +85,7 @@ public void When_requeueing_redrives_to_the_queue() Task.Delay(5000); //inspect the dlq - GetDLQCount(_dlqChannelName).Should().Be(1); + GetDLQCount(_dlqChannelName + ".fifo").Should().Be(1); } private int GetDLQCount(string queueName) @@ -88,7 +96,7 @@ private int GetDLQCount(string queueName) { QueueUrl = queueUrlResponse.QueueUrl, WaitTimeSeconds = 5, - MessageAttributeNames = new List { "All", "ApproximateReceiveCount" } + MessageAttributeNames = ["All", "ApproximateReceiveCount"] }).GetAwaiter().GetResult(); if (response.HttpStatusCode != HttpStatusCode.OK) diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq_async.cs index aa532f3aca..17ef2529dc 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq_async.cs @@ -27,31 +27,39 @@ public class SqsMessageProducerDlqTestsAsync : IDisposable, IAsyncDisposable public SqsMessageProducerDlqTestsAsync() { MyCommand myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; _dlqChannelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var correlationId = Guid.NewGuid().ToString(); + var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; var routingKey = new RoutingKey(topicName); - SqsSubscription subscription = new SqsSubscription( + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, - redrivePolicy: new RedrivePolicy(_dlqChannelName, 2) + redrivePolicy: new RedrivePolicy(_dlqChannelName, 2), + sqsType: SnsSqsType.Fifo ); _message = new Message( new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId ), new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) ); _awsConnection = GatewayFactory.CreateFactory(); - _sender = new SnsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + _sender = new SnsMessageProducer(_awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo, + Deduplication = true + }); _sender.ConfirmTopicExistsAsync(topicName).Wait(); @@ -74,7 +82,7 @@ public async Task When_requeueing_redrives_to_the_queue_async() await Task.Delay(5000); - int dlqCount = await GetDLQCountAsync(_dlqChannelName); + int dlqCount = await GetDLQCountAsync(_dlqChannelName + ".fifo"); dlqCount.Should().Be(1); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs index 5025b7aded..c42262f587 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs @@ -30,12 +30,13 @@ public class SnsReDrivePolicySDlqTests : IDisposable, IAsyncDisposable public SnsReDrivePolicySDlqTests() { - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; _dlqChannelName = $"Redrive-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var correlationId = Guid.NewGuid().ToString(); + var channelName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; var routingKey = new RoutingKey(topicName); //how are we consuming @@ -49,17 +50,14 @@ public SnsReDrivePolicySDlqTests() requeueDelay: TimeSpan.FromMilliseconds(50), messagePumpType: MessagePumpType.Reactor, //we want our SNS subscription to manage requeue limits using the DLQ for 'too many requeues' - redrivePolicy: new RedrivePolicy - ( - deadLetterQueueName: new ChannelName(_dlqChannelName), - maxReceiveCount: 2 - )); + redrivePolicy: new RedrivePolicy(new ChannelName(_dlqChannelName), 2), + sqsType: SnsSqsType.Fifo); //what do we send var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; _message = new Message( new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) ); @@ -71,7 +69,11 @@ public SnsReDrivePolicySDlqTests() _awsConnection, new SnsPublication { - Topic = routingKey, RequestType = typeof(MyDeferredCommand), MakeChannels = OnMissingChannel.Create + Topic = routingKey, + RequestType = typeof(MyDeferredCommand), + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo, + Deduplication = true } ); @@ -118,7 +120,7 @@ private int GetDLQCount(string queueName) QueueUrl = queueUrlResponse.QueueUrl, WaitTimeSeconds = 5, MessageSystemAttributeNames = ["ApproximateReceiveCount"], - MessageAttributeNames = new List { "All" } + MessageAttributeNames = ["All"] }).GetAwaiter().GetResult(); if (response.HttpStatusCode != HttpStatusCode.OK) @@ -151,7 +153,7 @@ public async Task When_throwing_defer_action_respect_redrive() await Task.Delay(5000); //inspect the dlq - GetDLQCount(_dlqChannelName).Should().Be(1); + GetDLQCount(_dlqChannelName + ".fifo").Should().Be(1); } public void Dispose() diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive_async.cs index 25132a8e02..54df071d3d 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive_async.cs @@ -30,12 +30,13 @@ public class SnsReDrivePolicySDlqTestsAsync : IDisposable, IAsyncDisposable public SnsReDrivePolicySDlqTestsAsync() { - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; _dlqChannelName = $"Redrive-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var correlationId = Guid.NewGuid().ToString(); + var channelName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; var routingKey = new RoutingKey(topicName); _subscription = new SqsSubscription( @@ -51,7 +52,7 @@ public SnsReDrivePolicySDlqTestsAsync() var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; _message = new Message( new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) ); @@ -63,7 +64,9 @@ public SnsReDrivePolicySDlqTestsAsync() { Topic = routingKey, RequestType = typeof(MyDeferredCommand), - MakeChannels = OnMissingChannel.Create + MakeChannels = OnMissingChannel.Create, + SnsType = SnsSqsType.Fifo, + Deduplication = true } ); @@ -92,7 +95,9 @@ public SnsReDrivePolicySDlqTestsAsync() _messagePump = new Proactor(provider, messageMapperRegistry, new EmptyMessageTransformerFactoryAsync(), new InMemoryRequestContextFactory(), _channel) { - Channel = _channel, TimeOut = TimeSpan.FromMilliseconds(5000), RequeueCount = 3 + Channel = _channel, + TimeOut = TimeSpan.FromMilliseconds(5000), + RequeueCount = 3 }; } @@ -110,7 +115,8 @@ public async Task GetDLQCountAsync(string queueName) if (response.HttpStatusCode != HttpStatusCode.OK) { - throw new AmazonSQSException($"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + throw new AmazonSQSException( + $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); } return response.Messages.Count; @@ -131,7 +137,7 @@ public async Task When_throwing_defer_action_respect_redrive_async() await Task.Delay(5000); - int dlqCount = await GetDLQCountAsync(_dlqChannelName); + var dlqCount = await GetDLQCountAsync(_dlqChannelName + ".fifo"); dlqCount.Should().Be(1); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs index 76b286a948..257119d332 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs @@ -21,22 +21,23 @@ public AWSValidateMissingTopicTests() //Because we don't use channel factory to create the infrastructure -it won't exist } - [Theory] - [InlineData(SnsSqsType.Standard, null)] - [InlineData(SnsSqsType.Fifo, "123")] - public void When_topic_missing_verify_throws(SnsSqsType type, string partitionKey) + [Fact] + public void When_topic_missing_verify_throws() { //arrange var producer = new SnsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Validate, - SnsType = type + SnsType = SnsSqsType.Fifo }); + + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; //act && assert Assert.Throws(() => producer.Send(new Message( - new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text") { PartitionKey = partitionKey}, + new MessageHeader("", _routingKey, MessageType.MT_EVENT, + type: "plain/text", partitionKey: messageGroupId), new MessageBody("Test")))); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws_async.cs index cae29a2f54..4e5a31af08 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws_async.cs @@ -29,13 +29,17 @@ public async Task When_topic_missing_verify_throws_async() var producer = new SnsMessageProducer(_awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Validate + MakeChannels = OnMissingChannel.Validate, + SnsType = SnsSqsType.Fifo }); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + // act & assert await Assert.ThrowsAsync(async () => await producer.SendAsync(new Message( - new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text"), + new MessageHeader("", _routingKey, MessageType.MT_EVENT, + type: "plain/text", partitionKey: messageGroupId), new MessageBody("Test")))); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs index 24a274b4fc..619bf9c5c7 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs @@ -23,22 +23,19 @@ public AWSValidateMissingTopicTests() //Because we don't use channel factory to create the infrastructure -it won't exist } - [Theory] - [InlineData(SnsSqsType.Standard, null)] - [InlineData(SnsSqsType.Fifo, "123")] - public void When_topic_missing_verify_throws(SnsSqsType type, string partitionKey) + [Fact] + public void When_topic_missing_verify_throws() { //arrange var producer = new SnsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Validate, - SnsType = type }); //act && assert Assert.Throws(() => producer.Send(new Message( - new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text") { PartitionKey = partitionKey}, + new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text"), new MessageBody("Test")))); } } From 75f431b2d1871090c981b87a1c59c4059c4816f4 Mon Sep 17 00:00:00 2001 From: Rafael Andrade Date: Fri, 3 Jan 2025 09:17:26 +0000 Subject: [PATCH 11/18] GH-1294 Improve Valid SQS/SNS name logic --- .../AWSNameExtensions.cs | 64 +++++++------------ .../SnsMessageProducer.cs | 2 +- ...agePublisher.cs => SnsMessagePublisher.cs} | 4 +- 3 files changed, 25 insertions(+), 45 deletions(-) rename src/Paramore.Brighter.MessagingGateway.AWSSQS/{SqsMessagePublisher.cs => SnsMessagePublisher.cs} (97%) diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSNameExtensions.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSNameExtensions.cs index 249bd6612b..e41174f22d 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSNameExtensions.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSNameExtensions.cs @@ -36,63 +36,43 @@ public static ChannelName ToValidSQSQueueName(this ChannelName? channelName, boo //SQS only allows 80 characters alphanumeric, hyphens, and underscores, but we might use a period in a //default typename strategy - var name = channelName.Value; - if (isFifo) + var queue = channelName.Value; + var maxLength = isFifo ? 75 : 80; + + queue = queue.Replace('.', '_'); + if (queue.Length > maxLength) { - if (name.EndsWith(".fifo")) - { - name = name.Substring(0, name.Length - 5); - } - - name = name.Replace(".", "_"); - if (name.Length > 75) - { - name = name.Substring(0, 75); - } - - name += ".fifo"; + queue = queue.Substring(0, maxLength); } - else + + if (isFifo) { - name = name.Replace(".", "_"); - if (name.Length > 80) - { - name = name.Substring(0, 80); - } + queue += ".fifo"; } - return new ChannelName(name); + return new ChannelName(queue); } public static RoutingKey ToValidSNSTopicName(this RoutingKey routingKey, bool isFifo = false) + => new(routingKey.Value.ToValidSNSTopicName(isFifo)); + + public static string ToValidSNSTopicName(this string topic, bool isFifo = false) { //SNS only topic names are limited to 256 characters. Alphanumeric characters plus hyphens (-) and //underscores (_) are allowed. Topic names must be unique within an AWS account. - var topic = routingKey.Value; - if (isFifo) + var maxLength = isFifo ? 251 : 256; + + topic = topic.Replace('.', '_'); + if (topic.Length > maxLength) { - if (topic.EndsWith(".fifo")) - { - topic = topic.Substring(0, topic.Length - 5); - } - - topic = topic.Replace(".", "_"); - if(topic.Length > 251) - { - topic = topic.Substring(0, 251); - } - - topic += ".fifo"; + topic = topic.Substring(0, maxLength); } - else + + if (isFifo) { - topic = topic.Replace(".", "_"); - if (topic.Length > 256) - { - topic = topic.Substring(0, 256); - } + topic += ".fifo"; } - return new RoutingKey(topic); + return topic; } } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs index a7341722a2..d8ee3225cf 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs @@ -125,7 +125,7 @@ public async Task SendAsync(Message message, CancellationToken cancellationToken $"Failed to publish message with topic {message.Header.Topic} and id {message.Id} and message: {message.Body} as the topic does not exist"); using var client = _clientFactory.CreateSnsClient(); - var publisher = new SqsMessagePublisher(ChannelTopicArn!, client, _publication.SnsType, _publication.Deduplication); + var publisher = new SnsMessagePublisher(ChannelTopicArn!, client, _publication.SnsType, _publication.Deduplication); var messageId = await publisher.PublishAsync(message); if (messageId == null) diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessagePublisher.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessagePublisher.cs similarity index 97% rename from src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessagePublisher.cs rename to src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessagePublisher.cs index efb6bac964..716c6c7186 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessagePublisher.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessagePublisher.cs @@ -32,14 +32,14 @@ THE SOFTWARE. */ namespace Paramore.Brighter.MessagingGateway.AWSSQS; -public class SqsMessagePublisher +public class SnsMessagePublisher { private readonly string _topicArn; private readonly AmazonSimpleNotificationServiceClient _client; private readonly SnsSqsType _snsSqsType; private readonly bool _deduplication; - public SqsMessagePublisher(string topicArn, AmazonSimpleNotificationServiceClient client, SnsSqsType snsSqsType, + public SnsMessagePublisher(string topicArn, AmazonSimpleNotificationServiceClient client, SnsSqsType snsSqsType, bool deduplication) { _topicArn = topicArn; From 58a701e730ef078b93b60698932d38343f1d5cd1 Mon Sep 17 00:00:00 2001 From: Rafael Andrade Date: Fri, 3 Jan 2025 09:25:58 +0000 Subject: [PATCH 12/18] GH-1294 Improve Valid SQS/SNS name logic --- .../AWSNameExtensions.cs | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSNameExtensions.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSNameExtensions.cs index e41174f22d..df8b03493f 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSNameExtensions.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSNameExtensions.cs @@ -34,21 +34,7 @@ public static ChannelName ToValidSQSQueueName(this ChannelName? channelName, boo return new ChannelName(string.Empty); } - //SQS only allows 80 characters alphanumeric, hyphens, and underscores, but we might use a period in a - //default typename strategy - var queue = channelName.Value; - var maxLength = isFifo ? 75 : 80; - - queue = queue.Replace('.', '_'); - if (queue.Length > maxLength) - { - queue = queue.Substring(0, maxLength); - } - - if (isFifo) - { - queue += ".fifo"; - } + var queue = Truncate(channelName.Value, isFifo, 80); return new ChannelName(queue); } @@ -60,19 +46,29 @@ public static string ToValidSNSTopicName(this string topic, bool isFifo = false) { //SNS only topic names are limited to 256 characters. Alphanumeric characters plus hyphens (-) and //underscores (_) are allowed. Topic names must be unique within an AWS account. - var maxLength = isFifo ? 251 : 256; + return Truncate(topic, isFifo, 256); + } + + private static string Truncate(string name, bool isFifo, int maxLength) + { + maxLength = isFifo switch + { + true when name.EndsWith("fifo") => name.Length - 5, + true => maxLength - 5, + false => maxLength + }; - topic = topic.Replace('.', '_'); - if (topic.Length > maxLength) + if (name.Length > maxLength) { - topic = topic.Substring(0, maxLength); + name = name.Substring(0, maxLength); } + name = name.Replace('.', '_'); if (isFifo) { - topic += ".fifo"; + name += ".fifo"; } - return topic; + return name; } } From 4299f907cd029b9e93f4ea527a97ba41d651576b Mon Sep 17 00:00:00 2001 From: Rafael Andrade Date: Fri, 3 Jan 2025 17:00:20 +0000 Subject: [PATCH 13/18] Add support to SQS Publication and add FIFO tests --- .../AWSMessagingGateway.cs | 433 ++++++++++++++++-- .../AWSNameExtensions.cs | 10 +- .../ChannelFactory.cs | 358 +++------------ .../HeaderNames.cs | 21 +- .../{SqsMessage.cs => IValidateQueue.cs} | 19 +- .../QueueFindBy.cs | 13 + .../RoutingKeyType.cs | 17 + .../SnsAttributes.cs | 51 ++- .../SnsMessageProducer.cs | 34 +- .../SnsMessageProducerFactory.cs | 107 +++-- .../SnsMessagePublisher.cs | 7 +- .../SnsProducerRegistryFactory.cs | 71 +-- .../SnsPublication.cs | 37 -- .../SqsAttributes.cs | 100 ++++ .../SqsInlineMessageCreator.cs | 21 +- .../SqsMessageCreator.cs | 34 +- .../SqsMessageProducer.cs | 165 +++++++ .../SqsMessageProducerFactory.cs | 78 ++++ .../SqsMessageSender.cs | 96 ++++ .../SqsProducerRegistryFactory.cs | 73 +++ .../SqsPublication.cs | 26 ++ .../SqsSubscription.cs | 40 +- .../ValidateQueueByName.cs | 46 ++ .../ValidateQueueByUrl.cs | 41 ++ .../ValidateTopicByArnConvention.cs | 5 +- .../ValidateTopicByName.cs | 6 +- ..._consumer_reads_multiple_messages_async.cs | 5 +- ...n_infastructure_exists_can_assume_async.cs | 7 +- ..._infrastructure_exists_can_verify_async.cs | 6 +- ...tructure_exists_can_verify_by_arn_async.cs | 6 +- ...message_via_the_messaging_gateway_async.cs | 7 +- ...When_queues_missing_assume_throws_async.cs | 68 +++ ...When_queues_missing_verify_throws_async.cs | 57 +++ ...hen_raw_message_delivery_disabled_async.cs | 4 +- ...sage_through_gateway_with_requeue_async.cs | 7 +- .../When_requeueing_a_message_async.cs | 9 +- ...en_requeueing_redrives_to_the_dlq_async.cs | 8 +- ...wing_defer_action_respect_redrive_async.cs | 5 +- .../When_topic_missing_verify_throws_async.cs | 12 +- ...essage_consumer_reads_multiple_messages.cs | 5 +- .../When_infastructure_exists_can_assume.cs | 5 +- .../When_infastructure_exists_can_verify.cs | 4 +- ..._infastructure_exists_can_verify_by_arn.cs | 8 +- ...ructure_exists_can_verify_by_convention.cs | 6 +- ...ructure_exists_can_verify_by_convention.cs | 6 +- ...ing_a_message_via_the_messaging_gateway.cs | 42 +- .../When_queues_missing_assume_throws.cs | 67 +++ .../When_queues_missing_verify_throws.cs | 62 +++ .../When_raw_message_delivery_disabled.cs | 14 +- ..._a_message_through_gateway_with_requeue.cs | 31 +- .../Fifo/Reactor/When_requeueing_a_message.cs | 85 ++++ .../When_requeueing_redrives_to_the_dlq.cs | 7 +- ...n_throwing_defer_action_respect_redrive.cs | 12 +- .../When_topic_missing_verify_throws.cs | 8 +- ..._consumer_reads_multiple_messages_async.cs | 2 +- ...hen_customising_aws_client_config_async.cs | 2 +- ...n_infastructure_exists_can_assume_async.cs | 2 +- ..._infastructure_exists_can_verify_by_arn.cs | 3 +- ..._infrastructure_exists_can_verify_async.cs | 2 +- ...tructure_exists_can_verify_by_arn_async.cs | 3 +- ...message_via_the_messaging_gateway_async.cs | 2 +- ...When_queues_missing_assume_throws_async.cs | 2 +- ...When_queues_missing_verify_throws_async.cs | 2 +- ...hen_raw_message_delivery_disabled_async.cs | 4 +- ...sage_through_gateway_with_requeue_async.cs | 4 +- .../When_requeueing_a_message_async.cs | 4 +- ...en_requeueing_redrives_to_the_dlq_async.cs | 4 +- ...wing_defer_action_respect_redrive_async.cs | 4 +- .../When_topic_missing_verify_throws_async.cs | 4 +- ...essage_consumer_reads_multiple_messages.cs | 2 +- .../When_customising_aws_client_config.cs | 2 +- .../When_infastructure_exists_can_assume.cs | 2 +- .../When_infastructure_exists_can_verify.cs | 2 +- ...ructure_exists_can_verify_by_convention.cs | 2 +- ...ructure_exists_can_verify_by_convention.cs | 2 +- ...ing_a_message_via_the_messaging_gateway.cs | 2 +- .../When_queues_missing_assume_throws.cs | 2 +- .../When_queues_missing_verify_throws.cs | 2 +- .../When_raw_message_delivery_disabled.cs | 4 +- ..._a_message_through_gateway_with_requeue.cs | 4 +- .../Reactor}/When_requeueing_a_message.cs | 4 +- .../When_requeueing_redrives_to_the_dlq.cs | 4 +- ...n_throwing_defer_action_respect_redrive.cs | 4 +- .../When_topic_missing_verify_throws.cs | 4 +- ..._consumer_reads_multiple_messages_async.cs | 152 ++++++ ...n_infastructure_exists_can_assume_async.cs | 103 +++++ ..._infrastructure_exists_can_verify_async.cs | 109 +++++ ...tructure_exists_can_verify_by_url_async.cs | 118 +++++ ...message_via_the_messaging_gateway_async.cs | 127 +++++ ...When_queues_missing_assume_throws_async.cs | 59 +++ ...When_queues_missing_verify_throws_async.cs | 50 ++ ...hen_raw_message_delivery_disabled_async.cs | 101 ++++ ...sage_through_gateway_with_requeue_async.cs | 92 ++++ .../When_requeueing_a_message_async.cs | 86 ++++ ...en_requeueing_redrives_to_the_dlq_async.cs | 117 +++++ ...wing_defer_action_respect_redrive_async.cs | 155 +++++++ .../When_topic_missing_verify_throws_async.cs | 46 ++ ...essage_consumer_reads_multiple_messages.cs | 160 +++++++ .../When_infastructure_exists_can_assume.cs | 103 +++++ .../When_infastructure_exists_can_verify.cs | 117 +++++ ..._infastructure_exists_can_verify_by_url.cs | 128 ++++++ ...ing_a_message_via_the_messaging_gateway.cs | 128 ++++++ .../When_queues_missing_assume_throws.cs | 67 +++ .../When_queues_missing_verify_throws.cs | 49 ++ ..._a_message_through_gateway_with_requeue.cs | 92 ++++ .../Fifo/Reactor/When_requeueing_a_message.cs | 86 ++++ .../When_requeueing_redrives_to_the_dlq.cs | 119 +++++ ...n_throwing_defer_action_respect_redrive.cs | 153 +++++++ .../When_topic_missing_verify_throws.cs | 45 ++ ..._consumer_reads_multiple_messages_async.cs | 131 ++++++ ...hen_customising_aws_client_config_async.cs | 95 ++++ ...n_infastructure_exists_can_assume_async.cs | 96 ++++ ..._infastructure_exists_can_verify_by_arn.cs | 125 +++++ ..._infrastructure_exists_can_verify_async.cs | 106 +++++ ...tructure_exists_can_verify_by_arn_async.cs | 116 +++++ ...message_via_the_messaging_gateway_async.cs | 109 +++++ ...When_queues_missing_assume_throws_async.cs | 8 +- ...When_queues_missing_verify_throws_async.cs | 8 +- ...hen_raw_message_delivery_disabled_async.cs | 93 ++++ ...sage_through_gateway_with_requeue_async.cs | 86 ++++ .../When_requeueing_a_message_async.cs | 82 ++++ ...en_requeueing_redrives_to_the_dlq_async.cs | 112 +++++ ...wing_defer_action_respect_redrive_async.cs | 149 ++++++ .../When_topic_missing_verify_throws_async.cs | 41 ++ ...essage_consumer_reads_multiple_messages.cs | 138 ++++++ .../When_customising_aws_client_config.cs | 93 ++++ .../When_infastructure_exists_can_assume.cs | 98 ++++ .../When_infastructure_exists_can_verify.cs | 112 +++++ ...ructure_exists_can_verify_by_convention.cs | 107 +++++ ...ructure_exists_can_verify_by_convention.cs | 104 +++++ ...ing_a_message_via_the_messaging_gateway.cs | 110 +++++ .../When_queues_missing_assume_throws.cs | 8 +- .../When_queues_missing_verify_throws.cs | 8 +- .../When_raw_message_delivery_disabled.cs | 94 ++++ ..._a_message_through_gateway_with_requeue.cs | 87 ++++ .../Reactor}/When_requeueing_a_message.cs | 23 +- .../When_requeueing_redrives_to_the_dlq.cs | 114 +++++ ...n_throwing_defer_action_respect_redrive.cs | 168 +++++++ .../When_topic_missing_verify_throws.cs | 39 ++ .../Paramore.Brighter.AWS.Tests.csproj | 4 + .../MyDeferredCommandHandlerAsync.cs | 6 +- .../TestDoubles/QuickHandlerFactoryAsync.cs | 5 +- 142 files changed, 7018 insertions(+), 777 deletions(-) rename src/Paramore.Brighter.MessagingGateway.AWSSQS/{SqsMessage.cs => IValidateQueue.cs} (78%) create mode 100644 src/Paramore.Brighter.MessagingGateway.AWSSQS/QueueFindBy.cs create mode 100644 src/Paramore.Brighter.MessagingGateway.AWSSQS/RoutingKeyType.cs create mode 100644 src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsAttributes.cs create mode 100644 src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs create mode 100644 src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducerFactory.cs create mode 100644 src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageSender.cs create mode 100644 src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsProducerRegistryFactory.cs create mode 100644 src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsPublication.cs create mode 100644 src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateQueueByName.cs create mode 100644 src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateQueueByUrl.cs rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Proactor}/When_a_message_consumer_reads_multiple_messages_async.cs (96%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Proactor}/When_infastructure_exists_can_assume_async.cs (93%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Proactor}/When_infrastructure_exists_can_verify_async.cs (96%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Proactor}/When_infrastructure_exists_can_verify_by_arn_async.cs (96%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Proactor}/When_posting_a_message_via_the_messaging_gateway_async.cs (96%) create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_queues_missing_assume_throws_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_queues_missing_verify_throws_async.cs rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Proactor}/When_raw_message_delivery_disabled_async.cs (95%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Proactor}/When_rejecting_a_message_through_gateway_with_requeue_async.cs (94%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Proactor}/When_requeueing_a_message_async.cs (90%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Proactor}/When_requeueing_redrives_to_the_dlq_async.cs (94%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Proactor}/When_throwing_defer_action_respect_redrive_async.cs (97%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Proactor}/When_topic_missing_verify_throws_async.cs (85%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Reactor}/When_a_message_consumer_reads_multiple_messages.cs (96%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Reactor}/When_infastructure_exists_can_assume.cs (95%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Reactor}/When_infastructure_exists_can_verify.cs (96%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Reactor}/When_infastructure_exists_can_verify_by_arn.cs (95%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Reactor}/When_infastructure_exists_can_verify_by_convention.cs (95%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Reactor}/When_infrastructure_exists_can_verify_by_convention.cs (96%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Reactor}/When_posting_a_message_via_the_messaging_gateway.cs (87%) create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_queues_missing_assume_throws.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_queues_missing_verify_throws.cs rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Reactor}/When_raw_message_delivery_disabled.cs (91%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Reactor}/When_rejecting_a_message_through_gateway_with_requeue.cs (84%) create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_requeueing_a_message.cs rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Reactor}/When_requeueing_redrives_to_the_dlq.cs (94%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Reactor}/When_throwing_defer_action_respect_redrive.cs (95%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sns/Fifo/Reactor}/When_topic_missing_verify_throws.cs (89%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Proactor}/When_a_message_consumer_reads_multiple_messages_async.cs (98%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Proactor}/When_customising_aws_client_config_async.cs (97%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Proactor}/When_infastructure_exists_can_assume_async.cs (97%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Proactor}/When_infastructure_exists_can_verify_by_arn.cs (97%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Proactor}/When_infrastructure_exists_can_verify_async.cs (98%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Proactor}/When_infrastructure_exists_can_verify_by_arn_async.cs (97%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Proactor}/When_posting_a_message_via_the_messaging_gateway_async.cs (98%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Proactor}/When_queues_missing_assume_throws_async.cs (96%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Proactor}/When_queues_missing_verify_throws_async.cs (96%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Proactor}/When_raw_message_delivery_disabled_async.cs (97%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Proactor}/When_rejecting_a_message_through_gateway_with_requeue_async.cs (96%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Proactor}/When_requeueing_a_message_async.cs (96%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Proactor}/When_requeueing_redrives_to_the_dlq_async.cs (97%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Proactor}/When_throwing_defer_action_respect_redrive_async.cs (98%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Proactor}/When_topic_missing_verify_throws_async.cs (92%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Reactor}/When_a_message_consumer_reads_multiple_messages.cs (98%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Reactor}/When_customising_aws_client_config.cs (97%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Reactor}/When_infastructure_exists_can_assume.cs (97%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Reactor}/When_infastructure_exists_can_verify.cs (98%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Reactor}/When_infastructure_exists_can_verify_by_convention.cs (98%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Reactor}/When_infrastructure_exists_can_verify_by_convention.cs (97%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Reactor}/When_posting_a_message_via_the_messaging_gateway.cs (98%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Reactor}/When_queues_missing_assume_throws.cs (96%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Reactor}/When_queues_missing_verify_throws.cs (96%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Reactor}/When_raw_message_delivery_disabled.cs (97%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Reactor}/When_rejecting_a_message_through_gateway_with_requeue.cs (96%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Reactor}/When_requeueing_a_message.cs (96%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Reactor}/When_requeueing_redrives_to_the_dlq.cs (97%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Reactor}/When_throwing_defer_action_respect_redrive.cs (98%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Standard => Sns/Standard/Reactor}/When_topic_missing_verify_throws.cs (92%) create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infastructure_exists_can_assume_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_queues_missing_assume_throws_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_queues_missing_verify_throws_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_raw_message_delivery_disabled_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_requeueing_a_message_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_requeueing_redrives_to_the_dlq_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_throwing_defer_action_respect_redrive_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_topic_missing_verify_throws_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_a_message_consumer_reads_multiple_messages.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_assume.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_verify.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_verify_by_url.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_posting_a_message_via_the_messaging_gateway.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_queues_missing_assume_throws.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_queues_missing_verify_throws.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_requeueing_a_message.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_requeueing_redrives_to_the_dlq.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_throwing_defer_action_respect_redrive.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_topic_missing_verify_throws.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_customising_aws_client_config_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_assume_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_verify_by_arn.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_by_arn_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sqs/Standard/Proactor}/When_queues_missing_assume_throws_async.cs (89%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sqs/Standard/Proactor}/When_queues_missing_verify_throws_async.cs (88%) create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_raw_message_delivery_disabled_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_a_message_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_redrives_to_the_dlq_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_throwing_defer_action_respect_redrive_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_topic_missing_verify_throws_async.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_a_message_consumer_reads_multiple_messages.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_customising_aws_client_config.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_assume.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify_by_convention.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infrastructure_exists_can_verify_by_convention.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_posting_a_message_via_the_messaging_gateway.cs rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sqs/Standard/Reactor}/When_queues_missing_assume_throws.cs (89%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sqs/Standard/Reactor}/When_queues_missing_verify_throws.cs (88%) create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_raw_message_delivery_disabled.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/{Fifo => Sqs/Standard/Reactor}/When_requeueing_a_message.cs (80%) create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_requeueing_redrives_to_the_dlq.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_throwing_defer_action_respect_redrive.cs create mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_topic_missing_verify_throws.cs diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs index 1328aa6948..0b6c6ea055 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs @@ -25,12 +25,18 @@ THE SOFTWARE. */ using System; using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Amazon.SimpleNotificationService; using Amazon.SimpleNotificationService.Model; using Amazon.SQS; +using Amazon.SQS.Model; using Microsoft.Extensions.Logging; using Paramore.Brighter.Logging; +using InvalidOperationException = System.InvalidOperationException; namespace Paramore.Brighter.MessagingGateway.AWSSQS; @@ -40,93 +46,428 @@ public class AWSMessagingGateway(AWSMessagingGatewayConnection awsConnection) private readonly AWSClientFactory _awsClientFactory = new(awsConnection); protected readonly AWSMessagingGatewayConnection AwsConnection = awsConnection; - protected string? ChannelTopicArn; + + /// + /// The Channel Address + /// The Channel Address can be a Topic ARN or Queue Url + /// + protected string? ChannelAddress => ChannelTopicArn ?? ChannelQueueUrl; + + /// + /// The Channel Topic Arn + /// + protected string? ChannelTopicArn { get; set; } + + /// + /// The Channel Queue URL + /// + protected string? ChannelQueueUrl { get; set; } + + /// + /// The Channel Dead Letter Queue ARN + /// + protected string? ChannelDeadLetterQueueArn { get; set; } protected async Task EnsureTopicAsync( RoutingKey topic, TopicFindBy topicFindBy, SnsAttributes? attributes, OnMissingChannel makeTopic = OnMissingChannel.Create, - SnsSqsType snsSqsType = SnsSqsType.Standard, - bool deduplication = false, CancellationToken cancellationToken = default) { - //on validate or assume, turn a routing key into a topicARN - if ((makeTopic == OnMissingChannel.Assume) || (makeTopic == OnMissingChannel.Validate)) - await ValidateTopicAsync(topic, topicFindBy, snsSqsType, cancellationToken); - else if (makeTopic == OnMissingChannel.Create) - await CreateTopicAsync(topic, attributes, snsSqsType, deduplication); + var type = attributes?.Type ?? SnsSqsType.Standard; + ChannelTopicArn = makeTopic switch + { + //on validate or assume, turn a routing key into a topicARN + OnMissingChannel.Assume or OnMissingChannel.Validate => await ValidateTopicAsync(topic, topicFindBy, type, + cancellationToken), + OnMissingChannel.Create => await CreateTopicAsync(topic, attributes), + _ => ChannelAddress + }; + return ChannelTopicArn; } - private async Task CreateTopicAsync(RoutingKey topicName, - SnsAttributes? snsAttributes, - SnsSqsType snsSqsType, - bool deduplication) + private async Task CreateTopicAsync(RoutingKey topic, SnsAttributes? snsAttributes) { using var snsClient = _awsClientFactory.CreateSnsClient(); + + + var topicName = topic.Value; var attributes = new Dictionary(); if (snsAttributes != null) { if (!string.IsNullOrEmpty(snsAttributes.DeliveryPolicy)) + { attributes.Add("DeliveryPolicy", snsAttributes.DeliveryPolicy); - if (!string.IsNullOrEmpty(snsAttributes.Policy)) attributes.Add("Policy", snsAttributes.Policy); - } + } - string name = topicName; - if (snsSqsType == SnsSqsType.Fifo) - { - if (!name.EndsWith(".fifo")) + if (!string.IsNullOrEmpty(snsAttributes.Policy)) { - name += ".fifo"; + attributes.Add("Policy", snsAttributes.Policy); } - attributes.Add("FifoTopic", "true"); - if (deduplication) + if (snsAttributes.Type == SnsSqsType.Fifo) { - attributes.Add("ContentBasedDeduplication", "true"); + topicName = topic.ToValidSNSTopicName(true); + attributes.Add("FifoTopic", "true"); + if (snsAttributes.ContentBasedDeduplication) + { + attributes.Add("ContentBasedDeduplication", "true"); + } } } - var createTopicRequest = new CreateTopicRequest(name) + var createTopicRequest = new CreateTopicRequest(topicName) { - Attributes = attributes, Tags = new List { new Tag { Key = "Source", Value = "Brighter" } } + Attributes = attributes, Tags = [new Tag { Key = "Source", Value = "Brighter" }] }; //create topic is idempotent, so safe to call even if topic already exists var createTopic = await snsClient.CreateTopicAsync(createTopicRequest); - if (!string.IsNullOrEmpty(createTopic.TopicArn)) - ChannelTopicArn = createTopic.TopicArn; - else - throw new InvalidOperationException($"Could not create Topic topic: {topicName} on {AwsConnection.Region}"); + { + return createTopic.TopicArn; + } + + throw new InvalidOperationException( + $"Could not create Topic topic: {topic} on {AwsConnection.Region}"); } - private async Task ValidateTopicAsync(RoutingKey topic, TopicFindBy findTopicBy, SnsSqsType snsSqsType, + private async Task ValidateTopicAsync(RoutingKey topic, TopicFindBy findTopicBy, SnsSqsType snsSqsType, CancellationToken cancellationToken = default) { - IValidateTopic topicValidationStrategy = GetTopicValidationStrategy(findTopicBy, snsSqsType); - (bool exists, string? topicArn) = await topicValidationStrategy.ValidateAsync(topic, cancellationToken); + var topicValidationStrategy = GetTopicValidationStrategy(findTopicBy, snsSqsType); + var (exists, topicArn) = await topicValidationStrategy.ValidateAsync(topic, cancellationToken); + if (exists) - ChannelTopicArn = topicArn; - else - throw new BrokerUnreachableException( - $"Topic validation error: could not find topic {topic}. Did you want Brighter to create infrastructure?"); + { + return topicArn; + } + + throw new BrokerUnreachableException( + $"Topic validation error: could not find topic {topic}. Did you want Brighter to create infrastructure?"); } private IValidateTopic GetTopicValidationStrategy(TopicFindBy findTopicBy, SnsSqsType type) + => findTopicBy switch + { + TopicFindBy.Arn => new ValidateTopicByArn(_awsClientFactory.CreateSnsClient()), + TopicFindBy.Name => new ValidateTopicByName(_awsClientFactory.CreateSnsClient(), type), + TopicFindBy.Convention => new ValidateTopicByArnConvention(AwsConnection.Credentials, + AwsConnection.Region, + AwsConnection.ClientConfigAction, + type), + _ => throw new ConfigurationException("Unknown TopicFindBy used to determine how to read RoutingKey") + }; + + + protected async Task EnsureQueueAsync( + string queue, + QueueFindBy queueFindBy, + SqsAttributes? sqsAttributes, + OnMissingChannel makeChannel = OnMissingChannel.Create, + CancellationToken cancellationToken = default) + { + var type = sqsAttributes?.Type ?? SnsSqsType.Standard; + ChannelQueueUrl = makeChannel switch + { + //on validate or assume, turn a routing key into a queueUrl + OnMissingChannel.Assume or OnMissingChannel.Validate => await ValidateQueueAsync(queue, queueFindBy, type, makeChannel, cancellationToken), + OnMissingChannel.Create => await CreateQueueAsync(queue, sqsAttributes, makeChannel, cancellationToken), + _ => ChannelQueueUrl + }; + + return ChannelQueueUrl; + } + + private async Task CreateQueueAsync( + string queueName, + SqsAttributes? sqsAttributes, + OnMissingChannel makeChannel, + CancellationToken cancellationToken) { - switch (findTopicBy) - { - case TopicFindBy.Arn: - return new ValidateTopicByArn(_awsClientFactory.CreateSnsClient()); - case TopicFindBy.Convention: - return new ValidateTopicByArnConvention(AwsConnection.Credentials, AwsConnection.Region, - AwsConnection.ClientConfigAction, type); - case TopicFindBy.Name: - return new ValidateTopicByName(_awsClientFactory.CreateSnsClient(), type); - default: - throw new ConfigurationException("Unknown TopicFindBy used to determine how to read RoutingKey"); + if (sqsAttributes?.RedrivePolicy != null) + { + ChannelDeadLetterQueueArn = await CreateDeadLetterQueueAsync(sqsAttributes, cancellationToken); + } + + using var sqsClient = _awsClientFactory.CreateSqsClient(); + + + var tags = new Dictionary { { "Source", "Brighter" } }; + var attributes = new Dictionary(); + if (sqsAttributes != null) + { + if (sqsAttributes.RedrivePolicy != null) + { + var policy = new + { + maxReceiveCount = sqsAttributes.RedrivePolicy.MaxReceiveCount, + deadLetterTargetArn = ChannelDeadLetterQueueArn + }; + + attributes.Add(QueueAttributeName.RedrivePolicy, + JsonSerializer.Serialize(policy, JsonSerialisationOptions.Options)); + } + + attributes.Add(QueueAttributeName.DelaySeconds, sqsAttributes.DelaySeconds.ToString()); + attributes.Add(QueueAttributeName.MessageRetentionPeriod, sqsAttributes.MessageRetentionPeriod.ToString()); + attributes.Add(QueueAttributeName.ReceiveMessageWaitTimeSeconds, sqsAttributes.TimeOut.Seconds.ToString()); + attributes.Add(QueueAttributeName.VisibilityTimeout, sqsAttributes.LockTimeout.ToString()); + if (sqsAttributes.IAMPolicy != null) + { + attributes.Add(QueueAttributeName.Policy, sqsAttributes.IAMPolicy); + } + + if (sqsAttributes.Tags != null) + { + foreach (var tag in sqsAttributes.Tags) + { + tags.Add(tag.Key, tag.Value); + } + } + + if (sqsAttributes.Type == SnsSqsType.Fifo) + { + queueName = queueName.ToValidSQSQueueName(true); + + attributes.Add(QueueAttributeName.FifoQueue, "true"); + if (sqsAttributes.ContentBasedDeduplication) + { + attributes.Add(QueueAttributeName.ContentBasedDeduplication, "true"); + } + + if (sqsAttributes is { DeduplicationScope: not null, FifoThroughputLimit: not null }) + { + attributes.Add(QueueAttributeName.FifoThroughputLimit, + sqsAttributes.FifoThroughputLimit.Value.ToString()); + attributes.Add(QueueAttributeName.DeduplicationScope, sqsAttributes.DeduplicationScope switch + { + DeduplicationScope.MessageGroup => "messageGroup", + _ => "queue" + }); + } + } + } + + string queueUrl; + var createQueueRequest = new CreateQueueRequest(queueName) { Attributes = attributes, Tags = tags }; + try + { + // create queue is idempotent, so safe to call even if queue already exists + var createQueueResponse = await sqsClient.CreateQueueAsync(createQueueRequest, cancellationToken); + queueUrl = createQueueResponse.QueueUrl; + } + catch (QueueNameExistsException) + { + var response = await sqsClient.GetQueueUrlAsync(queueName, cancellationToken); + queueUrl = response.QueueUrl; + } + + if (string.IsNullOrEmpty(queueUrl)) + { + throw new InvalidOperationException($"Could not create Queue queue: {queueName} on {AwsConnection.Region}"); + } + + if (sqsAttributes == null || sqsAttributes.RoutingKeyType == RoutingKeyType.PubSub) + { + using var snsClient = _awsClientFactory.CreateSnsClient(); + await CheckSubscriptionAsync(makeChannel, ChannelTopicArn!, queueUrl, sqsAttributes, sqsClient, snsClient); } + + return queueUrl; + } + + private async Task CreateDeadLetterQueueAsync( + SqsAttributes sqsAttributes, + CancellationToken cancellationToken) + { + using var sqsClient = _awsClientFactory.CreateSqsClient(); + + var queueName = sqsAttributes.RedrivePolicy!.DeadlLetterQueueName; + + var tags = new Dictionary { { "Source", "Brighter" } }; + var attributes = new Dictionary(); + if (sqsAttributes.Type == SnsSqsType.Fifo) + { + queueName = queueName.ToValidSQSQueueName(true); + + attributes.Add(QueueAttributeName.FifoQueue, "true"); + if (sqsAttributes.ContentBasedDeduplication) + { + attributes.Add(QueueAttributeName.ContentBasedDeduplication, "true"); + } + + if (sqsAttributes is { DeduplicationScope: not null, FifoThroughputLimit: not null }) + { + attributes.Add(QueueAttributeName.FifoThroughputLimit, + sqsAttributes.FifoThroughputLimit.Value.ToString()); + attributes.Add(QueueAttributeName.DeduplicationScope, sqsAttributes.DeduplicationScope switch + { + DeduplicationScope.MessageGroup => "messageGroup", + _ => "queue" + }); + } + } + + string queueUrl; + + try + { + var request = new CreateQueueRequest(queueName) { Attributes = attributes, Tags = tags }; + // create queue is idempotent, so safe to call even if queue already exists + var response = await sqsClient.CreateQueueAsync(request, cancellationToken); + + queueUrl = response.QueueUrl ?? throw new InvalidOperationException( + $"Could not find create DLQ, status: {response.HttpStatusCode}"); + } + catch (QueueNameExistsException) + { + var response = await sqsClient.GetQueueUrlAsync(queueName, cancellationToken); + queueUrl = response.QueueUrl; + } + + var attributesResponse = await sqsClient.GetQueueAttributesAsync( + new GetQueueAttributesRequest { QueueUrl = queueUrl, AttributeNames = [QueueAttributeName.QueueArn] }, + cancellationToken); + + if (attributesResponse.HttpStatusCode != HttpStatusCode.OK) + { + throw new InvalidOperationException( + $"Could not find ARN of DLQ, status: {attributesResponse.HttpStatusCode}"); + } + + return attributesResponse.QueueARN; + } + + private async Task ValidateQueueAsync(string queueName, + QueueFindBy findBy, + SnsSqsType type, + OnMissingChannel makeChannel, + CancellationToken cancellationToken) + { + var validationStrategy = GetQueueValidationStrategy(findBy, type); + var (exists, queueUrl) = await validationStrategy.ValidateAsync(queueName, cancellationToken); + + if (exists) + { + return queueUrl; + } + + if (makeChannel == OnMissingChannel.Assume) + { + return null; + } + + throw new QueueDoesNotExistException( + $"Queue validation error: could not find queue {queueName}. Did you want Brighter to create infrastructure?"); + } + + private IValidateQueue GetQueueValidationStrategy(QueueFindBy findQueueBy, SnsSqsType type) + => findQueueBy switch + { + QueueFindBy.Url => new ValidateQueueByUrl(_awsClientFactory.CreateSqsClient()), + QueueFindBy.Name => new ValidateQueueByName(_awsClientFactory.CreateSqsClient(), type), + _ => throw new ConfigurationException("Unknown TopicFindBy used to determine how to read RoutingKey") + }; + + private async Task CheckSubscriptionAsync(OnMissingChannel makeSubscriptions, + string topicArn, + string queueUrl, + SqsAttributes? sqsAttributes, + AmazonSQSClient sqsClient, + AmazonSimpleNotificationServiceClient snsClient) + { + if (makeSubscriptions == OnMissingChannel.Assume) + { + return; + } + + if (!await SubscriptionExistsAsync(topicArn, queueUrl, sqsClient, snsClient)) + { + if (makeSubscriptions == OnMissingChannel.Validate) + { + throw new BrokerUnreachableException( + $"Subscription validation error: could not find subscription for {queueUrl}"); + } + + if (makeSubscriptions == OnMissingChannel.Create) + { + await SubscribeToTopicAsync(topicArn, queueUrl, sqsAttributes, sqsClient, snsClient); + } + } + } + + private async Task SubscribeToTopicAsync( + string topicArn, + string queueUrl, + SqsAttributes? sqsAttributes, + AmazonSQSClient sqsClient, + AmazonSimpleNotificationServiceClient snsClient) + { + var arn = await snsClient.SubscribeQueueAsync(topicArn, sqsClient, queueUrl); + if (string.IsNullOrEmpty(arn)) + { + throw new InvalidOperationException( + $"Could not subscribe to topic: {topicArn} from queue: {queueUrl} in region {AwsConnection.Region}"); + } + + var response = await snsClient.SetSubscriptionAttributesAsync( + new SetSubscriptionAttributesRequest(arn, + "RawMessageDelivery", + sqsAttributes?.RawMessageDelivery.ToString()) + ); + + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new InvalidOperationException("Unable to set subscription attribute for raw message delivery"); + } + } + + private static async Task SubscriptionExistsAsync( + string topicArn, + string queueUrl, + AmazonSQSClient sqsClient, + AmazonSimpleNotificationServiceClient snsClient) + { + var queueArn = await GetQueueArnForChannelAsync(queueUrl, sqsClient); + + if (queueArn == null) + { + throw new BrokerUnreachableException($"Could not find queue ARN for queue {queueUrl}"); + } + + bool exists; + ListSubscriptionsByTopicResponse response; + do + { + response = await snsClient.ListSubscriptionsByTopicAsync( + new ListSubscriptionsByTopicRequest { TopicArn = topicArn }); + exists = response.Subscriptions.Any(sub => sub.Protocol.ToLower() == "sqs" && sub.Endpoint == queueArn); + } while (!exists && response.NextToken != null); + + return exists; + } + + /// + /// Gets the ARN of the queue for the channel. + /// Sync over async is used here; should be alright in context of channel creation. + /// + /// The queue url. + /// The SQS client. + /// The ARN of the queue. + private static async Task GetQueueArnForChannelAsync(string queueUrl, AmazonSQSClient sqsClient) + { + var result = await sqsClient.GetQueueAttributesAsync( + new GetQueueAttributesRequest { QueueUrl = queueUrl, AttributeNames = [QueueAttributeName.QueueArn] } + ); + + if (result.HttpStatusCode == HttpStatusCode.OK) + { + return result.QueueARN; + } + + return null; } } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSNameExtensions.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSNameExtensions.cs index df8b03493f..b5619b88a9 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSNameExtensions.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSNameExtensions.cs @@ -34,10 +34,14 @@ public static ChannelName ToValidSQSQueueName(this ChannelName? channelName, boo return new ChannelName(string.Empty); } - var queue = Truncate(channelName.Value, isFifo, 80); - - return new ChannelName(queue); + return new ChannelName(ToValidSQSQueueName(channelName.Value, isFifo)); } + + public static RoutingKey ToValidSQSQueueName(this RoutingKey routingKey, bool isFifo = false) + => new(ToValidSQSQueueName(routingKey.Value, isFifo)); + + public static string ToValidSQSQueueName(this string queue, bool isFifo = false) + => Truncate(queue, isFifo, 80); public static RoutingKey ToValidSNSTopicName(this RoutingKey routingKey, bool isFifo = false) => new(routingKey.Value.ToValidSNSTopicName(isFifo)); diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs index ba9361547e..fa0b718a06 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs @@ -43,14 +43,12 @@ THE SOFTWARE. */ namespace Paramore.Brighter.MessagingGateway.AWSSQS; /// -/// The class is responsible for creating and managing SQS channels. +/// The class is responsible for creating and managing SNS/SQS channels. /// public class ChannelFactory : AWSMessagingGateway, IAmAChannelFactory { private readonly SqsMessageConsumerFactory _messageConsumerFactory; private SqsSubscription? _subscription; - private string? _queueUrl; - private string? _dlqARN; private readonly AsyncRetryPolicy _retryPolicy; /// @@ -63,7 +61,7 @@ public ChannelFactory(AWSMessagingGatewayConnection awsConnection) _messageConsumerFactory = new SqsMessageConsumerFactory(awsConnection); _retryPolicy = Policy .Handle() - .WaitAndRetryAsync(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) }); + .WaitAndRetryAsync([TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10)]); } /// @@ -105,18 +103,33 @@ public async Task CreateAsyncChannelAsync(Subscription subscri throw new ConfigurationException( "We expect an SqsSubscription or SqsSubscription as a parameter"); - await EnsureTopicAsync(_subscription.RoutingKey, - _subscription.FindTopicBy, - _subscription.SnsAttributes, + + var isFifo = _subscription.SqsType == SnsSqsType.Fifo; + var routingKey = _subscription.ChannelName.Value.ToValidSQSQueueName(isFifo); + if (_subscription.RoutingKeyType == RoutingKeyType.PubSub) + { + var snsAttributes = _subscription.SnsAttributes ?? new SnsAttributes(); + snsAttributes.Type = _subscription.SqsType; + + await EnsureTopicAsync(_subscription.RoutingKey, + _subscription.FindTopicBy, + snsAttributes, + _subscription.MakeChannels, + ct); + + routingKey = _subscription.RoutingKey.ToValidSNSTopicName(isFifo); + } + + await EnsureQueueAsync( + _subscription.ChannelName.Value, + _subscription.QueueFindBy, + SqsAttributes.From(_subscription), _subscription.MakeChannels, - _subscription.SqsType, - _subscription.ContentBasedDeduplication, ct); - await EnsureQueueAsync(); return new ChannelAsync( - subscription.ChannelName.ToValidSQSQueueName(_subscription.SqsType == SnsSqsType.Fifo), - subscription.RoutingKey.ToValidSNSTopicName(_subscription.SqsType == SnsSqsType.Fifo), + subscription.ChannelName.ToValidSQSQueueName(isFifo), + new RoutingKey(routingKey), _messageConsumerFactory.CreateAsync(subscription), subscription.BufferSize ); @@ -138,7 +151,7 @@ public async Task DeleteQueueAsync() await QueueExistsAsync(sqsClient, _subscription.ChannelName.ToValidSQSQueueName(_subscription.SqsType == SnsSqsType.Fifo)); - if (queueExists.exists && queueExists.queueUrl != null) + if (queueExists is { exists: true, queueUrl: not null }) { try { @@ -188,18 +201,31 @@ private async Task CreateSyncChannelAsync(Subscription subscrip _subscription = sqsSubscription ?? throw new ConfigurationException( "We expect an SqsSubscription or SqsSubscription as a parameter"); + var routingKey = _subscription.ChannelName.Value; - await EnsureTopicAsync(_subscription.RoutingKey, - _subscription.FindTopicBy, - _subscription.SnsAttributes, - _subscription.MakeChannels, - _subscription.SqsType, - _subscription.ContentBasedDeduplication); - await EnsureQueueAsync(); + var isFifo = _subscription.SqsType == SnsSqsType.Fifo; + if (_subscription.RoutingKeyType == RoutingKeyType.PubSub) + { + var snsAttributes = _subscription.SnsAttributes ?? new SnsAttributes(); + snsAttributes.Type = _subscription.SqsType; + + await EnsureTopicAsync(_subscription.RoutingKey, + _subscription.FindTopicBy, + snsAttributes, + _subscription.MakeChannels); + + routingKey = _subscription.RoutingKey.ToValidSNSTopicName(isFifo); + } + + await EnsureQueueAsync( + _subscription.ChannelName.Value, + _subscription.QueueFindBy, + SqsAttributes.From(_subscription), + _subscription.MakeChannels); return new Channel( - subscription.ChannelName.ToValidSQSQueueName(_subscription.SqsType == SnsSqsType.Fifo), - subscription.RoutingKey.ToValidSNSTopicName(_subscription.SqsType == SnsSqsType.Fifo), + subscription.ChannelName.ToValidSQSQueueName(isFifo), + new RoutingKey(routingKey), _messageConsumerFactory.Create(subscription), subscription.BufferSize ); @@ -208,283 +234,7 @@ await EnsureTopicAsync(_subscription.RoutingKey, return channel; } - private async Task EnsureQueueAsync() - { - if (_subscription is null) - throw new InvalidOperationException("ChannelFactory: Subscription cannot be null"); - - if (_subscription.MakeChannels == OnMissingChannel.Assume) - return; - - using var sqsClient = new AWSClientFactory(AwsConnection).CreateSqsClient(); - var queueName = _subscription.ChannelName.ToValidSQSQueueName(_subscription.SqsType == SnsSqsType.Fifo); - var topicName = _subscription.RoutingKey.ToValidSNSTopicName(_subscription.SqsType == SnsSqsType.Fifo); - - (bool exists, _) = await QueueExistsAsync(sqsClient, queueName); - if (!exists) - { - if (_subscription.MakeChannels == OnMissingChannel.Create) - { - if (_subscription.RedrivePolicy != null) - { - await CreateDLQAsync(sqsClient); - } - - await CreateQueueAsync(sqsClient); - } - else if (_subscription.MakeChannels == OnMissingChannel.Validate) - { - var message = $"Queue does not exist: {queueName} for {topicName} on {AwsConnection.Region}"; - s_logger.LogDebug("Queue does not exist: {ChannelName} for {Topic} on {Region}", queueName, topicName, - AwsConnection.Region); - throw new QueueDoesNotExistException(message); - } - } - else - { - s_logger.LogDebug("Queue exists: {ChannelName} subscribed to {Topic} on {Region}", queueName, topicName, - AwsConnection.Region); - } - } - - private async Task CreateQueueAsync(AmazonSQSClient sqsClient) - { - if (_subscription is null) - { - throw new InvalidOperationException("ChannelFactory: Subscription cannot be null"); - } - - s_logger.LogDebug("Queue does not exist, creating queue: {ChannelName} subscribed to {Topic} on {Region}", - _subscription.ChannelName.Value, _subscription.RoutingKey.Value, AwsConnection.Region); - _queueUrl = null; - try - { - var attributes = new Dictionary(); - if (_subscription.RedrivePolicy != null && _dlqARN != null) - { - var policy = new - { - maxReceiveCount = _subscription.RedrivePolicy.MaxReceiveCount, deadLetterTargetArn = _dlqARN - }; - attributes.Add(QueueAttributeName.RedrivePolicy, - JsonSerializer.Serialize(policy, JsonSerialisationOptions.Options)); - } - - attributes.Add(QueueAttributeName.DelaySeconds, _subscription.DelaySeconds.ToString()); - attributes.Add(QueueAttributeName.MessageRetentionPeriod, _subscription.MessageRetentionPeriod.ToString()); - if (_subscription.IAMPolicy != null) attributes.Add(QueueAttributeName.Policy, _subscription.IAMPolicy); - attributes.Add(QueueAttributeName.ReceiveMessageWaitTimeSeconds, _subscription.TimeOut.Seconds.ToString()); - attributes.Add(QueueAttributeName.VisibilityTimeout, _subscription.LockTimeout.ToString()); - - var tags = new Dictionary { { "Source", "Brighter" } }; - if (_subscription.Tags != null) - { - foreach (var tag in _subscription.Tags) - { - tags.Add(tag.Key, tag.Value); - } - } - - var queueName = _subscription.ChannelName.Value; - if (_subscription.SqsType == SnsSqsType.Fifo) - { - if (!queueName.EndsWith(".fifo")) - { - queueName += ".fifo"; - } - - attributes.Add(QueueAttributeName.FifoQueue, "true"); - - if (_subscription.ContentBasedDeduplication) - { - attributes.Add(QueueAttributeName.ContentBasedDeduplication, "true"); - } - - if (_subscription.DeduplicationScope.HasValue && _subscription.FifoThroughputLimit.HasValue) - { - attributes.Add(QueueAttributeName.FifoThroughputLimit, - _subscription.FifoThroughputLimit.Value.ToString()); - attributes.Add(QueueAttributeName.DeduplicationScope, _subscription.DeduplicationScope switch - { - DeduplicationScope.MessageGroup => "messageGroup", - _ => "queue" - }); - } - } - - - var request = new CreateQueueRequest(queueName) { Attributes = attributes, Tags = tags }; - var response = await sqsClient.CreateQueueAsync(request); - _queueUrl = response.QueueUrl; - - if (!string.IsNullOrEmpty(_queueUrl)) - { - s_logger.LogDebug("Queue created: {URL}", _queueUrl); - using var snsClient = new AWSClientFactory(AwsConnection).CreateSnsClient(); - await CheckSubscriptionAsync(_subscription.MakeChannels, sqsClient, snsClient); - } - else - { - throw new InvalidOperationException( - $"Could not create queue: {_subscription.ChannelName.Value} subscribed to {ChannelTopicArn} on {AwsConnection.Region}"); - } - } - catch (QueueDeletedRecentlyException ex) - { - var error = - $"Could not create queue {_subscription.ChannelName.Value} because {ex.Message} waiting 60s to retry"; - s_logger.LogError(ex, "Could not create queue {ChannelName} because {ErrorMessage} waiting 60s to retry", - _subscription.ChannelName.Value, ex.Message); - Thread.Sleep(TimeSpan.FromSeconds(30)); - throw new ChannelFailureException(error, ex); - } - catch (AmazonSQSException ex) - { - var error = - $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {AwsConnection.Region.DisplayName} because {ex.Message}"; - s_logger.LogError(ex, - "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", - _queueUrl, _subscription.RoutingKey.Value, AwsConnection.Region.DisplayName, ex.Message); - throw new InvalidOperationException(error, ex); - } - catch (HttpErrorResponseException ex) - { - var error = - $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {AwsConnection.Region.DisplayName} because {ex.Message}"; - s_logger.LogError(ex, - "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", - _queueUrl, _subscription.RoutingKey.Value, AwsConnection.Region.DisplayName, ex.Message); - throw new InvalidOperationException(error, ex); - } - } - - private async Task CreateDLQAsync(AmazonSQSClient sqsClient) - { - if (_subscription is null) - { - throw new InvalidOperationException("ChannelFactory: Subscription cannot be null"); - } - - if (_subscription.RedrivePolicy == null) - { - throw new InvalidOperationException("ChannelFactory: RedrivePolicy cannot be null when creating a DLQ"); - } - - try - { - var queue = _subscription.RedrivePolicy.DeadlLetterQueueName.Value; - var attributes = new Dictionary(); - if (_subscription.SqsType == SnsSqsType.Fifo) - { - if (!queue.EndsWith(".fifo")) - { - queue += ".fifo"; - } - - attributes.Add(QueueAttributeName.FifoQueue, "true"); - if (_subscription.ContentBasedDeduplication) - { - attributes.Add(QueueAttributeName.ContentBasedDeduplication, "true"); - } - } - - var request = new CreateQueueRequest(queue) { Attributes = attributes }; - - var createDeadLetterQueueResponse = await sqsClient.CreateQueueAsync(request); - var queueUrl = createDeadLetterQueueResponse.QueueUrl; - - if (!string.IsNullOrEmpty(queueUrl)) - { - var attributesRequest = new GetQueueAttributesRequest - { - QueueUrl = queueUrl, AttributeNames = ["QueueArn"] - }; - - var attributesResponse = await sqsClient.GetQueueAttributesAsync(attributesRequest); - - if (attributesResponse.HttpStatusCode != HttpStatusCode.OK) - { - throw new InvalidOperationException( - $"Could not find ARN of DLQ, status: {attributesResponse.HttpStatusCode}"); - } - - _dlqARN = attributesResponse.QueueARN; - } - else - throw new InvalidOperationException( - $"Could not find create DLQ, status: {createDeadLetterQueueResponse.HttpStatusCode}"); - } - catch (QueueDeletedRecentlyException ex) - { - var error = - $"Could not create queue {_subscription.ChannelName.Value} because {ex.Message} waiting 60s to retry"; - s_logger.LogError(ex, "Could not create queue {ChannelName} because {ErrorMessage} waiting 60s to retry", - _subscription.ChannelName.Value, ex.Message); - Thread.Sleep(TimeSpan.FromSeconds(30)); - throw new ChannelFailureException(error, ex); - } - catch (AmazonSQSException ex) - { - var error = - $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {AwsConnection.Region.DisplayName} because {ex.Message}"; - s_logger.LogError(ex, - "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", - _queueUrl, _subscription.RoutingKey.Value, AwsConnection.Region.DisplayName, ex.Message); - throw new InvalidOperationException(error, ex); - } - catch (HttpErrorResponseException ex) - { - var error = - $"Could not create queue {_queueUrl} subscribed to topic {_subscription.RoutingKey.Value} in region {AwsConnection.Region.DisplayName} because {ex.Message}"; - s_logger.LogError(ex, - "Could not create queue {URL} subscribed to topic {Topic} in region {Region} because {ErrorMessage}", - _queueUrl, _subscription.RoutingKey.Value, AwsConnection.Region.DisplayName, ex.Message); - throw new InvalidOperationException(error, ex); - } - } - - private async Task CheckSubscriptionAsync(OnMissingChannel makeSubscriptions, AmazonSQSClient sqsClient, - AmazonSimpleNotificationServiceClient snsClient) - { - if (makeSubscriptions == OnMissingChannel.Assume) - return; - - if (!await SubscriptionExistsAsync(sqsClient, snsClient)) - { - if (makeSubscriptions == OnMissingChannel.Validate) - { - throw new BrokerUnreachableException( - $"Subscription validation error: could not find subscription for {_queueUrl}"); - } - else if (makeSubscriptions == OnMissingChannel.Create) - { - await SubscribeToTopicAsync(sqsClient, snsClient); - } - } - } - - private async Task SubscribeToTopicAsync(AmazonSQSClient sqsClient, AmazonSimpleNotificationServiceClient snsClient) - { - var arn = await snsClient.SubscribeQueueAsync(ChannelTopicArn, sqsClient, _queueUrl); - if (!string.IsNullOrEmpty(arn)) - { - var response = await snsClient.SetSubscriptionAttributesAsync( - new SetSubscriptionAttributesRequest(arn, "RawMessageDelivery", - _subscription?.RawMessageDelivery.ToString()) - ); - if (response.HttpStatusCode != HttpStatusCode.OK) - { - throw new InvalidOperationException("Unable to set subscription attribute for raw message delivery"); - } - } - else - { - throw new InvalidOperationException( - $"Could not subscribe to topic: {ChannelTopicArn} from queue: {_queueUrl} in region {AwsConnection.Region}"); - } - } - - private async Task<(bool exists, string? queueUrl)> QueueExistsAsync(AmazonSQSClient client, string? channelName) + private static async Task<(bool exists, string? queueUrl)> QueueExistsAsync(AmazonSQSClient client, string? channelName) { if (string.IsNullOrEmpty(channelName)) return (false, null); @@ -527,14 +277,14 @@ private async Task SubscriptionExistsAsync(AmazonSQSClient sqsClient, string? queueArn = await GetQueueArnForChannelAsync(sqsClient); if (queueArn == null) - throw new BrokerUnreachableException($"Could not find queue ARN for queue {_queueUrl}"); + throw new BrokerUnreachableException($"Could not find queue ARN for queue {ChannelQueueUrl}"); bool exists = false; ListSubscriptionsByTopicResponse response; do { response = await snsClient.ListSubscriptionsByTopicAsync( - new ListSubscriptionsByTopicRequest { TopicArn = ChannelTopicArn }); + new ListSubscriptionsByTopicRequest { TopicArn = ChannelAddress }); exists = response.Subscriptions.Any(sub => (sub.Protocol.ToLower() == "sqs") && (sub.Endpoint == queueArn)); } while (!exists && response.NextToken != null); @@ -550,7 +300,7 @@ private async Task SubscriptionExistsAsync(AmazonSQSClient sqsClient, private async Task GetQueueArnForChannelAsync(AmazonSQSClient sqsClient) { var result = await sqsClient.GetQueueAttributesAsync( - new GetQueueAttributesRequest { QueueUrl = _queueUrl, AttributeNames = new List { "QueueArn" } } + new GetQueueAttributesRequest { QueueUrl = ChannelQueueUrl, AttributeNames = ["QueueArn"] } ); if (result.HttpStatusCode == HttpStatusCode.OK) @@ -572,7 +322,7 @@ private async Task UnsubscribeFromTopicAsync(AmazonSimpleNotificationServiceClie do { response = await snsClient.ListSubscriptionsByTopicAsync( - new ListSubscriptionsByTopicRequest { TopicArn = ChannelTopicArn }); + new ListSubscriptionsByTopicRequest { TopicArn = ChannelAddress }); foreach (var sub in response.Subscriptions) { var unsubscribe = @@ -580,7 +330,7 @@ private async Task UnsubscribeFromTopicAsync(AmazonSimpleNotificationServiceClie if (unsubscribe.HttpStatusCode != HttpStatusCode.OK) { s_logger.LogError("Error unsubscribing from {TopicResourceName} for sub {ChannelResourceName}", - ChannelTopicArn, sub.SubscriptionArn); + ChannelAddress, sub.SubscriptionArn); } } } while (response.NextToken != null); diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/HeaderNames.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/HeaderNames.cs index 79bfd5c492..32edcd263d 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/HeaderNames.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/HeaderNames.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2022 Ian Cooper @@ -19,20 +20,22 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + #endregion namespace Paramore.Brighter.MessagingGateway.AWSSQS; public static class HeaderNames { - public static readonly string Id = "id"; - public static string Topic = "topic"; - public static string ContentType = "content-type"; - public static readonly string CorrelationId = "correlation-id"; - public static readonly string HandledCount = "handled-count"; - public static readonly string MessageType = "message-type"; - public static readonly string Timestamp = "timestamp"; - public static readonly string ReplyTo = "reply-to"; - public static string Bag = "bag"; + public const string Id = "id"; + public const string Topic = "topic"; + public const string ContentType = "content-type"; + public const string CorrelationId = "correlation-id"; + public const string HandledCount = "handled-count"; + public const string MessageType = "message-type"; + public const string Timestamp = "timestamp"; + public const string ReplyTo = "reply-to"; + public const string Subject = "subject"; + public const string Bag = "bag"; public const string DeduplicationId = "messageDeduplicationId"; } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessage.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/IValidateQueue.cs similarity index 78% rename from src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessage.cs rename to src/Paramore.Brighter.MessagingGateway.AWSSQS/IValidateQueue.cs index ca79ba0bf2..a81c2f642a 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessage.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/IValidateQueue.cs @@ -1,4 +1,4 @@ -#region Licence +#region Licence /* The MIT License (MIT) Copyright © 2022 Ian Cooper @@ -21,17 +21,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #endregion -using System; +using System.Threading; +using System.Threading.Tasks; -namespace Paramore.Brighter.MessagingGateway.AWSSQS -{ - /// - /// This class is used to deserialize a SNS backed SQS message - /// - public class SqsMessage - { - public Guid MessageId { get; set; } +namespace Paramore.Brighter.MessagingGateway.AWSSQS; - public string? Message { get; set; } - } +internal interface IValidateQueue +{ + Task<(bool, string?)> ValidateAsync(string queue, CancellationToken cancellationToken = default); } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/QueueFindBy.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/QueueFindBy.cs new file mode 100644 index 0000000000..e6d5fb8fdc --- /dev/null +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/QueueFindBy.cs @@ -0,0 +1,13 @@ +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +/// +/// How do we validate the queue - when we opt to validate or create (already exists) infrastructure +/// Relates to how we interpret the RoutingKey. Is it an Arn (0 or 1) or a name (2) +/// 0 - The queue is supplied as an url, and should be checked with a GetQueueAttributes call. May be any account. +/// 2 - The topic is supplies as a name, and should be checked by a GetQueueUrlAsync call. Must be in caller's account. +/// +public enum QueueFindBy +{ + Url, + Name +} diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/RoutingKeyType.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/RoutingKeyType.cs new file mode 100644 index 0000000000..91effe8d9d --- /dev/null +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/RoutingKeyType.cs @@ -0,0 +1,17 @@ +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +/// +/// The routing key type +/// +public enum RoutingKeyType +{ + /// + /// Use the Pub/Sub for routing key, aka SNS + /// + PubSub, + + /// + /// Use point-to-point for routing key, aka SQS + /// + PointToPoint +} diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsAttributes.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsAttributes.cs index 70c2454724..845151bdfc 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsAttributes.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsAttributes.cs @@ -24,27 +24,36 @@ THE SOFTWARE. */ using System.Collections.Generic; using Amazon.SimpleNotificationService.Model; -namespace Paramore.Brighter.MessagingGateway.AWSSQS +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +public class SnsAttributes { - public class SnsAttributes - { - /// - /// The policy that defines how Amazon SNS retries failed deliveries to HTTP/S endpoints - /// Ignored if TopicARN is set - /// - public string? DeliveryPolicy { get; set; } = null; - - /// - /// The JSON serialization of the topic's access control policy. - /// The policy that defines who can access your topic. By default, only the topic owner can publish or subscribe to the topic. - /// Ignored if TopicARN is set - /// - public string? Policy { get; set; } = null; + /// + /// The policy that defines how Amazon SNS retries failed deliveries to HTTP/S endpoints + /// Ignored if TopicARN is set + /// + public string? DeliveryPolicy { get; set; } = null; + + /// + /// The JSON serialization of the topic's access control policy. + /// The policy that defines who can access your topic. By default, only the topic owner can publish or subscribe to the topic. + /// Ignored if TopicARN is set + /// + public string? Policy { get; set; } = null; - /// - /// A list of resource tags to use when creating the publication - /// Ignored if TopicARN is set - /// - public List Tags => new List(); - } + /// + /// A list of resource tags to use when creating the publication + /// Ignored if TopicARN is set + /// + public List Tags => []; + + /// + /// The . + /// + public SnsSqsType Type { get; set; } = SnsSqsType.Standard; + + /// + /// Enable content based deduplication for Fifo Topics + /// + public bool ContentBasedDeduplication { get; set; } = true; } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs index d8ee3225cf..571b1fd99f 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs @@ -84,27 +84,34 @@ public async Task ConfirmTopicExistsAsync(string? topic = null, CancellationToken cancellationToken = default) { //Only do this on first send for a topic for efficiency; won't auto-recreate when goes missing at runtime as a result - if (!string.IsNullOrEmpty(ChannelTopicArn)) return !string.IsNullOrEmpty(ChannelTopicArn); + if (!string.IsNullOrEmpty(ChannelTopicArn)) + { + return true; + } RoutingKey? routingKey = null; - if (topic is null && _publication.Topic is not null) - routingKey = _publication.Topic; - else if (topic is not null) + if (topic is not null) + { routingKey = new RoutingKey(topic); + } + else if (_publication.Topic is not null) + { + routingKey = _publication.Topic; + } if (routingKey is null) + { throw new ConfigurationException("No topic specified for producer"); + } - await EnsureTopicAsync( + var topicArn = await EnsureTopicAsync( routingKey, _publication.FindTopicBy, _publication.SnsAttributes, _publication.MakeChannels, - _publication.SnsType, - _publication.Deduplication, cancellationToken); - return !string.IsNullOrEmpty(ChannelTopicArn); + return !string.IsNullOrEmpty(topicArn); } /// @@ -115,17 +122,18 @@ await EnsureTopicAsync( public async Task SendAsync(Message message, CancellationToken cancellationToken = default) { s_logger.LogDebug( - "SQSMessageProducer: Publishing message with topic {Topic} and id {Id} and message: {Request}", + "SNSMessageProducer: Publishing message with topic {Topic} and id {Id} and message: {Request}", message.Header.Topic, message.Id, message.Body); await ConfirmTopicExistsAsync(message.Header.Topic, cancellationToken); - if (string.IsNullOrEmpty(ChannelTopicArn)) + if (string.IsNullOrEmpty(ChannelAddress)) throw new InvalidOperationException( $"Failed to publish message with topic {message.Header.Topic} and id {message.Id} and message: {message.Body} as the topic does not exist"); using var client = _clientFactory.CreateSnsClient(); - var publisher = new SnsMessagePublisher(ChannelTopicArn!, client, _publication.SnsType, _publication.Deduplication); + var publisher = new SnsMessagePublisher(ChannelAddress!, client, + _publication.SnsAttributes?.Type ?? SnsSqsType.Standard); var messageId = await publisher.PublishAsync(message); if (messageId == null) @@ -133,7 +141,7 @@ public async Task SendAsync(Message message, CancellationToken cancellationToken $"Failed to publish message with topic {message.Header.Topic} and id {message.Id} and message: {message.Body}"); s_logger.LogDebug( - "SQSMessageProducer: Published message with topic {Topic}, Brighter messageId {MessageId} and SNS messageId {SNSMessageId}", + "SNSMessageProducer: Published message with topic {Topic}, Brighter messageId {MessageId} and SNS messageId {SNSMessageId}", message.Header.Topic, message.Id, messageId); } @@ -151,7 +159,7 @@ public async Task SendAsync(Message message, CancellationToken cancellationToken /// The sending delay /// Task. public void SendWithDelay(Message message, TimeSpan? delay = null) - { + { // SNS doesn't support publish with delay Send(message); } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducerFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducerFactory.cs index b172c86165..c504f54c52 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducerFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducerFactory.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2024 Dominic Hickie @@ -19,70 +20,82 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + #endregion using System.Collections.Generic; using System.Threading.Tasks; -using Paramore.Brighter.Tasks; -namespace Paramore.Brighter.MessagingGateway.AWSSQS +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +public class SnsMessageProducerFactory : IAmAMessageProducerFactory { - public class SnsMessageProducerFactory : IAmAMessageProducerFactory - { - private readonly AWSMessagingGatewayConnection _connection; - private readonly IEnumerable _publications; + private readonly AWSMessagingGatewayConnection _connection; + private readonly IEnumerable _publications; - /// - /// Creates a collection of SNS message producers from the SNS publication information - /// - /// The Connection to use to connect to AWS - /// The publications describing the SNS topics that we want to use - public SnsMessageProducerFactory( - AWSMessagingGatewayConnection connection, - IEnumerable publications) - { - _connection = connection; - _publications = publications; - } + /// + /// Creates a collection of SNS message producers from the SNS publication information + /// + /// The Connection to use to connect to AWS + /// The publications describing the SNS topics that we want to use + public SnsMessageProducerFactory( + AWSMessagingGatewayConnection connection, + IEnumerable publications) + { + _connection = connection; + _publications = publications; + } - /// - /// - /// Sync over async used here, alright in the context of producer creation - /// - public Dictionary Create() + /// + /// + /// Sync over async used here, alright in the context of producer creation + /// + public Dictionary Create() + { + var producers = new Dictionary(); + foreach (var p in _publications) { - var producers = new Dictionary(); - foreach (var p in _publications) + if (p.Topic is null) { - if (p.Topic is null) - throw new ConfigurationException($"Missing topic on Publication"); - - var producer = new SnsMessageProducer(_connection, p); - if (producer.ConfirmTopicExists()) - producers[p.Topic] = producer; - else - throw new ConfigurationException($"Missing SNS topic: {p.Topic}"); + throw new ConfigurationException("Missing topic on Publication"); } - return producers; + var producer = new SnsMessageProducer(_connection, p); + if (producer.ConfirmTopicExists()) + { + producers[p.Topic] = producer; + } + else + { + throw new ConfigurationException($"Missing SNS topic: {p.Topic}"); + } } - - public async Task> CreateAsync() + + return producers; + } + + /// + public async Task> CreateAsync() + { + var producers = new Dictionary(); + foreach (var p in _publications) { - var producers = new Dictionary(); - foreach (var p in _publications) + if (p.Topic is null) { - if (p.Topic is null) - throw new ConfigurationException($"Missing topic on Publication"); - - var producer = new SnsMessageProducer(_connection, p); - if (await producer.ConfirmTopicExistsAsync()) - producers[p.Topic] = producer; - else - throw new ConfigurationException($"Missing SNS topic: {p.Topic}"); + throw new ConfigurationException("Missing topic on Publication"); } - return producers; + var producer = new SnsMessageProducer(_connection, p); + if (await producer.ConfirmTopicExistsAsync()) + { + producers[p.Topic] = producer; + } + else + { + throw new ConfigurationException($"Missing SNS topic: {p.Topic}"); + } } + + return producers; } } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessagePublisher.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessagePublisher.cs index 716c6c7186..caf9bebd0d 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessagePublisher.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessagePublisher.cs @@ -37,15 +37,12 @@ public class SnsMessagePublisher private readonly string _topicArn; private readonly AmazonSimpleNotificationServiceClient _client; private readonly SnsSqsType _snsSqsType; - private readonly bool _deduplication; - public SnsMessagePublisher(string topicArn, AmazonSimpleNotificationServiceClient client, SnsSqsType snsSqsType, - bool deduplication) + public SnsMessagePublisher(string topicArn, AmazonSimpleNotificationServiceClient client, SnsSqsType snsSqsType) { _topicArn = topicArn; _client = client; _snsSqsType = snsSqsType; - _deduplication = deduplication; } public async Task PublishAsync(Message message) @@ -74,7 +71,7 @@ public SnsMessagePublisher(string topicArn, AmazonSimpleNotificationServiceClien if (_snsSqsType == SnsSqsType.Fifo) { publishRequest.MessageGroupId = message.Header.PartitionKey; - if (_deduplication && message.Header.Bag.TryGetValue(HeaderNames.DeduplicationId, out var deduplicationId)) + if (message.Header.Bag.TryGetValue(HeaderNames.DeduplicationId, out var deduplicationId)) { publishRequest.MessageDeduplicationId = (string)deduplicationId; } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsProducerRegistryFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsProducerRegistryFactory.cs index 4b6cd38c75..7f7a2f1179 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsProducerRegistryFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsProducerRegistryFactory.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2022 Ian Cooper @@ -19,46 +20,54 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + #endregion using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace Paramore.Brighter.MessagingGateway.AWSSQS +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +/// +/// The SNS Message Producer registry factory +/// +public class SnsProducerRegistryFactory : IAmAProducerRegistryFactory { - public class SnsProducerRegistryFactory : IAmAProducerRegistryFactory - { - private readonly AWSMessagingGatewayConnection _connection; - private readonly IEnumerable _snsPublications; + private readonly AWSMessagingGatewayConnection _connection; + private readonly IEnumerable _snsPublications; - /// - /// Create a collection of producers from the publication information - /// - /// The Connection to use to connect to AWS - /// The publication describing the SNS topic that we want to use - public SnsProducerRegistryFactory( - AWSMessagingGatewayConnection connection, - IEnumerable snsPublications) - { - _connection = connection; - _snsPublications = snsPublications; - } + /// + /// Create a collection of producers from the publication information + /// + /// The Connection to use to connect to AWS + /// The publication describing the SNS topic that we want to use + public SnsProducerRegistryFactory( + AWSMessagingGatewayConnection connection, + IEnumerable snsPublications) + { + _connection = connection; + _snsPublications = snsPublications; + } - /// - /// Create a message producer for each publication, add it into the registry under the key of the topic - /// - /// - public IAmAProducerRegistry Create() - { - var producerFactory = new SnsMessageProducerFactory(_connection, _snsPublications); - return new ProducerRegistry(producerFactory.Create()); - } + /// + /// Create a message producer for each publication, add it into the registry under the key of the topic + /// + /// The with . + public IAmAProducerRegistry Create() + { + var producerFactory = new SnsMessageProducerFactory(_connection, _snsPublications); + return new ProducerRegistry(producerFactory.Create()); + } - public async Task CreateAsync(CancellationToken ct = default) - { - var producerFactory = new SnsMessageProducerFactory(_connection, _snsPublications); - return new ProducerRegistry(await producerFactory.CreateAsync()); - } + /// + /// Create a message producer for each publication, add it into the registry under the key of the topic + /// + /// The . + /// The with . + public async Task CreateAsync(CancellationToken ct = default) + { + var producerFactory = new SnsMessageProducerFactory(_connection, _snsPublications); + return new ProducerRegistry(await producerFactory.CreateAsync()); } } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsPublication.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsPublication.cs index bb03480a0b..612a819229 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsPublication.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsPublication.cs @@ -44,41 +44,4 @@ public class SnsPublication : Publication /// as we use the topic from the header to dispatch to an Arn. /// public string? TopicArn { get; set; } - - /// - /// The AWS SQS type. - /// - public SnsSqsType SnsType { get; set; } = SnsSqsType.Standard; - - /// - /// Amazon SNS FIFO topics support message deduplication, which provides - /// exactly-once message delivery and processing as long as the following conditions are met: - /// - /// - /// - /// The subscribed Amazon SQS FIFO queue exists and has permissions that allow the - /// AmazonSNS service principal to deliver messages to the queue. - /// - /// - /// - /// - /// The Amazon SQS FIFO queue consumer processes the message and deletes it from the - /// queue before the visibility timeout expires. - /// - /// - /// - /// - /// The Amazon SNS subscription topic has no message filtering. When you configure - /// message filtering, Amazon SNS FIFO topics support at-most-once delivery, as messages - /// can be filtered out based on your subscription filter policies. - /// - /// - /// - /// - /// There are no network disruptions that prevent acknowledgment of the message delivery - /// - /// - /// - /// - public bool Deduplication { get; set; } } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsAttributes.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsAttributes.cs new file mode 100644 index 0000000000..145cea16b0 --- /dev/null +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsAttributes.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; + +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +/// +/// The SQS Attributes +/// +public class SqsAttributes +{ + /// + /// The routing key type. + /// + public RoutingKeyType RoutingKeyType { get; set; } + + /// + /// This governs how long, in seconds, a 'lock' is held on a message for one consumer + /// to process. SQS calls this the VisibilityTimeout + /// + public int LockTimeout { get; set; } + + /// + /// The length of time, in seconds, for which the delivery of all messages in the queue is delayed. + /// + public int DelaySeconds { get; set; } + + /// + /// The length of time, in seconds, for which Amazon SQS retains a message + /// + public int MessageRetentionPeriod { get; set; } + + /// + /// The JSON serialization of the queue's access control policy. + /// + public string? IAMPolicy { get; set; } + + /// + /// Indicate that the Raw Message Delivery setting is enabled or disabled + /// + public bool RawMessageDelivery { get; set; } + + /// + /// The policy that controls when we send messages to a DLQ after too many requeue attempts + /// + public RedrivePolicy? RedrivePolicy { get; set; } + + /// + /// Gets the timeout that we use to infer that nothing could be read from the channel i.e. is empty + /// or busy + /// + /// The timeout + public TimeSpan TimeOut { get; set; } + + /// + /// A list of resource tags to use when creating the queue + /// + public Dictionary? Tags { get; set; } + + /// + /// The AWS SQS type. + /// + public SnsSqsType Type { get; set; } + + /// + /// Enables or disable content-based deduplication, for Fifo queues. + /// + public bool ContentBasedDeduplication { get; set; } = true; + + /// + /// Specifies whether message deduplication occurs at the message group or queue level. + /// This configuration is used for high throughput for FIFO queues configuration + /// + public DeduplicationScope? DeduplicationScope { get; set; } + + /// + /// Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group + /// This configuration is used for high throughput for FIFO queues configuration + /// + public int? FifoThroughputLimit { get; set; } + + public static SqsAttributes From(SqsSubscription subscription) + { + return new SqsAttributes + { + RoutingKeyType = subscription.RoutingKeyType, + LockTimeout = subscription.LockTimeout, + DelaySeconds = subscription.DelaySeconds, + MessageRetentionPeriod = subscription.MessageRetentionPeriod, + IAMPolicy = subscription.IAMPolicy, + RawMessageDelivery = subscription.RawMessageDelivery, + RedrivePolicy = subscription.RedrivePolicy, + Tags = subscription.Tags, + Type = subscription.SqsType, + ContentBasedDeduplication = subscription.ContentBasedDeduplication, + DeduplicationScope = subscription.DeduplicationScope, + FifoThroughputLimit = subscription.FifoThroughputLimit, + TimeOut = subscription.TimeOut, + }; + } +} diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs index 7a8abb20d6..1d09aca44b 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs @@ -26,6 +26,7 @@ THE SOFTWARE. */ using System; using System.Collections.Generic; using System.Text.Json; +using Amazon; using Amazon.SQS; using Microsoft.Extensions.Logging; using Paramore.Brighter.Logging; @@ -239,13 +240,19 @@ private HeaderResult ReadTopic() { if (_messageAttributes.TryGetValue(HeaderNames.Topic, out var topicArn)) { - //we have an arn, and we want the topic - var s = topicArn.GetValueInString(); - if (string.IsNullOrEmpty(s)) - return new HeaderResult(RoutingKey.Empty, true); - - var arnElements = s!.Split(':'); - var topic = arnElements[(int)ARNAmazonSNS.TopicName]; + var topic = topicArn.GetValueInString() ?? string.Empty; + if (Arn.TryParse(topic, out var arn)) + { + topic = arn.Resource; + } + else + { + var indexOf = topic.LastIndexOf('/'); + if (indexOf != -1) + { + topic = topic.Substring(indexOf + 1); + } + } return new HeaderResult(new RoutingKey(topic), true); } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs index 8e8275928d..f7890cb529 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs @@ -26,6 +26,7 @@ THE SOFTWARE. */ using System; using System.Collections.Generic; using System.Text.Json; +using Amazon; using Amazon.SQS; using Amazon.SQS.Model; using Microsoft.Extensions.Logging; @@ -72,6 +73,7 @@ public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) var receiptHandle = ReadReceiptHandle(sqsMessage); var partitionKey = ReadPartitionKey(sqsMessage); var deduplicationId = ReadDeduplicationId(sqsMessage); + var subject = ReadSubject(sqsMessage); var bodyType = (contentType.Success ? contentType.Result : "plain/text"); @@ -87,7 +89,7 @@ public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage) contentType: bodyType!, handledCount: handledCount.Result, dataSchema: null, - subject: null, + subject: subject.Success ? subject.Result : string.Empty, delayed: TimeSpan.Zero, partitionKey: partitionKey.Success ? partitionKey.Result : string.Empty ); @@ -230,13 +232,25 @@ private static HeaderResult ReadContentType(Amazon.SQS.Model.Message sqs return new HeaderResult(string.Empty, true); } - private HeaderResult ReadTopic(Amazon.SQS.Model.Message sqsMessage) + private static HeaderResult ReadTopic(Amazon.SQS.Model.Message sqsMessage) { if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.Topic, out MessageAttributeValue? value)) { //we have an arn, and we want the topic - var arnElements = value.StringValue.Split(':'); - var topic = arnElements[(int)ARNAmazonSNS.TopicName]; + var topic = value.StringValue; + if (Arn.TryParse(value.StringValue, out var arn)) + { + topic = arn.Resource; + } + else + { + var indexOf = value.StringValue.LastIndexOf('/'); + if (indexOf != -1) + { + topic = value.StringValue.Substring(indexOf + 1); + } + } + return new HeaderResult(new RoutingKey(topic), true); } @@ -266,4 +280,16 @@ private static HeaderResult ReadDeduplicationId(Amazon.SQS.Model.Message return new HeaderResult(null, false); } + + private static HeaderResult ReadSubject(Amazon.SQS.Model.Message sqsMessage) + { + if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.Subject, out var value)) + { + //we have an arn, and we want the topic + var subject = value.StringValue; + return new HeaderResult(subject, true); + } + + return new HeaderResult(null, false); + } } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs new file mode 100644 index 0000000000..df4eece12b --- /dev/null +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs @@ -0,0 +1,165 @@ +#region Licence + +/* The MIT License (MIT) +Copyright © 2022 Ian Cooper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion + +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Paramore.Brighter.Tasks; + +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +/// +/// The SQS Message producer +/// +public class SqsMessageProducer : AWSMessagingGateway, IAmAMessageProducerAsync, IAmAMessageProducerSync +{ + private readonly SqsPublication _publication; + private readonly AWSClientFactory _clientFactory; + + /// + /// The publication configuration for this producer + /// + public Publication Publication => _publication; + + /// + /// The OTel Span we are writing Producer events too + /// + public Activity? Span { get; set; } + + /// + /// Initialize a new instance of the . + /// + /// How do we connect to AWS in order to manage middleware + /// Configuration of a producer + public SqsMessageProducer(AWSMessagingGatewayConnection connection, SqsPublication publication) + : base(connection) + { + _publication = publication; + _clientFactory = new AWSClientFactory(connection); + + if (publication.QueueUrl != null) + { + ChannelQueueUrl = publication.QueueUrl; + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public ValueTask DisposeAsync() => new(); + + /// + /// Confirm the queue exists. + /// + /// The queue name. + public bool ConfirmQueueExists(string? queue = null) + => BrighterAsyncContext.Run(async () => await ConfirmQueueExistsAsync(queue)); + + /// + /// Confirm the queue exists. + /// + /// The queue name. + /// The . + /// Return true if the queue exists otherwise return false + public async Task ConfirmQueueExistsAsync(string? queue = null, CancellationToken cancellationToken = default) + { + //Only do this on first send for a topic for efficiency; won't auto-recreate when goes missing at runtime as a result + if (!string.IsNullOrEmpty(ChannelQueueUrl)) + { + return true; + } + + _publication.SqsAttributes ??= new SqsAttributes(); + + // For SQS Publish, it should be always Point-to-Point + _publication.SqsAttributes.RoutingKeyType = RoutingKeyType.PointToPoint; + + RoutingKey? routingKey = null; + if (queue is not null) + { + routingKey = new RoutingKey(queue); + } + else if (_publication.Topic is not null) + { + routingKey = _publication.Topic; + } + + if (routingKey is null) + { + throw new ConfigurationException("No topic specified for producer"); + } + + var queueUrl = await EnsureQueueAsync( + routingKey, + _publication.FindQueueBy, + _publication.SqsAttributes, + _publication.MakeChannels, + cancellationToken); + + return !string.IsNullOrEmpty(queueUrl); + } + + public async Task SendAsync(Message message, CancellationToken cancellationToken = default) + => await SendWithDelayAsync(message, null, cancellationToken); + + public async Task SendWithDelayAsync(Message message, TimeSpan? delay, + CancellationToken cancellationToken = default) + { + s_logger.LogDebug( + "SQSMessageProducer: Publishing message with topic {Topic} and id {Id} and message: {Request}", + message.Header.Topic, message.Id, message.Body); + + await ConfirmQueueExistsAsync(message.Header.Topic, cancellationToken); + + using var client = _clientFactory.CreateSqsClient(); + var type = _publication.SqsAttributes?.Type ?? SnsSqsType.Standard; + var sender = new SqsMessageSender(ChannelQueueUrl!, type, client); + var messageId = await sender.SendAsync(message, delay, cancellationToken); + + if (messageId == null) + { + throw new InvalidOperationException( + $"Failed to publish message with topic {message.Header.Topic} and id {message.Id} and message: {message.Body}"); + } + + s_logger.LogDebug( + "SQSMessageProducer: Published message with topic {Topic}, Brighter messageId {MessageId} and SNS messageId {SNSMessageId}", + message.Header.Topic, message.Id, messageId); + } + + public void Send(Message message) => SendWithDelay(message, null); + + public void SendWithDelay(Message message, TimeSpan? delay) + => BrighterAsyncContext.Run(async () => await SendWithDelayAsync(message, delay)); +} diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducerFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducerFactory.cs new file mode 100644 index 0000000000..33b38531f0 --- /dev/null +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducerFactory.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +/// +/// The factory +/// +public class SqsMessageProducerFactory : IAmAMessageProducerFactory +{ + private readonly AWSMessagingGatewayConnection _connection; + private readonly IEnumerable _publications; + + /// + /// Initialize new instance of . + /// + /// The . + /// The collection of . + public SqsMessageProducerFactory(AWSMessagingGatewayConnection connection, + IEnumerable publications) + { + _connection = connection; + _publications = publications; + } + + /// + /// + /// Sync over async used here, alright in the context of producer creation + /// + public Dictionary Create() + { + var producers = new Dictionary(); + foreach (var sqs in _publications) + { + if (sqs.Topic is null) + { + throw new ConfigurationException("Missing topic on Publication"); + } + + var producer = new SqsMessageProducer(_connection, sqs); + if (producer.ConfirmQueueExists()) + { + producers[sqs.Topic] = producer; + } + else + { + throw new ConfigurationException($"Missing SQS queue: {sqs.Topic}"); + } + } + + return producers; + } + + /// + public async Task> CreateAsync() + { + var producers = new Dictionary(); + foreach (var sqs in _publications) + { + if (sqs.Topic is null) + { + throw new ConfigurationException("Missing topic on Publication"); + } + + var producer = new SqsMessageProducer(_connection, sqs); + if (await producer.ConfirmQueueExistsAsync()) + { + producers[sqs.Topic] = producer; + } + else + { + throw new ConfigurationException($"Missing SQS queue: {sqs.Topic}"); + } + } + + return producers; + } +} diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageSender.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageSender.cs new file mode 100644 index 0000000000..f0695aef99 --- /dev/null +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageSender.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Amazon.SQS; +using Amazon.SQS.Model; + +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +/// +/// Class responsible for sending a message to a SQS +/// +public class SqsMessageSender +{ + private readonly string _queueUrl; + private readonly SnsSqsType _queueType; + private readonly AmazonSQSClient _client; + + /// + /// Initialize the + /// + /// The queue ARN + /// The queue type + /// The SQS Client + public SqsMessageSender(string queueUrl, SnsSqsType queueType, AmazonSQSClient client) + { + _queueUrl = queueUrl; + _queueType = queueType; + _client = client; + } + + public async Task SendAsync(Message message, TimeSpan? delay, CancellationToken cancellationToken) + { + var request = new SendMessageRequest { QueueUrl = _queueUrl, MessageBody = message.Body.Value, }; + + if (delay != null) + { + request.DelaySeconds = (int)delay.Value.TotalSeconds; + } + + if (_queueType == SnsSqsType.Fifo) + { + request.MessageGroupId = message.Header.PartitionKey; + if (message.Header.Bag.TryGetValue(HeaderNames.DeduplicationId, out var deduplicationId)) + { + request.MessageDeduplicationId = (string)deduplicationId; + } + } + + var messageAttributes = new Dictionary + { + [HeaderNames.Id] = + new() { StringValue = Convert.ToString(message.Header.MessageId), DataType = "String" }, + [HeaderNames.Topic] = new() { StringValue = _queueUrl, DataType = "String" }, + [HeaderNames.ContentType] = new() { StringValue = message.Header.ContentType, DataType = "String" }, + [HeaderNames.CorrelationId] = + new() { StringValue = Convert.ToString(message.Header.CorrelationId), DataType = "String" }, + [HeaderNames.HandledCount] = + new() { StringValue = Convert.ToString(message.Header.HandledCount), DataType = "String" }, + [HeaderNames.MessageType] = + new() { StringValue = message.Header.MessageType.ToString(), DataType = "String" }, + [HeaderNames.Timestamp] = new() + { + StringValue = Convert.ToString(message.Header.TimeStamp), DataType = "String" + } + }; + + if (!string.IsNullOrEmpty(message.Header.ReplyTo)) + { + messageAttributes.Add(HeaderNames.ReplyTo, + new MessageAttributeValue { StringValue = message.Header.ReplyTo, DataType = "String" }); + } + + if (!string.IsNullOrEmpty(message.Header.Subject)) + { + messageAttributes.Add(HeaderNames.Subject, + new MessageAttributeValue { StringValue = message.Header.Subject, DataType = "String" }); + } + + // we can set up to 10 attributes; we have set 6 above, so use a single JSON object as the bag + var bagJson = JsonSerializer.Serialize(message.Header.Bag, JsonSerialisationOptions.Options); + messageAttributes[HeaderNames.Bag] = new() { StringValue = Convert.ToString(bagJson), DataType = "String" }; + request.MessageAttributes = messageAttributes; + + var response = await _client.SendMessageAsync(request, cancellationToken); + if (response.HttpStatusCode is System.Net.HttpStatusCode.OK or HttpStatusCode.Created + or HttpStatusCode.Accepted) + { + return response.MessageId; + } + + return null; + } +} diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsProducerRegistryFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsProducerRegistryFactory.cs new file mode 100644 index 0000000000..eb3bc3c26f --- /dev/null +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsProducerRegistryFactory.cs @@ -0,0 +1,73 @@ +#region Licence + +/* The MIT License (MIT) +Copyright © 2022 Ian Cooper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +/// +/// The SQS Message Producer registry factory +/// +public class SqsProducerRegistryFactory : IAmAProducerRegistryFactory +{ + private readonly AWSMessagingGatewayConnection _connection; + private readonly IEnumerable _sqsPublications; + + /// + /// Create a collection of producers from the publication information + /// + /// The Connection to use to connect to AWS + /// The publication describing the SNS topic that we want to use + public SqsProducerRegistryFactory( + AWSMessagingGatewayConnection connection, + IEnumerable sqsPublications) + { + _connection = connection; + _sqsPublications = sqsPublications; + } + + /// + /// Create a message producer for each publication, add it into the registry under the key of the topic + /// + /// The with . + public IAmAProducerRegistry Create() + { + var producerFactory = new SqsMessageProducerFactory(_connection, _sqsPublications); + return new ProducerRegistry(producerFactory.Create()); + } + + /// + /// Create a message producer for each publication, add it into the registry under the key of the topic + /// + /// The . + /// The with . + public async Task CreateAsync(CancellationToken ct = default) + { + var producerFactory = new SqsMessageProducerFactory(_connection, _sqsPublications); + return new ProducerRegistry(await producerFactory.CreateAsync()); + } +} diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsPublication.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsPublication.cs new file mode 100644 index 0000000000..3e13f6787a --- /dev/null +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsPublication.cs @@ -0,0 +1,26 @@ +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +/// +/// The SQS Message publication +/// +public class SqsPublication : Publication +{ + /// + /// Indicates how we should treat the routing key + /// QueueFindBy.Url -> the routing key is an url + /// TopicFindBy.Name -> Treat the routing key as a name & use GetQueueUrl to find it + /// + public QueueFindBy FindQueueBy { get; set; } = QueueFindBy.Name; + + /// + /// The attributes of the topic. If TopicARNs is set we will always assume that we do not + /// need to create or validate the SNS Topic + /// + public SqsAttributes? SqsAttributes { get; set; } + + /// + /// If we want to use queue Url and not queues you need to supply the Url to use for any message that you send to us, + /// as we use the topic from the header to dispatch to an url. + /// + public string? QueueUrl { get; set; } +} diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs index 40a08cc947..5d9f83cb21 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs @@ -53,6 +53,18 @@ public class SqsSubscription : Subscription /// public int MessageRetentionPeriod { get; } + /// + /// The routing key type. + /// + public RoutingKeyType RoutingKeyType { get; } + + /// + /// Indicates how we should treat the routing key + /// QueueFindBy.Url -> the routing key is an URL + /// TopicFindBy.Name -> Treat the routing key as a name & use GetQueueUrl to find it + /// + public QueueFindBy QueueFindBy { get; } + /// /// Indicates how we should treat the routing key /// TopicFindBy.Arn -> the routing key is an Arn @@ -61,6 +73,12 @@ public class SqsSubscription : Subscription /// public TopicFindBy FindTopicBy { get; } + /// + /// The attributes of the topic. If TopicARN is set we will always assume that we do not + /// need to create or validate the SNS Topic + /// + public SnsAttributes? SnsAttributes { get; } + /// /// The JSON serialization of the queue's access control policy. /// @@ -76,11 +94,6 @@ public class SqsSubscription : Subscription /// public RedrivePolicy? RedrivePolicy { get; } - /// - /// The attributes of the topic. If TopicARN is set we will always assume that we do not - /// need to create or validate the SNS Topic - /// - public SnsAttributes? SnsAttributes { get; } /// /// A list of resource tags to use when creating the queue @@ -140,6 +153,8 @@ public class SqsSubscription : Subscription /// Enables or disable content-based deduplication /// Specifies whether message deduplication occurs at the message group or queue level /// Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group + /// Specifies the routing key type + /// How the queue should be found when is point-to-point. public SqsSubscription( Type dataType, SubscriptionName? name = null, @@ -168,7 +183,9 @@ public SqsSubscription( SnsSqsType sqsType = SnsSqsType.Standard, bool contentBasedDeduplication = true, DeduplicationScope? deduplicationScope = null, - int? fifoThroughputLimit = null + int? fifoThroughputLimit = null, + RoutingKeyType routingKeyType = RoutingKeyType.PubSub, + QueueFindBy queueFindBy = QueueFindBy.Name ) : base(dataType, name, channelName, routingKey, bufferSize, noOfPerformers, timeOut, requeueCount, requeueDelay, unacceptableMessageLimit, messagePumpType, channelFactory, makeChannels, emptyChannelDelay, @@ -187,6 +204,8 @@ public SqsSubscription( ContentBasedDeduplication = contentBasedDeduplication; DeduplicationScope = deduplicationScope; FifoThroughputLimit = fifoThroughputLimit; + RoutingKeyType = routingKeyType; + QueueFindBy = queueFindBy; } } @@ -229,6 +248,8 @@ public class SqsSubscription : SqsSubscription where T : IRequest /// Enables or disable content-based deduplication /// Specifies whether message deduplication occurs at the message group or queue level /// Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group + /// Specifies the routing key type + /// How the queue should be found when is point-to-point. public SqsSubscription( SubscriptionName? name = null, ChannelName? channelName = null, @@ -256,14 +277,17 @@ public SqsSubscription( SnsSqsType sqsType = SnsSqsType.Standard, bool contentBasedDeduplication = true, DeduplicationScope? deduplicationScope = null, - int? fifoThroughputLimit = null + int? fifoThroughputLimit = null, + RoutingKeyType routingKeyType = RoutingKeyType.PubSub, + QueueFindBy queueFindBy = QueueFindBy.Name ) : base(typeof(T), name, channelName, routingKey, bufferSize, noOfPerformers, timeOut, requeueCount, requeueDelay, unacceptableMessageLimit, messagePumpType, channelFactory, lockTimeout, delaySeconds, messageRetentionPeriod, findTopicBy, iAmPolicy, redrivePolicy, snsAttributes, tags, makeChannels, rawMessageDelivery, emptyChannelDelay, - channelFailureDelay, sqsType, contentBasedDeduplication, deduplicationScope, fifoThroughputLimit) + channelFailureDelay, sqsType, contentBasedDeduplication, deduplicationScope, fifoThroughputLimit, + routingKeyType) { } } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateQueueByName.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateQueueByName.cs new file mode 100644 index 0000000000..05163de636 --- /dev/null +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateQueueByName.cs @@ -0,0 +1,46 @@ +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Amazon.SQS; +using Amazon.SQS.Model; + +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +/// +/// The class is responsible for validating an AWS SQS queue by its name. +/// +public class ValidateQueueByName : IValidateQueue, IDisposable +{ + private readonly AmazonSQSClient _client; + private readonly SnsSqsType _type; + + /// + /// Initialize new instance of . + /// + /// The client. + /// The SQS type. + public ValidateQueueByName(AmazonSQSClient client, SnsSqsType type) + { + _client = client; + _type = type; + } + + /// + public async Task<(bool, string?)> ValidateAsync(string queue, CancellationToken cancellationToken = default) + { + try + { + queue = queue.ToValidSQSQueueName(_type == SnsSqsType.Fifo); + var queueUrlResponse = await _client.GetQueueUrlAsync(queue, cancellationToken); + return (queueUrlResponse.HttpStatusCode == HttpStatusCode.OK, queueUrlResponse.QueueUrl); + } + catch (QueueDoesNotExistException) + { + return (false, queue); + } + } + + /// + public void Dispose() => _client.Dispose(); +} diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateQueueByUrl.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateQueueByUrl.cs new file mode 100644 index 0000000000..4208877c64 --- /dev/null +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateQueueByUrl.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Amazon.SQS; +using Amazon.SQS.Model; + +namespace Paramore.Brighter.MessagingGateway.AWSSQS; + +/// +/// The class is responsible for validating an AWS SQS queue by its url. +/// +public class ValidateQueueByUrl : IValidateQueue, IDisposable +{ + private readonly AmazonSQSClient _client; + + /// + /// Initialize new instance of . + /// + /// The client. + public ValidateQueueByUrl(AmazonSQSClient client) + { + _client = client; + } + + /// + public async Task<(bool, string?)> ValidateAsync(string queue, CancellationToken cancellationToken = default) + { + try + { + _ = await _client.GetQueueAttributesAsync(queue, [QueueAttributeName.QueueArn], cancellationToken); + return (true, queue); + } + catch (QueueDoesNotExistException) + { + return (false, queue); + } + } + + /// + public void Dispose() => _client.Dispose(); +} diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByArnConvention.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByArnConvention.cs index e1943619b4..3d4872103d 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByArnConvention.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByArnConvention.cs @@ -83,10 +83,7 @@ private async Task GetArnFromTopic(string topicName) if (callerIdentityResponse.HttpStatusCode != HttpStatusCode.OK) throw new InvalidOperationException("Could not find identity of AWS account"); - if (_type == SnsSqsType.Fifo && !topicName.EndsWith(".fifo")) - { - topicName += ".fifo"; - } + topicName = topicName.ToValidSNSTopicName(_type == SnsSqsType.Fifo); return new Arn { diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByName.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByName.cs index 026576dfbe..766d60f718 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByName.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ValidateTopicByName.cs @@ -76,11 +76,7 @@ public ValidateTopicByName(AmazonSimpleNotificationServiceClient snsClient, SnsS /// public async Task<(bool, string? TopicArn)> ValidateAsync(string topicName, CancellationToken cancellationToken = default) { - if (_type == SnsSqsType.Fifo && !topicName.EndsWith(".fifo")) - { - topicName += ".fifo"; - } - + topicName = topicName.ToValidSNSTopicName(_type == SnsSqsType.Fifo); var topic = await _snsClient.FindTopicAsync(topicName); return (topic != null, topic?.TopicArn); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs similarity index 96% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs index f7d9e3a442..e87f86e43f 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -40,7 +40,6 @@ public SQSBufferedConsumerTestsAsync() bufferSize: BufferSize, makeChannels: OnMissingChannel.Create, sqsType: SnsSqsType.Fifo, - contentBasedDeduplication: true, deduplicationScope: DeduplicationScope.MessageGroup, fifoThroughputLimit: 1 )).GetAwaiter().GetResult(); @@ -51,7 +50,7 @@ public SQSBufferedConsumerTestsAsync() _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo, Deduplication = true + MakeChannels = OnMissingChannel.Create, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } }); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_infastructure_exists_can_assume_async.cs similarity index 93% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_infastructure_exists_can_assume_async.cs index c39d558dc7..e1df37baf2 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_infastructure_exists_can_assume_async.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -64,7 +64,10 @@ public AWSAssumeInfrastructureTestsAsync() ); _messageProducer = new SnsMessageProducer(awsConnection, - new SnsPublication { MakeChannels = OnMissingChannel.Assume, SnsType = SnsSqsType.Fifo }); + new SnsPublication + { + MakeChannels = OnMissingChannel.Assume, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } + }); _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(true)); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_infrastructure_exists_can_verify_async.cs similarity index 96% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_infrastructure_exists_can_verify_async.cs index e75019302b..324bfc228b 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_infrastructure_exists_can_verify_async.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -68,7 +68,7 @@ public AWSValidateInfrastructureTestsAsync() FindTopicBy = TopicFindBy.Name, MakeChannels = OnMissingChannel.Validate, Topic = new RoutingKey(topicName), - SnsType = SnsSqsType.Fifo + SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } } ); @@ -89,7 +89,7 @@ public async Task When_infrastructure_exists_can_verify_async() await _consumer.AcknowledgeAsync(message); } - + public void Dispose() { //Clean up resources that we have created diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_arn_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_infrastructure_exists_can_verify_by_arn_async.cs similarity index 96% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_arn_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_infrastructure_exists_can_verify_by_arn_async.cs index 60faf421d5..859a582351 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_arn_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_infrastructure_exists_can_verify_by_arn_async.cs @@ -2,15 +2,13 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -72,7 +70,7 @@ public AWSValidateInfrastructureByArnTestsAsync() TopicArn = topicArn, FindTopicBy = TopicFindBy.Arn, MakeChannels = OnMissingChannel.Validate, - SnsType = SnsSqsType.Fifo + SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } }); _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs similarity index 96% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs index 0958d46a44..0055719ad2 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs @@ -7,7 +7,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Proactor; [Trait("Category", "AWS")] public class SqsMessageProducerSendAsyncTests : IAsyncDisposable, IDisposable @@ -62,10 +62,9 @@ public SqsMessageProducerSendAsyncTests() _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { - Topic = new RoutingKey(_topicName), + Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo, - Deduplication = true + SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } }); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_queues_missing_assume_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_queues_missing_assume_throws_async.cs new file mode 100644 index 0000000000..1e4cc87099 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_queues_missing_assume_throws_async.cs @@ -0,0 +1,68 @@ +using System; +using System.Threading.Tasks; +using Amazon.SQS.Model; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Proactor; + +[Trait("Category", "AWS")] +public class AWSAssumeQueuesTestsAsync : IAsyncDisposable, IDisposable +{ + private readonly ChannelFactory _channelFactory; + private readonly IAmAMessageConsumerAsync _consumer; + + public AWSAssumeQueuesTestsAsync() + { + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + makeChannels: OnMissingChannel.Assume, + messagePumpType: MessagePumpType.Proactor, + sqsType: SnsSqsType.Fifo + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //create the topic, we want the queue to be the issue + //We need to create the topic at least, to check the queues + var producer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } + }); + + producer.ConfirmTopicExistsAsync(topicName).Wait(); + + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateAsyncChannel(subscription); + + //We need to create the topic at least, to check the queues + _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); + } + + [Fact] + public async Task When_queues_missing_assume_throws_async() + { + //we will try to get the queue url, and fail because it does not exist + await Assert.ThrowsAsync(async () => + await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(1000))); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_queues_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_queues_missing_verify_throws_async.cs new file mode 100644 index 0000000000..63532ac3a9 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_queues_missing_verify_throws_async.cs @@ -0,0 +1,57 @@ +using System; +using System.Threading.Tasks; +using Amazon.SQS.Model; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Proactor; + +[Trait("Category", "AWS")] +public class AWSValidateQueuesTestsAsync : IAsyncDisposable +{ + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; + private ChannelFactory _channelFactory; + + public AWSValidateQueuesTestsAsync() + { + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + _subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo + ); + + _awsConnection = GatewayFactory.CreateFactory(); + + // We need to create the topic at least, to check the queues + var producer = new SnsMessageProducer(_awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } + }); + producer.ConfirmTopicExistsAsync(topicName).Wait(); + } + + [Fact] + public async Task When_queues_missing_verify_throws_async() + { + // We have no queues so we should throw + // We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(_awsConnection); + await Assert.ThrowsAsync(async () => + await _channelFactory.CreateAsyncChannelAsync(_subscription)); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_raw_message_delivery_disabled_async.cs similarity index 95% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_raw_message_delivery_disabled_async.cs index 84ac3c8ce0..243ce52948 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_raw_message_delivery_disabled_async.cs @@ -7,7 +7,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -41,7 +41,7 @@ public SqsRawMessageDeliveryTestsAsync() _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo, Deduplication = true + MakeChannels = OnMissingChannel.Create, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } }); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs similarity index 94% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs index 040e6e364a..d70b0d4a22 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs @@ -7,7 +7,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -50,11 +50,10 @@ public SqsMessageConsumerRequeueTestsAsync() _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateAsyncChannel(subscription); - _messageProducer = new SnsMessageProducer(awsConnection, + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo + MakeChannels = OnMissingChannel.Create, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } }); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_requeueing_a_message_async.cs similarity index 90% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_requeueing_a_message_async.cs index 2eb04097cd..7b1d37577b 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_requeueing_a_message_async.cs @@ -1,14 +1,13 @@ using System; using System.Text.Json; using System.Threading.Tasks; -using Amazon.Runtime.CredentialManagement; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Proactor; [Trait("Category", "AWS")] public class SqsMessageProducerRequeueTestsAsync : IDisposable, IAsyncDisposable @@ -48,12 +47,10 @@ public SqsMessageProducerRequeueTestsAsync() var awsConnection = GatewayFactory.CreateFactory(); - _sender = new SnsMessageProducer(awsConnection, + _sender = new SnsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo, - Deduplication = true + MakeChannels = OnMissingChannel.Create, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } }); _channelFactory = new ChannelFactory(awsConnection); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_requeueing_redrives_to_the_dlq_async.cs similarity index 94% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_requeueing_redrives_to_the_dlq_async.cs index 17ef2529dc..2c2af76335 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_requeueing_redrives_to_the_dlq_async.cs @@ -11,7 +11,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -47,7 +47,7 @@ public SqsMessageProducerDlqTestsAsync() _message = new Message( new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId ), + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) ); @@ -56,9 +56,7 @@ public SqsMessageProducerDlqTestsAsync() _sender = new SnsMessageProducer(_awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo, - Deduplication = true + MakeChannels = OnMissingChannel.Create, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } }); _sender.ConfirmTopicExistsAsync(topicName).Wait(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_throwing_defer_action_respect_redrive_async.cs similarity index 97% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_throwing_defer_action_respect_redrive_async.cs index 54df071d3d..4991184954 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_throwing_defer_action_respect_redrive_async.cs @@ -13,7 +13,7 @@ using Polly.Registry; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -65,8 +65,7 @@ public SnsReDrivePolicySDlqTestsAsync() Topic = routingKey, RequestType = typeof(MyDeferredCommand), MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo, - Deduplication = true + SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } } ); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_topic_missing_verify_throws_async.cs similarity index 85% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_topic_missing_verify_throws_async.cs index 4e5a31af08..9d1d5e4339 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Proactor/When_topic_missing_verify_throws_async.cs @@ -4,10 +4,10 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Proactor; [Trait("Category", "AWS")] -public class AWSValidateMissingTopicTestsAsync +public class AWSValidateMissingTopicTestsAsync { private readonly AWSMessagingGatewayConnection _awsConnection; private readonly RoutingKey _routingKey; @@ -30,15 +30,15 @@ public async Task When_topic_missing_verify_throws_async() new SnsPublication { MakeChannels = OnMissingChannel.Validate, - SnsType = SnsSqsType.Fifo + SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } }); var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; - + // act & assert - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAsync(async () => await producer.SendAsync(new Message( - new MessageHeader("", _routingKey, MessageType.MT_EVENT, + new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text", partitionKey: messageGroupId), new MessageBody("Test")))); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_a_message_consumer_reads_multiple_messages.cs similarity index 96% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_a_message_consumer_reads_multiple_messages.cs index a3b63980c7..ce5dd28b5f 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_a_message_consumer_reads_multiple_messages.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_a_message_consumer_reads_multiple_messages.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -51,7 +51,8 @@ public SQSBufferedConsumerTests() _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create, SnsType = SnsSqsType.Fifo, Deduplication = true + MakeChannels = OnMissingChannel.Create, + SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } }); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_infastructure_exists_can_assume.cs similarity index 95% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_infastructure_exists_can_assume.cs index 271307879e..637948d139 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_assume.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_infastructure_exists_can_assume.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -66,8 +66,7 @@ public AWSAssumeInfrastructureTests() _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Assume, - SnsType = SnsSqsType.Fifo + MakeChannels = OnMissingChannel.Assume, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } }); _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(true)); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_infastructure_exists_can_verify.cs similarity index 96% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_infastructure_exists_can_verify.cs index 5d57fbce6f..1e97b00a8c 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_infastructure_exists_can_verify.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -72,7 +72,7 @@ public AWSValidateInfrastructureTests() FindTopicBy = TopicFindBy.Name, MakeChannels = OnMissingChannel.Validate, Topic = new RoutingKey(topicName), - SnsType = SnsSqsType.Fifo + SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } } ); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_infastructure_exists_can_verify_by_arn.cs similarity index 95% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_infastructure_exists_can_verify_by_arn.cs index e0a5717149..defbefe047 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_arn.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_infastructure_exists_can_verify_by_arn.cs @@ -2,15 +2,13 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -56,7 +54,7 @@ public AWSValidateInfrastructureByArnTests() _channelFactory = new ChannelFactory(awsConnection); var channel = _channelFactory.CreateSyncChannel(subscription); - var topicArn= FindTopicArn(awsConnection, routingKey.ToValidSNSTopicName(true)); + var topicArn = FindTopicArn(awsConnection, routingKey.ToValidSNSTopicName(true)); var routingKeyArn = new RoutingKey(topicArn); //Now change the subscription to validate, just check what we made @@ -78,7 +76,7 @@ public AWSValidateInfrastructureByArnTests() TopicArn = topicArn, FindTopicBy = TopicFindBy.Arn, MakeChannels = OnMissingChannel.Validate, - SnsType = SnsSqsType.Fifo + SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } }); _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_infastructure_exists_can_verify_by_convention.cs similarity index 95% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_infastructure_exists_can_verify_by_convention.cs index 90231abdd8..dd985931d6 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infastructure_exists_can_verify_by_convention.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_infastructure_exists_can_verify_by_convention.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -69,9 +69,9 @@ public AWSValidateInfrastructureByConventionTests() awsConnection, new SnsPublication { - FindTopicBy = TopicFindBy.Convention, + FindTopicBy = TopicFindBy.Convention, MakeChannels = OnMissingChannel.Validate, - SnsType = SnsSqsType.Fifo + SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } } ); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_infrastructure_exists_can_verify_by_convention.cs similarity index 96% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_convention.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_infrastructure_exists_can_verify_by_convention.cs index e29ebcef46..b0904b6d6d 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_infrastructure_exists_can_verify_by_convention.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_infrastructure_exists_can_verify_by_convention.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -67,7 +67,7 @@ public AWSValidateInfrastructureByConventionTestsAsync() { FindTopicBy = TopicFindBy.Convention, MakeChannels = OnMissingChannel.Validate, - SnsType = SnsSqsType.Fifo + SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } } ); @@ -88,7 +88,7 @@ public async Task When_infrastructure_exists_can_verify_async() await _consumer.AcknowledgeAsync(message); } - + public void Dispose() { //Clean up resources that we have created diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_posting_a_message_via_the_messaging_gateway.cs similarity index 87% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_posting_a_message_via_the_messaging_gateway.cs index 195f34dd9f..73445afec9 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_posting_a_message_via_the_messaging_gateway.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_posting_a_message_via_the_messaging_gateway.cs @@ -7,7 +7,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Reactor; [Trait("Category", "AWS")] public class SqsMessageProducerSendTests : IDisposable, IAsyncDisposable @@ -26,7 +26,7 @@ public class SqsMessageProducerSendTests : IDisposable, IAsyncDisposable public SqsMessageProducerSendTests() { - _myCommand = new MyCommand{Value = "Test"}; + _myCommand = new MyCommand { Value = "Test" }; _correlationId = Guid.NewGuid().ToString(); _replyTo = "http:\\queueUrl"; _contentType = "text\\plain"; @@ -35,7 +35,7 @@ public SqsMessageProducerSendTests() _deduplicationId = $"DeduplicationId{Guid.NewGuid():N}"; var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(_topicName); - + var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), @@ -44,31 +44,28 @@ public SqsMessageProducerSendTests() rawMessageDelivery: false, sqsType: SnsSqsType.Fifo ); - + _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: _correlationId, replyTo: new RoutingKey(_replyTo), contentType: _contentType, partitionKey: _messageGroupId) { - Bag = - { - [HeaderNames.DeduplicationId] = _deduplicationId - } + Bag = { [HeaderNames.DeduplicationId] = _deduplicationId } }, - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); var awsConnection = GatewayFactory.CreateFactory(); - + _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateSyncChannel(subscription); - - _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication - { - Topic = new RoutingKey(_topicName), - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo, - Deduplication = true - }); + + _messageProducer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + Topic = new RoutingKey(_topicName), + MakeChannels = OnMissingChannel.Create, + SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } + }); } [Fact] @@ -79,9 +76,9 @@ public async Task When_posting_a_message_via_the_producer() _messageProducer.Send(_message); await Task.Delay(1000); - + var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - + //clear the queue _channel.Acknowledge(message); @@ -102,7 +99,7 @@ public async Task When_posting_a_message_via_the_producer() message.Header.Delayed.Should().Be(TimeSpan.Zero); //{"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} message.Body.Value.Should().Be(_message.Body.Value); - + message.Header.PartitionKey.Should().Be(_messageGroupId); message.Header.Bag.Should().ContainKey(HeaderNames.DeduplicationId); message.Header.Bag[HeaderNames.DeduplicationId].Should().Be(_deduplicationId); @@ -122,10 +119,9 @@ public async ValueTask DisposeAsync() await _channelFactory.DeleteQueueAsync(); await _messageProducer.DisposeAsync(); } - + private static DateTime RoundToSeconds(DateTime dateTime) { return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind); } - } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_queues_missing_assume_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_queues_missing_assume_throws.cs new file mode 100644 index 0000000000..80a5b85dcf --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_queues_missing_assume_throws.cs @@ -0,0 +1,67 @@ +using System; +using System.Threading.Tasks; +using Amazon.SQS.Model; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Reactor; + +[Trait("Category", "AWS")] +public class AWSAssumeQueuesTests : IDisposable, IAsyncDisposable +{ + private readonly ChannelFactory _channelFactory; + private readonly SqsMessageConsumer _consumer; + + public AWSAssumeQueuesTests() + { + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Assume, + sqsType: SnsSqsType.Fifo + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //create the topic, we want the queue to be the issue + //We need to create the topic at least, to check the queues + var producer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } + }); + + producer.ConfirmTopicExistsAsync(topicName).Wait(); + + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + //We need to create the topic at least, to check the queues + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); + } + + [Fact] + public void When_queues_missing_assume_throws() + { + //we will try to get the queue url, and fail because it does not exist + Assert.Throws(() => _consumer.Receive(TimeSpan.FromMilliseconds(1000))); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_queues_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_queues_missing_verify_throws.cs new file mode 100644 index 0000000000..c4ccaa6b86 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_queues_missing_verify_throws.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading.Tasks; +using Amazon.SQS.Model; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Reactor; + +[Trait("Category", "AWS")] +public class AWSValidateQueuesTests : IDisposable, IAsyncDisposable +{ + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; + private ChannelFactory _channelFactory; + + public AWSValidateQueuesTests() + { + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + _subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo + ); + + _awsConnection = GatewayFactory.CreateFactory(); + + //We need to create the topic at least, to check the queues + var producer = new SnsMessageProducer(_awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } + }); + producer.ConfirmTopicExistsAsync(topicName).Wait(); + } + + [Fact] + public void When_queues_missing_verify_throws() + { + //We have no queues so we should throw + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(_awsConnection); + Assert.Throws(() => _channelFactory.CreateSyncChannel(_subscription)); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_raw_message_delivery_disabled.cs similarity index 91% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_raw_message_delivery_disabled.cs index d46619cf3c..c83e0c6a76 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_raw_message_delivery_disabled.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_raw_message_delivery_disabled.cs @@ -7,7 +7,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -42,9 +42,7 @@ public SqsRawMessageDeliveryTests() _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo, - Deduplication = true + MakeChannels = OnMissingChannel.Create, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } }); } @@ -61,13 +59,7 @@ public void When_raw_message_delivery_disabled() correlationId: Guid.NewGuid().ToString(), replyTo: RoutingKey.Empty, contentType: "text\\plain", - partitionKey: messageGroupId) - { - Bag = - { - [HeaderNames.DeduplicationId] = deduplicationId - } - }; + partitionKey: messageGroupId) { Bag = { [HeaderNames.DeduplicationId] = deduplicationId } }; var customHeaderItem = new KeyValuePair("custom-header-item", "custom-header-item-value"); messageHeader.Bag.Add(customHeaderItem.Key, customHeaderItem.Value); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs similarity index 84% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs index 0ba3481f32..e3d4b80637 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_rejecting_a_message_through_gateway_with_requeue.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs @@ -7,7 +7,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -21,7 +21,7 @@ public class SqsMessageConsumerRequeueTests : IDisposable public SqsMessageConsumerRequeueTests() { - _myCommand = new MyCommand{Value = "Test"}; + _myCommand = new MyCommand { Value = "Test" }; const string replyTo = "http:\\queueUrl"; const string contentType = "text\\plain"; var correlationId = Guid.NewGuid().ToString(); @@ -29,7 +29,7 @@ public SqsMessageConsumerRequeueTests() var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; var topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); - + SqsSubscription subscription = new( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), @@ -37,25 +37,24 @@ public SqsMessageConsumerRequeueTests() routingKey: routingKey, sqsType: SnsSqsType.Fifo ); - + _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); - + //Must have credentials stored in the SDK Credentials store or shared credentials file var awsConnection = GatewayFactory.CreateFactory(); - + //We need to do this manually in a test - will create the channel from subscriber parameters _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateSyncChannel(subscription); - - _messageProducer = new SnsMessageProducer(awsConnection, + + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo + MakeChannels = OnMissingChannel.Create, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } }); } @@ -65,7 +64,7 @@ public void When_rejecting_a_message_through_gateway_with_requeue() _messageProducer.Send(_message); var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - + _channel.Reject(message); //Let the timeout change @@ -73,7 +72,7 @@ public void When_rejecting_a_message_through_gateway_with_requeue() //should requeue_the_message message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - + //clear the queue _channel.Acknowledge(message); @@ -82,13 +81,13 @@ public void When_rejecting_a_message_through_gateway_with_requeue() public void Dispose() { - _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteTopicAsync().Wait(); _channelFactory.DeleteQueueAsync().Wait(); } - + public async ValueTask DisposeAsync() { - await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteTopicAsync(); await _channelFactory.DeleteQueueAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_requeueing_a_message.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_requeueing_a_message.cs new file mode 100644 index 0000000000..6e74cb514b --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_requeueing_a_message.cs @@ -0,0 +1,85 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Reactor; + +[Trait("Category", "AWS")] +public class SqsMessageProducerRequeueTests : IDisposable, IAsyncDisposable +{ + private readonly IAmAMessageProducerSync _sender; + private Message _requeuedMessage; + private Message _receivedMessage; + private readonly IAmAChannelSync _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; + + public SqsMessageProducerRequeueTests() + { + MyCommand myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var channelName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + //Must have credentials stored in the SDK Credentials store or shared credentials file + var awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } + }); + + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); + } + + [Fact] + public void When_requeueing_a_message() + { + _sender.Send(_message); + _receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(_receivedMessage); + + _requeuedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + //clear the queue + _channel.Acknowledge(_requeuedMessage); + + _requeuedMessage.Body.Value.Should().Be(_receivedMessage.Body.Value); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_requeueing_redrives_to_the_dlq.cs similarity index 94% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_requeueing_redrives_to_the_dlq.cs index ee02080d57..6eebf5b660 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_redrives_to_the_dlq.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_requeueing_redrives_to_the_dlq.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Net; using System.Text.Json; using System.Threading.Tasks; @@ -11,7 +10,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -56,9 +55,7 @@ public SqsMessageProducerDlqTests() _sender = new SnsMessageProducer(_awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo, - Deduplication = true + MakeChannels = OnMissingChannel.Create, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } }); _sender.ConfirmTopicExistsAsync(topicName).Wait(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_throwing_defer_action_respect_redrive.cs similarity index 95% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_throwing_defer_action_respect_redrive.cs index c42262f587..42a899fe0b 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_throwing_defer_action_respect_redrive.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_throwing_defer_action_respect_redrive.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Net; using System.Text.Json; using System.Threading.Tasks; @@ -13,7 +12,7 @@ using Polly.Registry; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] @@ -69,11 +68,10 @@ public SnsReDrivePolicySDlqTests() _awsConnection, new SnsPublication { - Topic = routingKey, - RequestType = typeof(MyDeferredCommand), + Topic = routingKey, + RequestType = typeof(MyDeferredCommand), MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo, - Deduplication = true + SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } } ); @@ -113,7 +111,7 @@ public SnsReDrivePolicySDlqTests() private int GetDLQCount(string queueName) { - using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest { diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_topic_missing_verify_throws.cs similarity index 89% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_topic_missing_verify_throws.cs index 257119d332..98a944df4a 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_topic_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Fifo/Reactor/When_topic_missing_verify_throws.cs @@ -3,7 +3,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Fifo.Reactor; [Trait("Category", "AWS")] public class AWSValidateMissingTopicTests @@ -29,14 +29,14 @@ public void When_topic_missing_verify_throws() new SnsPublication { MakeChannels = OnMissingChannel.Validate, - SnsType = SnsSqsType.Fifo + SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } }); - + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; //act && assert Assert.Throws(() => producer.Send(new Message( - new MessageHeader("", _routingKey, MessageType.MT_EVENT, + new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text", partitionKey: messageGroupId), new MessageBody("Test")))); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs similarity index 98% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs index 0b40e19a22..93c8afc1f3 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_customising_aws_client_config_async.cs similarity index 97% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_customising_aws_client_config_async.cs index 99ad58b5cd..8e015cdb84 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_customising_aws_client_config_async.cs @@ -7,7 +7,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Proactor; [Trait("Category", "AWS")] public class CustomisingAwsClientConfigTestsAsync : IDisposable, IAsyncDisposable diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_infastructure_exists_can_assume_async.cs similarity index 97% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_infastructure_exists_can_assume_async.cs index 7dfa0c5a94..85ae4a3169 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_infastructure_exists_can_assume_async.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_arn.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_infastructure_exists_can_verify_by_arn.cs similarity index 97% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_arn.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_infastructure_exists_can_verify_by_arn.cs index 737575329d..a1f2e3e3b6 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_arn.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_infastructure_exists_can_verify_by_arn.cs @@ -4,14 +4,13 @@ using System.Threading.Tasks; using Amazon; using Amazon.Runtime; -using Amazon.SimpleNotificationService; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_infrastructure_exists_can_verify_async.cs similarity index 98% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_infrastructure_exists_can_verify_async.cs index f3478785de..37bbc50ffd 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_infrastructure_exists_can_verify_async.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Proactor { [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_arn_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_infrastructure_exists_can_verify_by_arn_async.cs similarity index 97% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_arn_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_infrastructure_exists_can_verify_by_arn_async.cs index 2cb54562d8..eb74572536 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_arn_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_infrastructure_exists_can_verify_by_arn_async.cs @@ -4,14 +4,13 @@ using System.Threading.Tasks; using Amazon; using Amazon.Runtime; -using Amazon.SimpleNotificationService; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs similarity index 98% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs index 50c21e5063..3aabf7a96b 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs @@ -7,7 +7,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Proactor; [Trait("Category", "AWS")] public class SqsMessageProducerSendAsyncTests : IAsyncDisposable, IDisposable diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_queues_missing_assume_throws_async.cs similarity index 96% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_queues_missing_assume_throws_async.cs index fdb549dbf0..00ebc34930 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_queues_missing_assume_throws_async.cs @@ -6,7 +6,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Proactor; [Trait("Category", "AWS")] public class AWSAssumeQueuesTestsAsync : IAsyncDisposable, IDisposable diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_queues_missing_verify_throws_async.cs similarity index 96% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_queues_missing_verify_throws_async.cs index 9325cc2955..4f3ce55ef5 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_queues_missing_verify_throws_async.cs @@ -6,7 +6,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Proactor; [Trait("Category", "AWS")] public class AWSValidateQueuesTestsAsync : IAsyncDisposable diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_raw_message_delivery_disabled_async.cs similarity index 97% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_raw_message_delivery_disabled_async.cs index eacc39f327..d4352dd889 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_raw_message_delivery_disabled_async.cs @@ -1,15 +1,13 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs similarity index 96% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs index 87de1aa60b..cf1691c408 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs @@ -1,15 +1,13 @@ using System; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_requeueing_a_message_async.cs similarity index 96% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_requeueing_a_message_async.cs index a919bfa1d5..81b7ba11e9 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_requeueing_a_message_async.cs @@ -1,8 +1,6 @@ using System; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using Amazon.Runtime.CredentialManagement; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; @@ -10,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Proactor; [Trait("Category", "AWS")] public class SqsMessageProducerRequeueTestsAsync : IDisposable, IAsyncDisposable diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_requeueing_redrives_to_the_dlq_async.cs similarity index 97% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_requeueing_redrives_to_the_dlq_async.cs index 3c0827cddb..923ee62abe 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_requeueing_redrives_to_the_dlq_async.cs @@ -3,8 +3,6 @@ using System.Net; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using Amazon.SQS; using Amazon.SQS.Model; using FluentAssertions; @@ -13,7 +11,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_throwing_defer_action_respect_redrive_async.cs similarity index 98% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_throwing_defer_action_respect_redrive_async.cs index fefd1a60a8..fe89a16d3a 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_throwing_defer_action_respect_redrive_async.cs @@ -3,8 +3,6 @@ using System.Net; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using Amazon.SQS; using Amazon.SQS.Model; using FluentAssertions; @@ -15,7 +13,7 @@ using Polly.Registry; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_topic_missing_verify_throws_async.cs similarity index 92% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_topic_missing_verify_throws_async.cs index 91cd06fb68..9e05510a16 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Proactor/When_topic_missing_verify_throws_async.cs @@ -1,12 +1,10 @@ using System; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Proactor; [Trait("Category", "AWS")] public class AWSValidateMissingTopicTestsAsync diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_a_message_consumer_reads_multiple_messages.cs similarity index 98% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_a_message_consumer_reads_multiple_messages.cs index 11cb902069..0abbe10adf 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_a_message_consumer_reads_multiple_messages.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_a_message_consumer_reads_multiple_messages.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_customising_aws_client_config.cs similarity index 97% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_customising_aws_client_config.cs index c23c2d70d1..142e62fc57 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_customising_aws_client_config.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_customising_aws_client_config.cs @@ -7,7 +7,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Reactor; [Trait("Category", "AWS")] public class CustomisingAwsClientConfigTests : IDisposable, IAsyncDisposable diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_infastructure_exists_can_assume.cs similarity index 97% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_infastructure_exists_can_assume.cs index c3d9d6aa08..12183aabd5 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_assume.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_infastructure_exists_can_assume.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_infastructure_exists_can_verify.cs similarity index 98% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_infastructure_exists_can_verify.cs index 18d4e601b0..bd69a5ea0d 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_infastructure_exists_can_verify.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_infastructure_exists_can_verify_by_convention.cs similarity index 98% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_convention.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_infastructure_exists_can_verify_by_convention.cs index 4b0a0da7d2..aa64e963e9 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infastructure_exists_can_verify_by_convention.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_infastructure_exists_can_verify_by_convention.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_infrastructure_exists_can_verify_by_convention.cs similarity index 97% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_convention.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_infrastructure_exists_can_verify_by_convention.cs index 6b712889fa..83b4dbf54c 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_infrastructure_exists_can_verify_by_convention.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_infrastructure_exists_can_verify_by_convention.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_posting_a_message_via_the_messaging_gateway.cs similarity index 98% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_posting_a_message_via_the_messaging_gateway.cs index 48de945144..eee916b9da 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_posting_a_message_via_the_messaging_gateway.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_posting_a_message_via_the_messaging_gateway.cs @@ -7,7 +7,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Reactor; [Trait("Category", "AWS")] public class SqsMessageProducerSendTests : IDisposable, IAsyncDisposable diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_queues_missing_assume_throws.cs similarity index 96% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_queues_missing_assume_throws.cs index 4afac9d990..69bedf1660 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_assume_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_queues_missing_assume_throws.cs @@ -6,7 +6,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Reactor; [Trait("Category", "AWS")] public class AWSAssumeQueuesTests : IDisposable, IAsyncDisposable diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_queues_missing_verify_throws.cs similarity index 96% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_queues_missing_verify_throws.cs index 37319b3ec5..478fc908d3 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_queues_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_queues_missing_verify_throws.cs @@ -6,7 +6,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Reactor; [Trait("Category", "AWS")] public class AWSValidateQueuesTests : IDisposable, IAsyncDisposable diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_raw_message_delivery_disabled.cs similarity index 97% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_raw_message_delivery_disabled.cs index c501cd0e82..6973f615a9 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_raw_message_delivery_disabled.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_raw_message_delivery_disabled.cs @@ -1,15 +1,13 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs similarity index 96% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs index a0751e950d..f20ce2b29f 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_rejecting_a_message_through_gateway_with_requeue.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs @@ -1,15 +1,13 @@ using System; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_requeueing_a_message.cs similarity index 96% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_requeueing_a_message.cs index 3380a80f77..ed053b3c5a 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_a_message.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_requeueing_a_message.cs @@ -1,8 +1,6 @@ using System; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using Amazon.Runtime.CredentialManagement; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; @@ -10,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Reactor; [Trait("Category", "AWS")] public class SqsMessageProducerRequeueTests : IDisposable, IAsyncDisposable diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_requeueing_redrives_to_the_dlq.cs similarity index 97% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_requeueing_redrives_to_the_dlq.cs index 33b29d823a..3b13e3ac6b 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_requeueing_redrives_to_the_dlq.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_requeueing_redrives_to_the_dlq.cs @@ -3,8 +3,6 @@ using System.Net; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using Amazon.SQS; using Amazon.SQS.Model; using FluentAssertions; @@ -13,7 +11,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_throwing_defer_action_respect_redrive.cs similarity index 98% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_throwing_defer_action_respect_redrive.cs index f73f02f7f0..8141f75b1c 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_throwing_defer_action_respect_redrive.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_throwing_defer_action_respect_redrive.cs @@ -3,8 +3,6 @@ using System.Net; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using Amazon.SQS; using Amazon.SQS.Model; using FluentAssertions; @@ -15,7 +13,7 @@ using Polly.Registry; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Reactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_topic_missing_verify_throws.cs similarity index 92% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_topic_missing_verify_throws.cs index 619bf9c5c7..2b1a698030 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Standard/When_topic_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sns/Standard/Reactor/When_topic_missing_verify_throws.cs @@ -1,11 +1,9 @@ using System; -using Amazon; -using Amazon.Runtime; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Standard; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sns.Standard.Reactor; [Trait("Category", "AWS")] public class AWSValidateMissingTopicTests diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs new file mode 100644 index 0000000000..6b29deafab --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Proactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SQSBufferedConsumerTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly SqsMessageProducer _messageProducer; + private readonly SqsMessageConsumer _consumer; + private readonly string _queueName; + private readonly ChannelFactory _channelFactory; + private const string ContentType = "text\\plain"; + private const int BufferSize = 3; + private const int MessageCount = 4; + + public SQSBufferedConsumerTestsAsync() + { + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + _queueName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + + //we need the channel to create the queues and notifications + var routingKey = new RoutingKey(_queueName); + + var channel = _channelFactory.CreateAsyncChannelAsync(new SqsSubscription( + name: new SubscriptionName(_queueName), + channelName: new ChannelName(_queueName), + routingKey: routingKey, + bufferSize: BufferSize, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + deduplicationScope: DeduplicationScope.MessageGroup, + fifoThroughputLimit: 1, + routingKeyType: RoutingKeyType.PointToPoint + )).GetAwaiter().GetResult(); + + //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel + //just for the tests, so create a new consumer from the properties + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(true), BufferSize); + _messageProducer = new SqsMessageProducer(awsConnection, + new SqsPublication + { + MakeChannels = OnMissingChannel.Create, SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + }); + } + + [Fact] + public async Task When_a_message_consumer_reads_multiple_messages_async() + { + var routingKey = new RoutingKey(_queueName); + + var messageGroupIdOne = $"MessageGroup{Guid.NewGuid():N}"; + var messageOne = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdOne), + new MessageBody("test content one") + ); + + var messageTwo = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdOne), + new MessageBody("test content two") + ); + + var messageGroupIdTwo = $"MessageGroup{Guid.NewGuid():N}"; + var deduplicationId = $"DeduplicationId{Guid.NewGuid():N}"; + var messageThree = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdTwo) + { + Bag = { [HeaderNames.DeduplicationId] = deduplicationId } + }, + new MessageBody("test content three") + ); + + var messageFour = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdTwo) + { + Bag = { [HeaderNames.DeduplicationId] = deduplicationId } + }, + new MessageBody("test content four") + ); + + var messageFive = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdTwo), + new MessageBody("test content four") + ); + + //send MESSAGE_COUNT messages + await _messageProducer.SendAsync(messageOne); + await _messageProducer.SendAsync(messageTwo); + await _messageProducer.SendAsync(messageThree); + await _messageProducer.SendAsync(messageFour); + await _messageProducer.SendAsync(messageFive); + + int iteration = 0; + var messagesReceived = new List(); + var messagesReceivedCount = messagesReceived.Count; + do + { + iteration++; + var outstandingMessageCount = MessageCount - messagesReceivedCount; + + //retrieve messages + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(10000)); + + messages.Length.Should().BeLessOrEqualTo(outstandingMessageCount); + + //should not receive more than buffer in one hit + messages.Length.Should().BeLessOrEqualTo(BufferSize); + + var moreMessages = messages.Where(m => m.Header.MessageType == MessageType.MT_COMMAND); + foreach (var message in moreMessages) + { + messagesReceived.Add(message); + await _consumer.AcknowledgeAsync(message); + } + + messagesReceivedCount = messagesReceived.Count; + + await Task.Delay(1000); + } while ((iteration <= 5) && (messagesReceivedCount < MessageCount)); + + messagesReceivedCount.Should().Be(4); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _messageProducer.DisposeAsync(); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().GetAwaiter().GetResult(); + _channelFactory.DeleteQueueAsync().GetAwaiter().GetResult(); + _messageProducer.DisposeAsync().GetAwaiter().GetResult(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infastructure_exists_can_assume_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infastructure_exists_can_assume_async.cs new file mode 100644 index 0000000000..0a19cd4592 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infastructure_exists_can_assume_async.cs @@ -0,0 +1,103 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Proactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSAssumeInfrastructureTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly SqsMessageConsumer _consumer; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSAssumeInfrastructureTestsAsync() + { + _myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(queueName), + channelName: new ChannelName(queueName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateAsyncChannel(subscription); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(queueName), + channelName: channel.Name, + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Assume, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint); + + _messageProducer = new SqsMessageProducer(awsConnection, + new SqsPublication + { + MakeChannels = OnMissingChannel.Assume, SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + }); + + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(true)); + } + + [Fact] + public async Task When_infastructure_exists_can_assume() + { + //arrange + await _messageProducer.SendAsync(_message); + + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + await _consumer.AcknowledgeAsync(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_async.cs new file mode 100644 index 0000000000..b4789433c1 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_async.cs @@ -0,0 +1,109 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Proactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumerAsync _consumer; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureTestsAsync() + { + _myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(queueName), + channelName: new ChannelName(queueName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateAsyncChannel(subscription); + + subscription = new( + name: new SubscriptionName(queueName), + channelName: channel.Name, + routingKey: routingKey, + findTopicBy: TopicFindBy.Name, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo + ); + + _messageProducer = new SqsMessageProducer( + awsConnection, + new SqsPublication + { + FindQueueBy= QueueFindBy.Name, + MakeChannels = OnMissingChannel.Validate, + Topic = new RoutingKey(queueName), + SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + } + ); + + _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify_async() + { + await _messageProducer.SendAsync(_message); + + await Task.Delay(1000); + + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + await _consumer.AcknowledgeAsync(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + ((IAmAMessageConsumerSync)_consumer).Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _consumer.DisposeAsync(); + await _messageProducer.DisposeAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs new file mode 100644 index 0000000000..89adfe2cd1 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs @@ -0,0 +1,118 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Proactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureByUrlTestsAsync : IAsyncDisposable, IDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumerAsync _consumer; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureByUrlTestsAsync() + { + _myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(queueName), + channelName: new ChannelName(queueName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateAsyncChannel(subscription); + + var queueUrl = FindQueueUrl(awsConnection, routingKey.ToValidSQSQueueName(true)).Result; + + subscription = new( + name: new SubscriptionName(queueName), + channelName: channel.Name, + routingKey: routingKey, + queueFindBy: QueueFindBy.Url, + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint + ); + + _messageProducer = new SqsMessageProducer( + awsConnection, + new SqsPublication + { + Topic = routingKey, + QueueUrl = queueUrl, + FindQueueBy = QueueFindBy.Url, + MakeChannels = OnMissingChannel.Validate, + SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + }); + + _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify_async() + { + await _messageProducer.SendAsync(_message); + + await Task.Delay(1000); + + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + await _consumer.AcknowledgeAsync(message); + } + + private static async Task FindQueueUrl(AWSMessagingGatewayConnection connection, string queueName) + { + using var snsClient = new AWSClientFactory(connection).CreateSqsClient(); + var topicResponse = await snsClient.GetQueueUrlAsync(queueName); + return topicResponse.QueueUrl; + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + ((IAmAMessageConsumerSync)_consumer).Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _consumer.DisposeAsync(); + await _messageProducer.DisposeAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs new file mode 100644 index 0000000000..c75f25a17d --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs @@ -0,0 +1,127 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Proactor; + +[Trait("Category", "AWS")] +public class SqsMessageProducerSendAsyncTests : IAsyncDisposable, IDisposable +{ + private readonly Message _message; + private readonly IAmAChannelAsync _channel; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + private readonly string _correlationId; + private readonly string _replyTo; + private readonly string _contentType; + private readonly string _queueName; + private readonly string _messageGroupId; + private readonly string _deduplicationId; + + public SqsMessageProducerSendAsyncTests() + { + _myCommand = new MyCommand { Value = "Test" }; + _correlationId = Guid.NewGuid().ToString(); + _replyTo = "http:\\queueUrl"; + _contentType = "text\\plain"; + _messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + _deduplicationId = $"DeduplicationId{Guid.NewGuid():N}"; + _queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(_queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(_queueName), + channelName: new ChannelName(_queueName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + rawMessageDelivery: true, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: _correlationId, + replyTo: new RoutingKey(_replyTo), contentType: _contentType, partitionKey: _messageGroupId) + { + Bag = { [HeaderNames.DeduplicationId] = _deduplicationId } + }, + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateAsyncChannel(subscription); + + _messageProducer = new SqsMessageProducer(awsConnection, + new SqsPublication + { + Topic = new RoutingKey(_queueName), + MakeChannels = OnMissingChannel.Create, + SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + }); + } + + [Fact] + public async Task When_posting_a_message_via_the_producer_async() + { + // arrange + _message.Header.Subject = "test subject"; + await _messageProducer.SendAsync(_message); + + await Task.Delay(1000); + + var message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + // clear the queue + await _channel.AcknowledgeAsync(message); + + // should_send_the_message_to_aws_sqs + message.Header.MessageType.Should().Be(MessageType.MT_COMMAND); + + message.Id.Should().Be(_myCommand.Id); + message.Redelivered.Should().BeFalse(); + message.Header.MessageId.Should().Be(_myCommand.Id); + message.Header.Topic.Value.Should().Contain(_queueName); + message.Header.CorrelationId.Should().Be(_correlationId); + message.Header.ReplyTo.Should().Be(_replyTo); + message.Header.ContentType.Should().Be(_contentType); + message.Header.HandledCount.Should().Be(0); + message.Header.Subject.Should().Be(_message.Header.Subject); + // allow for clock drift in the following test, more important to have a contemporary timestamp than anything + message.Header.TimeStamp.Should().BeAfter(RoundToSeconds(DateTime.UtcNow.AddMinutes(-1))); + message.Header.Delayed.Should().Be(TimeSpan.Zero); + // {"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} + message.Body.Value.Should().Be(_message.Body.Value); + + message.Header.PartitionKey.Should().Be(_messageGroupId); + message.Header.Bag.Should().ContainKey(HeaderNames.DeduplicationId); + message.Header.Bag[HeaderNames.DeduplicationId].Should().Be(_deduplicationId); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _messageProducer.DisposeAsync(); + } + + private static DateTime RoundToSeconds(DateTime dateTime) + { + return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_queues_missing_assume_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_queues_missing_assume_throws_async.cs new file mode 100644 index 0000000000..11893c79cb --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_queues_missing_assume_throws_async.cs @@ -0,0 +1,59 @@ +using System; +using System.Threading.Tasks; +using Amazon.SQS.Model; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Proactor; + +[Trait("Category", "AWS")] +public class AWSAssumeQueuesTestsAsync : IAsyncDisposable, IDisposable +{ + private readonly ChannelFactory _channelFactory; + private readonly IAmAMessageConsumerAsync _consumer; + + public AWSAssumeQueuesTestsAsync() + { + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), + routingKey: routingKey, + makeChannels: OnMissingChannel.Assume, + messagePumpType: MessagePumpType.Proactor, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + _ = _channelFactory.CreateAsyncChannel(subscription); + + //We need to create the topic at least, to check the queues + _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); + } + + [Fact] + public async Task When_queues_missing_assume_throws_async() + { + //we will try to get the queue url, and fail because it does not exist + await Assert.ThrowsAsync(async () => + await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(1000))); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_queues_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_queues_missing_verify_throws_async.cs new file mode 100644 index 0000000000..a437dbc79d --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_queues_missing_verify_throws_async.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading.Tasks; +using Amazon.SQS.Model; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Proactor; + +[Trait("Category", "AWS")] +public class AWSValidateQueuesTestsAsync : IAsyncDisposable +{ + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; + private ChannelFactory _channelFactory; + + public AWSValidateQueuesTestsAsync() + { + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); + + _subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), + routingKey: routingKey, + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint + ); + + _awsConnection = GatewayFactory.CreateFactory(); + } + + [Fact] + public async Task When_queues_missing_verify_throws_async() + { + // We have no queues so we should throw + // We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(_awsConnection); + await Assert.ThrowsAsync(async () => + await _channelFactory.CreateAsyncChannelAsync(_subscription)); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_raw_message_delivery_disabled_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_raw_message_delivery_disabled_async.cs new file mode 100644 index 0000000000..962c7a7eb9 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_raw_message_delivery_disabled_async.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Proactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsRawMessageDeliveryTestsAsync : IAsyncDisposable, IDisposable +{ + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly IAmAChannelAsync _channel; + private readonly RoutingKey _routingKey; + + public SqsRawMessageDeliveryTestsAsync() + { + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + var queueName = $"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey(queueName); + + const int bufferSize = 10; + + // Set rawMessageDelivery to false + _channel = _channelFactory.CreateAsyncChannel(new SqsSubscription( + name: new SubscriptionName(queueName), + channelName: new ChannelName(queueName), + routingKey: _routingKey, + bufferSize: bufferSize, + makeChannels: OnMissingChannel.Create, + rawMessageDelivery: true, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint)); + + _messageProducer = new SqsMessageProducer(awsConnection, + new SqsPublication + { + MakeChannels = OnMissingChannel.Create, SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + }); + } + + [Fact] + public async Task When_raw_message_delivery_disabled_async() + { + // Arrange + var messageGroupId = $"MessageGroupId{Guid.NewGuid():N}"; + var deduplicationId = $"DeduplicationId{Guid.NewGuid():N}"; + var messageHeader = new MessageHeader( + Guid.NewGuid().ToString(), + _routingKey, + MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), + replyTo: RoutingKey.Empty, + contentType: "text\\plain", + partitionKey: messageGroupId) { Bag = { [HeaderNames.DeduplicationId] = deduplicationId } }; + + var customHeaderItem = new KeyValuePair("custom-header-item", "custom-header-item-value"); + messageHeader.Bag.Add(customHeaderItem.Key, customHeaderItem.Value); + + var messageToSend = new Message(messageHeader, new MessageBody("test content one")); + + // Act + await _messageProducer.SendAsync(messageToSend); + + var messageReceived = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(10000)); + + await _channel.AcknowledgeAsync(messageReceived); + + // Assert + messageReceived.Id.Should().Be(messageToSend.Id); + messageReceived.Header.Topic.Should().Be(messageToSend.Header.Topic.ToValidSNSTopicName(true)); + messageReceived.Header.MessageType.Should().Be(messageToSend.Header.MessageType); + messageReceived.Header.CorrelationId.Should().Be(messageToSend.Header.CorrelationId); + messageReceived.Header.ReplyTo.Should().Be(messageToSend.Header.ReplyTo); + messageReceived.Header.ContentType.Should().Be(messageToSend.Header.ContentType); + messageReceived.Header.Bag.Should().ContainKey(customHeaderItem.Key).And.ContainValue(customHeaderItem.Value); + messageReceived.Body.Value.Should().Be(messageToSend.Body.Value); + messageReceived.Header.PartitionKey.Should().Be(messageGroupId); + messageReceived.Header.Bag.Should().ContainKey(HeaderNames.DeduplicationId); + messageReceived.Header.Bag[HeaderNames.DeduplicationId].Should().Be(deduplicationId); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs new file mode 100644 index 0000000000..ee6c7e8ab0 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs @@ -0,0 +1,92 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Proactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageConsumerRequeueTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly IAmAChannelAsync _channel; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public SqsMessageConsumerRequeueTestsAsync() + { + _myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var queueName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(queueName), + channelName: new ChannelName(queueName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateAsyncChannel(subscription); + + _messageProducer = new SqsMessageProducer(awsConnection, + new SqsPublication + { + MakeChannels = OnMissingChannel.Create, SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + }); + } + + [Fact] + public async Task When_rejecting_a_message_through_gateway_with_requeue_async() + { + await _messageProducer.SendAsync(_message); + + var message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + await _channel.RejectAsync(message); + + // Let the timeout change + await Task.Delay(TimeSpan.FromMilliseconds(3000)); + + // should requeue_the_message + message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + // clear the queue + await _channel.AcknowledgeAsync(message); + + message.Id.Should().Be(_myCommand.Id); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_requeueing_a_message_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_requeueing_a_message_async.cs new file mode 100644 index 0000000000..99dbb921e3 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_requeueing_a_message_async.cs @@ -0,0 +1,86 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Proactor; + +[Trait("Category", "AWS")] +public class SqsMessageProducerRequeueTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly IAmAMessageProducerAsync _sender; + private Message _requeuedMessage; + private Message _receivedMessage; + private readonly IAmAChannelAsync _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; + + public SqsMessageProducerRequeueTestsAsync() + { + MyCommand myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var subcriptionName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(subcriptionName), + channelName: new ChannelName(queueName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SqsMessageProducer(awsConnection, + new SqsPublication + { + MakeChannels = OnMissingChannel.Create, SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + }); + + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateAsyncChannel(subscription); + } + + [Fact] + public async Task When_requeueing_a_message_async() + { + await _sender.SendAsync(_message); + _receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + await _channel.RequeueAsync(_receivedMessage); + + _requeuedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + await _channel.AcknowledgeAsync(_requeuedMessage); + + _requeuedMessage.Body.Value.Should().Be(_receivedMessage.Body.Value); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_requeueing_redrives_to_the_dlq_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_requeueing_redrives_to_the_dlq_async.cs new file mode 100644 index 0000000000..23483c3134 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_requeueing_redrives_to_the_dlq_async.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.SQS; +using Amazon.SQS.Model; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Proactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageProducerDlqTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly SqsMessageProducer _sender; + private readonly IAmAChannelAsync _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly string _dlqChannelName; + + public SqsMessageProducerDlqTestsAsync() + { + MyCommand myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + _dlqChannelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var correlationId = Guid.NewGuid().ToString(); + var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(queueName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + redrivePolicy: new RedrivePolicy(_dlqChannelName, 2), + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + _awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SqsMessageProducer(_awsConnection, + new SqsPublication + { + MakeChannels = OnMissingChannel.Create, SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + }); + + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateAsyncChannel(subscription); + } + + [Fact] + public async Task When_requeueing_redrives_to_the_queue_async() + { + await _sender.SendAsync(_message); + var receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + await _channel.RequeueAsync(receivedMessage); + + receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + await _channel.RequeueAsync(receivedMessage); + + receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + await _channel.RequeueAsync(receivedMessage); + + await Task.Delay(5000); + + int dlqCount = await GetDLQCountAsync(_dlqChannelName.ToValidSQSQueueName(true)); + dlqCount.Should().Be(1); + } + + private async Task GetDLQCountAsync(string queueName) + { + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); + var queueUrlResponse = await sqsClient.GetQueueUrlAsync(queueName); + var response = await sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest + { + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageAttributeNames = ["All", "ApproximateReceiveCount"] + }); + + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new AmazonSQSException( + $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + } + + return response.Messages.Count; + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_throwing_defer_action_respect_redrive_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_throwing_defer_action_respect_redrive_async.cs new file mode 100644 index 0000000000..6a370316d4 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_throwing_defer_action_respect_redrive_async.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.SQS; +using Amazon.SQS.Model; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Paramore.Brighter.ServiceActivator; +using Polly.Registry; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Proactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SnsReDrivePolicySDlqTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly IAmAMessagePump _messagePump; + private readonly Message _message; + private readonly string _dlqChannelName; + private readonly IAmAChannelAsync _channel; + private readonly SqsMessageProducer _sender; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; + private readonly ChannelFactory _channelFactory; + + public SnsReDrivePolicySDlqTestsAsync() + { + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + _dlqChannelName = $"Redrive-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(queueName); + + _subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), + routingKey: routingKey, + requeueCount: -1, + requeueDelay: TimeSpan.FromMilliseconds(50), + messagePumpType: MessagePumpType.Proactor, + redrivePolicy: new RedrivePolicy(new ChannelName(_dlqChannelName), 2), + routingKeyType: RoutingKeyType.PointToPoint + ); + + var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + _awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SqsMessageProducer( + _awsConnection, + new SqsPublication + { + Topic = routingKey, + RequestType = typeof(MyDeferredCommand), + MakeChannels = OnMissingChannel.Create, + SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + } + ); + + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateAsyncChannel(_subscription); + + IHandleRequestsAsync handler = new MyDeferredCommandHandlerAsync(); + + var subscriberRegistry = new SubscriberRegistry(); + subscriberRegistry.RegisterAsync(); + + IAmACommandProcessor commandProcessor = new CommandProcessor( + subscriberRegistry: subscriberRegistry, + handlerFactory: new QuickHandlerFactoryAsync(() => handler), + requestContextFactory: new InMemoryRequestContextFactory(), + policyRegistry: new PolicyRegistry() + ); + var provider = new CommandProcessorProvider(commandProcessor); + + var messageMapperRegistry = new MessageMapperRegistry( + new SimpleMessageMapperFactory(_ => new MyDeferredCommandMessageMapper()), + null + ); + messageMapperRegistry.Register(); + + _messagePump = new Proactor(provider, messageMapperRegistry, + new EmptyMessageTransformerFactoryAsync(), new InMemoryRequestContextFactory(), _channel) + { + Channel = _channel, + TimeOut = TimeSpan.FromMilliseconds(5000), + RequeueCount = 3 + }; + } + + public async Task GetDLQCountAsync(string queueName) + { + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); + var queueUrlResponse = await sqsClient.GetQueueUrlAsync(queueName); + var response = await sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest + { + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageSystemAttributeNames = new List { "ApproximateReceiveCount" }, + MessageAttributeNames = new List { "All" } + }); + + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new AmazonSQSException( + $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + } + + return response.Messages.Count; + } + + [Fact(Skip = "Failing async tests caused by task scheduler issues")] + public async Task When_throwing_defer_action_respect_redrive_async() + { + await _sender.SendAsync(_message); + + var task = Task.Factory.StartNew(() => _messagePump.Run(), TaskCreationOptions.LongRunning); + await Task.Delay(5000); + + var quitMessage = MessageFactory.CreateQuitMessage(_subscription.RoutingKey); + _channel.Enqueue(quitMessage); + + await Task.WhenAll(task); + + await Task.Delay(5000); + + var dlqCount = await GetDLQCountAsync(_dlqChannelName + ".fifo"); + dlqCount.Should().Be(1); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_topic_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_topic_missing_verify_throws_async.cs new file mode 100644 index 0000000000..8355763476 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_topic_missing_verify_throws_async.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading.Tasks; +using Amazon.SQS.Model; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Proactor; + +[Trait("Category", "AWS")] +public class AWSValidateMissingTopicTestsAsync +{ + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly RoutingKey _routingKey; + + public AWSValidateMissingTopicTestsAsync() + { + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey(queueName); + + _awsConnection = GatewayFactory.CreateFactory(); + + // Because we don't use channel factory to create the infrastructure - it won't exist + } + + [Fact] + public async Task When_topic_missing_verify_throws_async() + { + // arrange + var producer = new SqsMessageProducer(_awsConnection, + new SqsPublication + { + MakeChannels = OnMissingChannel.Validate, + SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + }); + + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + + // act & assert + await Assert.ThrowsAsync(async () => + await producer.SendAsync(new Message( + new MessageHeader("", _routingKey, MessageType.MT_EVENT, + type: "plain/text", partitionKey: messageGroupId), + new MessageBody("Test")))); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_a_message_consumer_reads_multiple_messages.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_a_message_consumer_reads_multiple_messages.cs new file mode 100644 index 0000000000..febb854ad4 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_a_message_consumer_reads_multiple_messages.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Reactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SQSBufferedConsumerTests : IDisposable, IAsyncDisposable +{ + private readonly SqsMessageProducer _messageProducer; + private readonly SqsMessageConsumer _consumer; + private readonly string _queueName; + private readonly ChannelFactory _channelFactory; + private const string ContentType = "text\\plain"; + private const int BufferSize = 3; + private const int MessageCount = 4; + + public SQSBufferedConsumerTests() + { + var awsConnection = GatewayFactory.CreateFactory(); + _channelFactory = new ChannelFactory(awsConnection); + + var subscriptionName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _queueName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + + //we need the channel to create the queues and notifications + var routingKey = new RoutingKey(_queueName); + + var channel = _channelFactory.CreateSyncChannel(new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(_queueName), + routingKey: routingKey, + bufferSize: BufferSize, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + contentBasedDeduplication: true, + deduplicationScope: DeduplicationScope.MessageGroup, + fifoThroughputLimit: 1, + routingKeyType: RoutingKeyType.PointToPoint + )); + + //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel + //just for the tests, so create a new consumer from the properties + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(true), BufferSize); + _messageProducer = new SqsMessageProducer(awsConnection, + new SqsPublication + { + MakeChannels = OnMissingChannel.Create, + SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + }); + } + + [Fact] + public async Task When_a_message_consumer_reads_multiple_messages() + { + var routingKey = new RoutingKey(_queueName); + + var messageGroupIdOne = $"MessageGroup{Guid.NewGuid():N}"; + var messageOne = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdOne), + new MessageBody("test content one") + ); + + var messageTwo = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdOne), + new MessageBody("test content two") + ); + + + var messageGroupIdTwo = $"MessageGroup{Guid.NewGuid():N}"; + var deduplicationId = $"DeduplicationId{Guid.NewGuid():N}"; + + var messageThree = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdTwo) + { + Bag = { [HeaderNames.DeduplicationId] = deduplicationId } + }, + new MessageBody("test content three") + ); + + var messageFour = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdTwo) + { + Bag = { [HeaderNames.DeduplicationId] = deduplicationId } + }, + new MessageBody("test content four") + ); + + var messageFive = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType, partitionKey: messageGroupIdTwo), + new MessageBody("test content four") + ); + + //send MESSAGE_COUNT messages + _messageProducer.Send(messageOne); + _messageProducer.Send(messageTwo); + _messageProducer.Send(messageThree); + _messageProducer.Send(messageFour); + _messageProducer.Send(messageFive); + + + int iteration = 0; + var messagesReceived = new List(); + var messagesReceivedCount = messagesReceived.Count; + do + { + iteration++; + var outstandingMessageCount = MessageCount - messagesReceivedCount; + + //retrieve messages + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(10000)); + + messages.Length.Should().BeLessOrEqualTo(outstandingMessageCount); + + //should not receive more than buffer in one hit + messages.Length.Should().BeLessOrEqualTo(BufferSize); + + var moreMessages = messages.Where(m => m.Header.MessageType == MessageType.MT_COMMAND); + foreach (var message in moreMessages) + { + messagesReceived.Add(message); + _consumer.Acknowledge(message); + } + + messagesReceivedCount = messagesReceived.Count; + + await Task.Delay(1000); + } while ((iteration <= 5) && (messagesReceivedCount < MessageCount)); + + + messagesReceivedCount.Should().Be(4); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageProducerAsync)_messageProducer).DisposeAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_assume.cs new file mode 100644 index 0000000000..d08c5eb964 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_assume.cs @@ -0,0 +1,103 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Reactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSAssumeInfrastructureTests : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly SqsMessageConsumer _consumer; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSAssumeInfrastructureTests() + { + _myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(queueName), + channelName: new ChannelName(queueName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(queueName), + channelName: channel.Name, + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Assume, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint); + + _messageProducer = new SqsMessageProducer(awsConnection, + new SqsPublication + { + MakeChannels = OnMissingChannel.Assume, SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + }); + + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(true)); + } + + [Fact] + public void When_infastructure_exists_can_assume() + { + //arrange + _messageProducer.Send(_message); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_verify.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_verify.cs new file mode 100644 index 0000000000..dce16c2e0d --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_verify.cs @@ -0,0 +1,117 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Reactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureTests : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumerSync _consumer; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureTests() + { + _myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(subscriptionName), + channelName: channel.Name, + routingKey: routingKey, + findTopicBy: TopicFindBy.Name, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo + ); + + _messageProducer = new SqsMessageProducer( + awsConnection, + new SqsPublication + { + FindQueueBy = QueueFindBy.Name, + MakeChannels = OnMissingChannel.Validate, + Topic = new RoutingKey(queueName), + SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + } + ); + + _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify() + { + //arrange + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _consumer.Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); + await _messageProducer.DisposeAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_verify_by_url.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_verify_by_url.cs new file mode 100644 index 0000000000..92a53c64e8 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_verify_by_url.cs @@ -0,0 +1,128 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Reactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureByUrlTests : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumerSync _consumer; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureByUrlTests () + { + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + + _myCommand = new MyCommand { Value = "Test" }; + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + var queueUrl = FindQueueUrl(awsConnection, routingKey.ToValidSQSQueueName(true)); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(subscriptionName), + channelName: channel.Name, + routingKey: routingKey, + findTopicBy: TopicFindBy.Arn, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint + ); + + _messageProducer = new SqsMessageProducer( + awsConnection, + new SqsPublication + { + Topic = routingKey, + QueueUrl = queueUrl, + FindQueueBy = QueueFindBy.Url, + MakeChannels = OnMissingChannel.Validate, + SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + }); + + _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify() + { + //arrange + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _consumer.Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); + await _messageProducer.DisposeAsync(); + } + + private static string FindQueueUrl(AWSMessagingGatewayConnection connection, string queueName) + { + using var snsClient = new AWSClientFactory(connection).CreateSqsClient(); + var topicResponse = snsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); + return topicResponse.QueueUrl; + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_posting_a_message_via_the_messaging_gateway.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_posting_a_message_via_the_messaging_gateway.cs new file mode 100644 index 0000000000..ab5d48d695 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_posting_a_message_via_the_messaging_gateway.cs @@ -0,0 +1,128 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Reactor; + +[Trait("Category", "AWS")] +public class SqsMessageProducerSendAsyncTests : IAsyncDisposable, IDisposable +{ + private readonly Message _message; + private readonly IAmAChannelSync _channel; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + private readonly string _correlationId; + private readonly string _replyTo; + private readonly string _contentType; + private readonly string _queueName; + private readonly string _messageGroupId; + private readonly string _deduplicationId; + + public SqsMessageProducerSendAsyncTests() + { + _myCommand = new MyCommand { Value = "Test" }; + _correlationId = Guid.NewGuid().ToString(); + _replyTo = "http:\\queueUrl"; + _contentType = "text\\plain"; + _messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + _deduplicationId = $"DeduplicationId{Guid.NewGuid():N}"; + _queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(_queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(_queueName), + channelName: new ChannelName(_queueName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + rawMessageDelivery: true, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: _correlationId, + replyTo: new RoutingKey(_replyTo), contentType: _contentType, partitionKey: _messageGroupId) + { + Bag = { [HeaderNames.DeduplicationId] = _deduplicationId } + }, + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); + + _messageProducer = new SqsMessageProducer(awsConnection, + new SqsPublication + { + Topic = new RoutingKey(_queueName), + MakeChannels = OnMissingChannel.Create, + SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + }); + } + + [Fact] + public void When_posting_a_message_via_the_producer() + { + // arrange + _message.Header.Subject = "test subject"; + _messageProducer.Send(_message); + + Task.Delay(1000).GetAwaiter().GetResult(); + + var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + // clear the queue + _channel.Acknowledge(message); + + // should_send_the_message_to_aws_sqs + message.Header.MessageType.Should().Be(MessageType.MT_COMMAND); + + message.Id.Should().Be(_myCommand.Id); + message.Redelivered.Should().BeFalse(); + message.Header.MessageId.Should().Be(_myCommand.Id); + message.Header.Topic.Value.Should().Contain(_queueName); + message.Header.CorrelationId.Should().Be(_correlationId); + message.Header.ReplyTo.Should().Be(_replyTo); + message.Header.ContentType.Should().Be(_contentType); + message.Header.HandledCount.Should().Be(0); + message.Header.Subject.Should().Be(_message.Header.Subject); + // allow for clock drift in the following test, more important to have a contemporary timestamp than anything + message.Header.TimeStamp.Should().BeAfter(RoundToSeconds(DateTime.UtcNow.AddMinutes(-1))); + message.Header.Delayed.Should().Be(TimeSpan.Zero); + // {"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} + message.Body.Value.Should().Be(_message.Body.Value); + + message.Header.PartitionKey.Should().Be(_messageGroupId); + message.Header.Bag.Should().ContainKey(HeaderNames.DeduplicationId); + message.Header.Bag[HeaderNames.DeduplicationId].Should().Be(_deduplicationId); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _messageProducer.DisposeAsync(); + } + + private static DateTime RoundToSeconds(DateTime dateTime) + { + return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind); + } +} + diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_queues_missing_assume_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_queues_missing_assume_throws.cs new file mode 100644 index 0000000000..a3c051278f --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_queues_missing_assume_throws.cs @@ -0,0 +1,67 @@ +using System; +using System.Threading.Tasks; +using Amazon.SQS.Model; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Reactor; + +[Trait("Category", "AWS")] +public class AWSAssumeQueuesTests : IDisposable, IAsyncDisposable +{ + private readonly ChannelFactory _channelFactory; + private readonly SqsMessageConsumer _consumer; + + public AWSAssumeQueuesTests() + { + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Assume, + sqsType: SnsSqsType.Fifo + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //create the topic, we want the queue to be the issue + //We need to create the topic at least, to check the queues + var producer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } + }); + + producer.ConfirmTopicExistsAsync(topicName).Wait(); + + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + //We need to create the topic at least, to check the queues + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); + } + + [Fact] + public void When_queues_missing_assume_throws() + { + //we will try to get the queue url, and fail because it does not exist + Assert.Throws(() => _consumer.Receive(TimeSpan.FromMilliseconds(1000))); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_queues_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_queues_missing_verify_throws.cs new file mode 100644 index 0000000000..e83a9855ba --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_queues_missing_verify_throws.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading.Tasks; +using Amazon.SQS.Model; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Reactor; + +[Trait("Category", "AWS")] +public class AWSValidateQueuesTests : IAsyncDisposable +{ + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; + private ChannelFactory _channelFactory; + + public AWSValidateQueuesTests() + { + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); + + _subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), + routingKey: routingKey, + makeChannels: OnMissingChannel.Validate, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint + ); + + _awsConnection = GatewayFactory.CreateFactory(); + } + + [Fact] + public void When_queues_missing_verify_throws() + { + // We have no queues so we should throw + // We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(_awsConnection); + Assert.Throws(() => _channelFactory.CreateAsyncChannel(_subscription)); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs new file mode 100644 index 0000000000..1f70bf6309 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs @@ -0,0 +1,92 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Reactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageConsumerRequeueTests : IDisposable +{ + private readonly Message _message; + private readonly IAmAChannelSync _channel; + private readonly SqsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public SqsMessageConsumerRequeueTests() + { + _myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var queueName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(queueName), + channelName: new ChannelName(queueName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); + + _messageProducer = new SqsMessageProducer(awsConnection, + new SqsPublication + { + MakeChannels = OnMissingChannel.Create, SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + }); + } + + [Fact] + public void When_rejecting_a_message_through_gateway_with_requeue() + { + _messageProducer.Send(_message); + + var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + _channel.Reject(message); + + // Let the timeout change + Task.Delay(TimeSpan.FromMilliseconds(3000)).GetAwaiter().GetResult(); + + // should requeue_the_message + message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + // clear the queue + _channel.Acknowledge(message); + + message.Id.Should().Be(_myCommand.Id); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_requeueing_a_message.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_requeueing_a_message.cs new file mode 100644 index 0000000000..d93ea1c6ae --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_requeueing_a_message.cs @@ -0,0 +1,86 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Reactor; + +[Trait("Category", "AWS")] +public class SqsMessageProducerRequeueTests : IDisposable, IAsyncDisposable +{ + private readonly IAmAMessageProducerSync _sender; + private Message _requeuedMessage; + private Message _receivedMessage; + private readonly IAmAChannelSync _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; + + public SqsMessageProducerRequeueTests() + { + MyCommand myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create, + sqsType: SnsSqsType.Fifo, + routingKeyType: RoutingKeyType.PointToPoint + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SqsMessageProducer(awsConnection, + new SqsPublication + { + MakeChannels = OnMissingChannel.Create, SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + }); + + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); + } + + [Fact] + public void When_requeueing_a_message() + { + _sender.Send(_message); + _receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(_receivedMessage); + + _requeuedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + _channel.Acknowledge(_requeuedMessage); + + _requeuedMessage.Body.Value.Should().Be(_receivedMessage.Body.Value); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_requeueing_redrives_to_the_dlq.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_requeueing_redrives_to_the_dlq.cs new file mode 100644 index 0000000000..3add76a4c5 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_requeueing_redrives_to_the_dlq.cs @@ -0,0 +1,119 @@ +using System; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.SQS; +using Amazon.SQS.Model; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Reactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageProducerDlqTests : IDisposable, IAsyncDisposable +{ + private readonly SnsMessageProducer _sender; + private readonly IAmAChannelSync _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly string _dlqChannelName; + + public SqsMessageProducerDlqTests() + { + MyCommand myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + _dlqChannelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var correlationId = Guid.NewGuid().ToString(); + var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + redrivePolicy: new RedrivePolicy(_dlqChannelName, 2), + sqsType: SnsSqsType.Fifo + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + //Must have credentials stored in the SDK Credentials store or shared credentials file + _awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SnsMessageProducer(_awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create, SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } + }); + + _sender.ConfirmTopicExistsAsync(topicName).Wait(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); + } + + [Fact] + public void When_requeueing_redrives_to_the_queue() + { + _sender.Send(_message); + var receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(receivedMessage); + + receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(receivedMessage); + + //should force us into the dlq + receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(receivedMessage); + + Task.Delay(5000); + + //inspect the dlq + GetDLQCount(_dlqChannelName + ".fifo").Should().Be(1); + } + + private int GetDLQCount(string queueName) + { + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); + var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); + var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest + { + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageAttributeNames = ["All", "ApproximateReceiveCount"] + }).GetAwaiter().GetResult(); + + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new AmazonSQSException( + $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + } + + return response.Messages.Count; + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_throwing_defer_action_respect_redrive.cs new file mode 100644 index 0000000000..672a81e83d --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_throwing_defer_action_respect_redrive.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.SQS; +using Amazon.SQS.Model; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Paramore.Brighter.ServiceActivator; +using Polly.Registry; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Reactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SnsReDrivePolicySDlqTests : IDisposable, IAsyncDisposable +{ + private readonly IAmAMessagePump _messagePump; + private readonly Message _message; + private readonly string _dlqChannelName; + private readonly IAmAChannelSync _channel; + private readonly SqsMessageProducer _sender; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; + private readonly ChannelFactory _channelFactory; + + public SnsReDrivePolicySDlqTests() + { + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + _dlqChannelName = $"Redrive-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + var routingKey = new RoutingKey(queueName); + + _subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), + routingKey: routingKey, + requeueCount: -1, + requeueDelay: TimeSpan.FromMilliseconds(50), + messagePumpType: MessagePumpType.Proactor, + redrivePolicy: new RedrivePolicy(new ChannelName(_dlqChannelName), 2), + routingKeyType: RoutingKeyType.PointToPoint + ); + + var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + _awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SqsMessageProducer( + _awsConnection, + new SqsPublication + { + Topic = routingKey, + RequestType = typeof(MyDeferredCommand), + MakeChannels = OnMissingChannel.Create, + SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + } + ); + + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateSyncChannel(_subscription); + + IHandleRequestsAsync handler = new MyDeferredCommandHandlerAsync(); + + var subscriberRegistry = new SubscriberRegistry(); + subscriberRegistry.RegisterAsync(); + + IAmACommandProcessor commandProcessor = new CommandProcessor( + subscriberRegistry: subscriberRegistry, + handlerFactory: new QuickHandlerFactoryAsync(() => handler), + requestContextFactory: new InMemoryRequestContextFactory(), + policyRegistry: new PolicyRegistry() + ); + var provider = new CommandProcessorProvider(commandProcessor); + + var messageMapperRegistry = new MessageMapperRegistry( + new SimpleMessageMapperFactory(_ => new MyDeferredCommandMessageMapper()), + null + ); + messageMapperRegistry.Register(); + + _messagePump = new Reactor(provider, messageMapperRegistry, + new EmptyMessageTransformerFactory(), new InMemoryRequestContextFactory(), _channel) + { + Channel = _channel, TimeOut = TimeSpan.FromMilliseconds(5000), RequeueCount = 3 + }; + } + + public int GetDLQCountAsync(string queueName) + { + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); + var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); + var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest + { + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageSystemAttributeNames = ["ApproximateReceiveCount"], + MessageAttributeNames = new List { "All" } + }).GetAwaiter().GetResult(); + + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new AmazonSQSException( + $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + } + + return response.Messages.Count; + } + + [Fact(Skip = "Failing async tests caused by task scheduler issues")] + public void When_throwing_defer_action_respect_redrive_async() + { + _sender.Send(_message); + + var task = Task.Factory.StartNew(() => _messagePump.Run(), TaskCreationOptions.LongRunning); + Task.Delay(5000).GetAwaiter().GetResult(); + + var quitMessage = MessageFactory.CreateQuitMessage(_subscription.RoutingKey); + _channel.Enqueue(quitMessage); + + Task.WaitAll(task); + + Task.Delay(5000).GetAwaiter().GetResult(); + + var dlqCount = GetDLQCountAsync(_dlqChannelName + ".fifo"); + dlqCount.Should().Be(1); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_topic_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_topic_missing_verify_throws.cs new file mode 100644 index 0000000000..8bf9cf6d6e --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_topic_missing_verify_throws.cs @@ -0,0 +1,45 @@ +using System; +using Amazon.SQS.Model; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Fifo.Reactor; + +[Trait("Category", "AWS")] +public class AWSValidateMissingTopicTests +{ + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly RoutingKey _routingKey; + + public AWSValidateMissingTopicTests() + { + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey(queueName); + + _awsConnection = GatewayFactory.CreateFactory(); + + // Because we don't use channel factory to create the infrastructure - it won't exist + } + + [Fact] + public void When_topic_missing_verify_throws() + { + // arrange + var producer = new SqsMessageProducer(_awsConnection, + new SqsPublication + { + MakeChannels = OnMissingChannel.Validate, + SqsAttributes = new SqsAttributes { Type = SnsSqsType.Fifo } + }); + + var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + + // act & assert + Assert.Throws(() => + producer.Send(new Message( + new MessageHeader("", _routingKey, MessageType.MT_EVENT, + type: "plain/text", partitionKey: messageGroupId), + new MessageBody("Test")))); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs new file mode 100644 index 0000000000..2730234f9e --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SQSBufferedConsumerTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageConsumer _consumer; + private readonly string _topicName; + private readonly ChannelFactory _channelFactory; + private const string ContentType = "text\\plain"; + private const int BufferSize = 3; + private const int MessageCount = 4; + + public SQSBufferedConsumerTestsAsync() + { + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + var channelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _topicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + + //we need the channel to create the queues and notifications + var routingKey = new RoutingKey(_topicName); + + var channel = _channelFactory.CreateAsyncChannelAsync(new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + bufferSize: BufferSize, + makeChannels: OnMissingChannel.Create + )).GetAwaiter().GetResult(); + + //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel + //just for the tests, so create a new consumer from the properties + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), BufferSize); + _messageProducer = new SnsMessageProducer(awsConnection, + new SnsPublication { MakeChannels = OnMissingChannel.Create }); + } + + [Fact] + public async Task When_a_message_consumer_reads_multiple_messages_async() + { + var routingKey = new RoutingKey(_topicName); + + var messageOne = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType), + new MessageBody("test content one") + ); + + var messageTwo = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType), + new MessageBody("test content two") + ); + + var messageThree = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType), + new MessageBody("test content three") + ); + + var messageFour = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType), + new MessageBody("test content four") + ); + + //send MESSAGE_COUNT messages + await _messageProducer.SendAsync(messageOne); + await _messageProducer.SendAsync(messageTwo); + await _messageProducer.SendAsync(messageThree); + await _messageProducer.SendAsync(messageFour); + + int iteration = 0; + var messagesReceived = new List(); + var messagesReceivedCount = messagesReceived.Count; + do + { + iteration++; + var outstandingMessageCount = MessageCount - messagesReceivedCount; + + //retrieve messages + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(10000)); + + messages.Length.Should().BeLessOrEqualTo(outstandingMessageCount); + + //should not receive more than buffer in one hit + messages.Length.Should().BeLessOrEqualTo(BufferSize); + + var moreMessages = messages.Where(m => m.Header.MessageType == MessageType.MT_COMMAND); + foreach (var message in moreMessages) + { + messagesReceived.Add(message); + await _consumer.AcknowledgeAsync(message); + } + + messagesReceivedCount = messagesReceived.Count; + + await Task.Delay(1000); + + } while ((iteration <= 5) && (messagesReceivedCount < MessageCount)); + + messagesReceivedCount.Should().Be(4); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _messageProducer.DisposeAsync(); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().GetAwaiter().GetResult(); + _channelFactory.DeleteQueueAsync().GetAwaiter().GetResult(); + _messageProducer.DisposeAsync().GetAwaiter().GetResult(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_customising_aws_client_config_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_customising_aws_client_config_async.cs new file mode 100644 index 0000000000..d5a033cd7d --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_customising_aws_client_config_async.cs @@ -0,0 +1,95 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; + +[Trait("Category", "AWS")] +public class CustomisingAwsClientConfigTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly IAmAChannelAsync _channel; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + + public CustomisingAwsClientConfigTestsAsync() + { + MyCommand myCommand = new() { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + string correlationId = Guid.NewGuid().ToString(); + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + messagePumpType: MessagePumpType.Proactor, + routingKey: routingKey + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + var subscribeAwsConnection = GatewayFactory.CreateFactory(config => + { + config.HttpClientFactory = + new InterceptingHttpClientFactory(new InterceptingDelegatingHandler("async_sub")); + }); + + _channelFactory = new ChannelFactory(subscribeAwsConnection); + _channel = _channelFactory.CreateAsyncChannel(subscription); + + var publishAwsConnection = GatewayFactory.CreateFactory(config => + { + config.HttpClientFactory = + new InterceptingHttpClientFactory(new InterceptingDelegatingHandler("async_pub")); + }); + + _messageProducer = new SnsMessageProducer(publishAwsConnection, + new SnsPublication { Topic = new RoutingKey(topicName), MakeChannels = OnMissingChannel.Create }); + } + + [Fact] + public async Task When_customising_aws_client_config() + { + //arrange + await _messageProducer.SendAsync(_message); + + await Task.Delay(1000); + + var message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + //clear the queue + await _channel.AcknowledgeAsync(message); + + //publish_and_subscribe_should_use_custom_http_client_factory + InterceptingDelegatingHandler.RequestCount.Should().ContainKey("async_sub"); + InterceptingDelegatingHandler.RequestCount["async_sub"].Should().BeGreaterThan(0); + + InterceptingDelegatingHandler.RequestCount.Should().ContainKey("async_pub"); + InterceptingDelegatingHandler.RequestCount["async_pub"].Should().BeGreaterThan(0); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_assume_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_assume_async.cs new file mode 100644 index 0000000000..cac101dd76 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_assume_async.cs @@ -0,0 +1,96 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSAssumeInfrastructureTestsAsync : IDisposable, IAsyncDisposable +{ private readonly Message _message; + private readonly SqsMessageConsumer _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSAssumeInfrastructureTestsAsync() + { + _myCommand = new MyCommand{Value = "Test"}; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateAsyncChannel(subscription); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Assume + ); + + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Assume}); + + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); + } + + [Fact] + public async Task When_infastructure_exists_can_assume() + { + //arrange + await _messageProducer.SendAsync(_message); + + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + await _consumer.AcknowledgeAsync(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_verify_by_arn.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_verify_by_arn.cs new file mode 100644 index 0000000000..b400bdb6c3 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_verify_by_arn.cs @@ -0,0 +1,125 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureByArnTests : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumerSync _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureByArnTests() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey($"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var awsConnection = GatewayFactory.CreateFactory(credentials, region); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + var topicArn = FindTopicArn(awsConnection, routingKey.Value); + var routingKeyArn = new RoutingKey(topicArn); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKeyArn, + findTopicBy: TopicFindBy.Arn, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Validate + ); + + _messageProducer = new SnsMessageProducer( + awsConnection, + new SnsPublication + { + Topic = routingKey, + TopicArn = topicArn, + FindTopicBy = TopicFindBy.Arn, + MakeChannels = OnMissingChannel.Validate + }); + + _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify() + { + //arrange + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _consumer.Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); + await _messageProducer.DisposeAsync(); + } + + private static string FindTopicArn(AWSMessagingGatewayConnection connection, string topicName) + { + using var snsClient = new AWSClientFactory(connection).CreateSnsClient(); + var topicResponse = snsClient.FindTopicAsync(topicName).GetAwaiter().GetResult(); + return topicResponse.TopicArn; + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_async.cs new file mode 100644 index 0000000000..c6be3f81ac --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_async.cs @@ -0,0 +1,106 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor +{ + [Trait("Category", "AWS")] + [Trait("Fragile", "CI")] + public class AWSValidateInfrastructureTestsAsync : IDisposable, IAsyncDisposable + { + private readonly Message _message; + private readonly IAmAMessageConsumerAsync _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureTestsAsync() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateAsyncChannel(subscription); + + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + findTopicBy: TopicFindBy.Name, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Validate + ); + + _messageProducer = new SnsMessageProducer( + awsConnection, + new SnsPublication + { + FindTopicBy = TopicFindBy.Name, + MakeChannels = OnMissingChannel.Validate, + Topic = new RoutingKey(topicName) + } + ); + + _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify_async() + { + await _messageProducer.SendAsync(_message); + + await Task.Delay(1000); + + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + await _consumer.AcknowledgeAsync(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + ((IAmAMessageConsumerSync)_consumer).Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _consumer.DisposeAsync(); + await _messageProducer.DisposeAsync(); + } + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_by_arn_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_by_arn_async.cs new file mode 100644 index 0000000000..6b7fb5f886 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_by_arn_async.cs @@ -0,0 +1,116 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureByArnTestsAsync : IAsyncDisposable, IDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumerAsync _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureByArnTestsAsync() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey($"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); + var awsConnection = GatewayFactory.CreateFactory(credentials, region); + + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateAsyncChannel(subscription); + + var topicArn = FindTopicArn(awsConnection, routingKey.Value).Result; + var routingKeyArn = new RoutingKey(topicArn); + + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKeyArn, + findTopicBy: TopicFindBy.Arn, + makeChannels: OnMissingChannel.Validate + ); + + _messageProducer = new SnsMessageProducer( + awsConnection, + new SnsPublication + { + Topic = routingKey, + TopicArn = topicArn, + FindTopicBy = TopicFindBy.Arn, + MakeChannels = OnMissingChannel.Validate + }); + + _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify_async() + { + await _messageProducer.SendAsync(_message); + + await Task.Delay(1000); + + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + await _consumer.AcknowledgeAsync(message); + } + + private static async Task FindTopicArn(AWSMessagingGatewayConnection connection, string topicName) + { + using var snsClient = new AWSClientFactory(connection).CreateSnsClient(); + var topicResponse = await snsClient.FindTopicAsync(topicName); + return topicResponse.TopicArn; + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + ((IAmAMessageConsumerSync)_consumer).Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _consumer.DisposeAsync(); + await _messageProducer.DisposeAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs new file mode 100644 index 0000000000..69db8bb110 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs @@ -0,0 +1,109 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; + +[Trait("Category", "AWS")] +public class SqsMessageProducerSendAsyncTests : IAsyncDisposable, IDisposable +{ + private readonly Message _message; + private readonly IAmAChannelAsync _channel; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + private readonly string _correlationId; + private readonly string _replyTo; + private readonly string _contentType; + private readonly string _topicName; + + public SqsMessageProducerSendAsyncTests() + { + _myCommand = new MyCommand { Value = "Test" }; + _correlationId = Guid.NewGuid().ToString(); + _replyTo = "http:\\queueUrl"; + _contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(_topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + rawMessageDelivery: false + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: _correlationId, + replyTo: new RoutingKey(_replyTo), contentType: _contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateAsyncChannel(subscription); + + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create }); + } + + [Fact] + public async Task When_posting_a_message_via_the_producer_async() + { + // arrange + _message.Header.Subject = "test subject"; + await _messageProducer.SendAsync(_message); + + await Task.Delay(1000); + + var message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + // clear the queue + await _channel.AcknowledgeAsync(message); + + // should_send_the_message_to_aws_sqs + message.Header.MessageType.Should().Be(MessageType.MT_COMMAND); + + message.Id.Should().Be(_myCommand.Id); + message.Redelivered.Should().BeFalse(); + message.Header.MessageId.Should().Be(_myCommand.Id); + message.Header.Topic.Value.Should().Contain(_topicName); + message.Header.CorrelationId.Should().Be(_correlationId); + message.Header.ReplyTo.Should().Be(_replyTo); + message.Header.ContentType.Should().Be(_contentType); + message.Header.HandledCount.Should().Be(0); + message.Header.Subject.Should().Be(_message.Header.Subject); + // allow for clock drift in the following test, more important to have a contemporary timestamp than anything + message.Header.TimeStamp.Should().BeAfter(RoundToSeconds(DateTime.UtcNow.AddMinutes(-1))); + message.Header.Delayed.Should().Be(TimeSpan.Zero); + // {"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} + message.Body.Value.Should().Be(_message.Body.Value); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _messageProducer.DisposeAsync(); + } + + private static DateTime RoundToSeconds(DateTime dateTime) + { + return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_assume_throws_async.cs similarity index 89% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_assume_throws_async.cs index 2b3211ff75..4c32ca9043 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_assume_throws_async.cs @@ -6,7 +6,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; [Trait("Category", "AWS")] public class AWSAssumeQueuesTestsAsync : IAsyncDisposable, IDisposable @@ -25,8 +25,7 @@ public AWSAssumeQueuesTestsAsync() channelName: new ChannelName(channelName), routingKey: routingKey, makeChannels: OnMissingChannel.Assume, - messagePumpType: MessagePumpType.Proactor, - sqsType: SnsSqsType.Fifo + messagePumpType: MessagePumpType.Proactor ); var awsConnection = GatewayFactory.CreateFactory(); @@ -36,8 +35,7 @@ public AWSAssumeQueuesTestsAsync() var producer = new SnsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo + MakeChannels = OnMissingChannel.Create }); producer.ConfirmTopicExistsAsync(topicName).Wait(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_verify_throws_async.cs similarity index 88% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_verify_throws_async.cs index 94087f0986..39da97ce59 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_verify_throws_async.cs @@ -6,7 +6,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; [Trait("Category", "AWS")] public class AWSValidateQueuesTestsAsync : IAsyncDisposable @@ -25,8 +25,7 @@ public AWSValidateQueuesTestsAsync() name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, - makeChannels: OnMissingChannel.Validate, - sqsType: SnsSqsType.Fifo + makeChannels: OnMissingChannel.Validate ); _awsConnection = GatewayFactory.CreateFactory(); @@ -35,8 +34,7 @@ public AWSValidateQueuesTestsAsync() var producer = new SnsMessageProducer(_awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo + MakeChannels = OnMissingChannel.Create }); producer.ConfirmTopicExistsAsync(topicName).Wait(); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_raw_message_delivery_disabled_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_raw_message_delivery_disabled_async.cs new file mode 100644 index 0000000000..ea13c2872e --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_raw_message_delivery_disabled_async.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsRawMessageDeliveryTestsAsync : IAsyncDisposable, IDisposable +{ + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly IAmAChannelAsync _channel; + private readonly RoutingKey _routingKey; + + public SqsRawMessageDeliveryTestsAsync() + { + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + var channelName = $"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey($"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); + + var bufferSize = 10; + + // Set rawMessageDelivery to false + _channel = _channelFactory.CreateAsyncChannel(new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: _routingKey, + bufferSize: bufferSize, + makeChannels: OnMissingChannel.Create, + rawMessageDelivery: false)); + + _messageProducer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create + }); + } + + [Fact] + public async Task When_raw_message_delivery_disabled_async() + { + // Arrange + var messageHeader = new MessageHeader( + Guid.NewGuid().ToString(), + _routingKey, + MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), + replyTo: RoutingKey.Empty, + contentType: "text\\plain"); + + var customHeaderItem = new KeyValuePair("custom-header-item", "custom-header-item-value"); + messageHeader.Bag.Add(customHeaderItem.Key, customHeaderItem.Value); + + var messageToSend = new Message(messageHeader, new MessageBody("test content one")); + + // Act + await _messageProducer.SendAsync(messageToSend); + + var messageReceived = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(10000)); + + await _channel.AcknowledgeAsync(messageReceived); + + // Assert + messageReceived.Id.Should().Be(messageToSend.Id); + messageReceived.Header.Topic.Should().Be(messageToSend.Header.Topic); + messageReceived.Header.MessageType.Should().Be(messageToSend.Header.MessageType); + messageReceived.Header.CorrelationId.Should().Be(messageToSend.Header.CorrelationId); + messageReceived.Header.ReplyTo.Should().Be(messageToSend.Header.ReplyTo); + messageReceived.Header.ContentType.Should().Be(messageToSend.Header.ContentType); + messageReceived.Header.Bag.Should().ContainKey(customHeaderItem.Key).And.ContainValue(customHeaderItem.Value); + messageReceived.Body.Value.Should().Be(messageToSend.Body.Value); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs new file mode 100644 index 0000000000..558c295c61 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs @@ -0,0 +1,86 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageConsumerRequeueTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly IAmAChannelAsync _channel; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public SqsMessageConsumerRequeueTestsAsync() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateAsyncChannel(subscription); + + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + } + + [Fact] + public async Task When_rejecting_a_message_through_gateway_with_requeue_async() + { + await _messageProducer.SendAsync(_message); + + var message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + await _channel.RejectAsync(message); + + // Let the timeout change + await Task.Delay(TimeSpan.FromMilliseconds(3000)); + + // should requeue_the_message + message = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + // clear the queue + await _channel.AcknowledgeAsync(message); + + message.Id.Should().Be(_myCommand.Id); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_a_message_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_a_message_async.cs new file mode 100644 index 0000000000..9a1101624f --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_a_message_async.cs @@ -0,0 +1,82 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.Runtime.CredentialManagement; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; + +[Trait("Category", "AWS")] +public class SqsMessageProducerRequeueTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly IAmAMessageProducerAsync _sender; + private Message _requeuedMessage; + private Message _receivedMessage; + private readonly IAmAChannelAsync _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; + + public SqsMessageProducerRequeueTestsAsync() + { + MyCommand myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + new CredentialProfileStoreChain(); + + var awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SnsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateAsyncChannel(subscription); + } + + [Fact] + public async Task When_requeueing_a_message_async() + { + await _sender.SendAsync(_message); + _receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + await _channel.RequeueAsync(_receivedMessage); + + _requeuedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + await _channel.AcknowledgeAsync(_requeuedMessage); + + _requeuedMessage.Body.Value.Should().Be(_receivedMessage.Body.Value); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_redrives_to_the_dlq_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_redrives_to_the_dlq_async.cs new file mode 100644 index 0000000000..376d9d5510 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_redrives_to_the_dlq_async.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.SQS; +using Amazon.SQS.Model; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageProducerDlqTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly SnsMessageProducer _sender; + private readonly IAmAChannelAsync _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly string _dlqChannelName; + + public SqsMessageProducerDlqTestsAsync() + { + MyCommand myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _dlqChannelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + redrivePolicy: new RedrivePolicy(_dlqChannelName, 2) + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + _awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SnsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + + _sender.ConfirmTopicExistsAsync(topicName).Wait(); + + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateAsyncChannel(subscription); + } + + [Fact] + public async Task When_requeueing_redrives_to_the_queue_async() + { + await _sender.SendAsync(_message); + var receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + await _channel.RequeueAsync(receivedMessage); + + receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + await _channel.RequeueAsync(receivedMessage); + + receivedMessage = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + await _channel.RequeueAsync(receivedMessage); + + await Task.Delay(5000); + + int dlqCount = await GetDLQCountAsync(_dlqChannelName); + dlqCount.Should().Be(1); + } + + private async Task GetDLQCountAsync(string queueName) + { + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); + var queueUrlResponse = await sqsClient.GetQueueUrlAsync(queueName); + var response = await sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest + { + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageAttributeNames = new List { "All", "ApproximateReceiveCount" } + }); + + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new AmazonSQSException( + $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + } + + return response.Messages.Count; + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_throwing_defer_action_respect_redrive_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_throwing_defer_action_respect_redrive_async.cs new file mode 100644 index 0000000000..e0bfd2de3e --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_throwing_defer_action_respect_redrive_async.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.SQS; +using Amazon.SQS.Model; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Paramore.Brighter.ServiceActivator; +using Polly.Registry; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SnsReDrivePolicySDlqTestsAsync : IDisposable, IAsyncDisposable +{ + private readonly IAmAMessagePump _messagePump; + private readonly Message _message; + private readonly string _dlqChannelName; + private readonly IAmAChannelAsync _channel; + private readonly SnsMessageProducer _sender; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; + private readonly ChannelFactory _channelFactory; + + public SnsReDrivePolicySDlqTestsAsync() + { + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _dlqChannelName = $"Redrive-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + _subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + requeueCount: -1, + requeueDelay: TimeSpan.FromMilliseconds(50), + messagePumpType: MessagePumpType.Proactor, + redrivePolicy: new RedrivePolicy(new ChannelName(_dlqChannelName), 2) + ); + + var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + _awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SnsMessageProducer( + _awsConnection, + new SnsPublication + { + Topic = routingKey, + RequestType = typeof(MyDeferredCommand), + MakeChannels = OnMissingChannel.Create + } + ); + + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateAsyncChannel(_subscription); + + IHandleRequestsAsync handler = new MyDeferredCommandHandlerAsync(); + + var subscriberRegistry = new SubscriberRegistry(); + subscriberRegistry.RegisterAsync(); + + IAmACommandProcessor commandProcessor = new CommandProcessor( + subscriberRegistry: subscriberRegistry, + handlerFactory: new QuickHandlerFactoryAsync(() => handler), + requestContextFactory: new InMemoryRequestContextFactory(), + policyRegistry: new PolicyRegistry() + ); + var provider = new CommandProcessorProvider(commandProcessor); + + var messageMapperRegistry = new MessageMapperRegistry( + new SimpleMessageMapperFactory(_ => new MyDeferredCommandMessageMapper()), + null + ); + messageMapperRegistry.Register(); + + _messagePump = new Proactor(provider, messageMapperRegistry, + new EmptyMessageTransformerFactoryAsync(), new InMemoryRequestContextFactory(), _channel) + { + Channel = _channel, TimeOut = TimeSpan.FromMilliseconds(5000), RequeueCount = 3 + }; + } + + public async Task GetDLQCountAsync(string queueName) + { + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); + var queueUrlResponse = await sqsClient.GetQueueUrlAsync(queueName); + var response = await sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest + { + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageSystemAttributeNames = new List { "ApproximateReceiveCount" }, + MessageAttributeNames = new List { "All" } + }); + + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new AmazonSQSException($"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + } + + return response.Messages.Count; + } + + [Fact(Skip = "Failing async tests caused by task scheduler issues")] + public async Task When_throwing_defer_action_respect_redrive_async() + { + await _sender.SendAsync(_message); + + var task = Task.Factory.StartNew(() => _messagePump.Run(), TaskCreationOptions.LongRunning); + await Task.Delay(5000); + + var quitMessage = MessageFactory.CreateQuitMessage(_subscription.RoutingKey); + _channel.Enqueue(quitMessage); + + await Task.WhenAll(task); + + await Task.Delay(5000); + + int dlqCount = await GetDLQCountAsync(_dlqChannelName); + dlqCount.Should().Be(1); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_topic_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_topic_missing_verify_throws_async.cs new file mode 100644 index 0000000000..561fb2cebc --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_topic_missing_verify_throws_async.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading.Tasks; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; + +[Trait("Category", "AWS")] +public class AWSValidateMissingTopicTestsAsync +{ + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly RoutingKey _routingKey; + + public AWSValidateMissingTopicTestsAsync() + { + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey(topicName); + + _awsConnection = GatewayFactory.CreateFactory(); + + // Because we don't use channel factory to create the infrastructure - it won't exist + } + + [Fact] + public async Task When_topic_missing_verify_throws_async() + { + // arrange + var producer = new SnsMessageProducer(_awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Validate + }); + + // act & assert + await Assert.ThrowsAsync(async () => + await producer.SendAsync(new Message( + new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text"), + new MessageBody("Test")))); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_a_message_consumer_reads_multiple_messages.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_a_message_consumer_reads_multiple_messages.cs new file mode 100644 index 0000000000..135a00d434 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_a_message_consumer_reads_multiple_messages.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SQSBufferedConsumerTests : IDisposable, IAsyncDisposable +{ + private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageConsumer _consumer; + private readonly string _topicName; + private readonly ChannelFactory _channelFactory; + private const string ContentType = "text\\plain"; + private const int BufferSize = 3; + private const int MessageCount = 4; + + public SQSBufferedConsumerTests() + { + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + var channelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _topicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + + //we need the channel to create the queues and notifications + var routingKey = new RoutingKey(_topicName); + + var channel = _channelFactory.CreateSyncChannel(new SqsSubscription( + name: new SubscriptionName(channelName), + channelName:new ChannelName(channelName), + routingKey:routingKey, + bufferSize: BufferSize, + makeChannels: OnMissingChannel.Create + )); + + //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel + //just for the tests, so create a new consumer from the properties + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), BufferSize); + _messageProducer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create + }); + } + + [Fact] + public async Task When_a_message_consumer_reads_multiple_messages() + { + var routingKey = new RoutingKey(_topicName); + + var messageOne = new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType), + new MessageBody("test content one") + ); + + var messageTwo= new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType), + new MessageBody("test content two") + ); + + var messageThree= new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType), + new MessageBody("test content three") + ); + + var messageFour= new Message( + new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), contentType: ContentType), + new MessageBody("test content four") + ); + + //send MESSAGE_COUNT messages + _messageProducer.Send(messageOne); + _messageProducer.Send(messageTwo); + _messageProducer.Send(messageThree); + _messageProducer.Send(messageFour); + + + int iteration = 0; + var messagesReceived = new List(); + var messagesReceivedCount = messagesReceived.Count; + do + { + iteration++; + var outstandingMessageCount = MessageCount - messagesReceivedCount; + + //retrieve messages + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(10000)); + + messages.Length.Should().BeLessOrEqualTo(outstandingMessageCount); + + //should not receive more than buffer in one hit + messages.Length.Should().BeLessOrEqualTo(BufferSize); + + var moreMessages = messages.Where(m => m.Header.MessageType == MessageType.MT_COMMAND); + foreach (var message in moreMessages) + { + messagesReceived.Add(message); + _consumer.Acknowledge(message); + } + + messagesReceivedCount = messagesReceived.Count; + + await Task.Delay(1000); + + } while ((iteration <= 5) && (messagesReceivedCount < MessageCount)); + + + messagesReceivedCount.Should().Be(4); + + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageProducerAsync) _messageProducer).DisposeAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_customising_aws_client_config.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_customising_aws_client_config.cs new file mode 100644 index 0000000000..99ddd3f37a --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_customising_aws_client_config.cs @@ -0,0 +1,93 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; + +[Trait("Category", "AWS")] +public class CustomisingAwsClientConfigTests : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly IAmAChannelSync _channel; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + + public CustomisingAwsClientConfigTests() + { + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + MyCommand myCommand = new() { Value = "Test" }; + var correlationId = Guid.NewGuid().ToString(); + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + messagePumpType: MessagePumpType.Reactor, + routingKey: routingKey + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + var subscribeAwsConnection = GatewayFactory.CreateFactory(config => + { + config.HttpClientFactory = new InterceptingHttpClientFactory(new InterceptingDelegatingHandler("sync_sub")); + }); + + _channelFactory = new ChannelFactory(subscribeAwsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); + + var publishAwsConnection = GatewayFactory.CreateFactory(config => + { + config.HttpClientFactory = new InterceptingHttpClientFactory(new InterceptingDelegatingHandler("sync_pub")); + }); + + _messageProducer = new SnsMessageProducer(publishAwsConnection, + new SnsPublication { Topic = new RoutingKey(topicName), MakeChannels = OnMissingChannel.Create }); + } + + [Fact] + public async Task When_customising_aws_client_config() + { + //arrange + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + //clear the queue + _channel.Acknowledge(message); + + //publish_and_subscribe_should_use_custom_http_client_factory + InterceptingDelegatingHandler.RequestCount.Should().ContainKey("sync_sub"); + InterceptingDelegatingHandler.RequestCount["sync_sub"].Should().BeGreaterThan(0); + + InterceptingDelegatingHandler.RequestCount.Should().ContainKey("sync_pub"); + InterceptingDelegatingHandler.RequestCount["sync_pub"].Should().BeGreaterThan(0); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_assume.cs new file mode 100644 index 0000000000..d712fb48d9 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_assume.cs @@ -0,0 +1,98 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSAssumeInfrastructureTests : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly SqsMessageConsumer _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSAssumeInfrastructureTests() + { + _myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Assume + ); + + _messageProducer = new SnsMessageProducer(awsConnection, + new SnsPublication { MakeChannels = OnMissingChannel.Assume }); + + _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); + } + + [Fact] + public void When_infastructure_exists_can_assume() + { + //arrange + _messageProducer.Send(_message); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify.cs new file mode 100644 index 0000000000..c702a31062 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify.cs @@ -0,0 +1,112 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureTests : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumerSync _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureTests() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + //Now change the subscription to validate, just check what we made + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + findTopicBy: TopicFindBy.Name, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Validate + ); + + _messageProducer = new SnsMessageProducer( + awsConnection, + new SnsPublication + { + FindTopicBy = TopicFindBy.Name, + MakeChannels = OnMissingChannel.Validate, + Topic = new RoutingKey(topicName) + } + ); + + _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify() + { + //arrange + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _consumer.Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); + await _messageProducer.DisposeAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify_by_convention.cs new file mode 100644 index 0000000000..71a7be113d --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify_by_convention.cs @@ -0,0 +1,107 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureByConventionTests : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumerSync _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureByConventionTests() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + //This doesn't look that different from our create tests - this is because we create using the channel factory in + //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateSyncChannel(subscription); + + //Now change the subscription to validate, just check what we made - will make the SNS Arn to prevent ListTopics call + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + findTopicBy: TopicFindBy.Convention, + messagePumpType: MessagePumpType.Reactor, + makeChannels: OnMissingChannel.Validate + ); + + _messageProducer = new SnsMessageProducer( + awsConnection, + new SnsPublication { FindTopicBy = TopicFindBy.Convention, MakeChannels = OnMissingChannel.Validate } + ); + + _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify() + { + //arrange + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); + + //Assert + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + //clear the queue + _consumer.Acknowledge(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _consumer.Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); + await _messageProducer.DisposeAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infrastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infrastructure_exists_can_verify_by_convention.cs new file mode 100644 index 0000000000..faa4e7434d --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infrastructure_exists_can_verify_by_convention.cs @@ -0,0 +1,104 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class AWSValidateInfrastructureByConventionTestsAsync : IAsyncDisposable, IDisposable +{ + private readonly Message _message; + private readonly IAmAMessageConsumerAsync _consumer; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public AWSValidateInfrastructureByConventionTestsAsync() + { + _myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Create + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + var channel = _channelFactory.CreateAsyncChannel(subscription); + + subscription = new( + name: new SubscriptionName(channelName), + channelName: channel.Name, + routingKey: routingKey, + findTopicBy: TopicFindBy.Convention, + messagePumpType: MessagePumpType.Proactor, + makeChannels: OnMissingChannel.Validate + ); + + _messageProducer = new SnsMessageProducer( + awsConnection, + new SnsPublication + { + FindTopicBy = TopicFindBy.Convention, + MakeChannels = OnMissingChannel.Validate + } + ); + + _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); + } + + [Fact] + public async Task When_infrastructure_exists_can_verify_async() + { + await _messageProducer.SendAsync(_message); + + await Task.Delay(1000); + + var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); + + var message = messages.First(); + message.Id.Should().Be(_myCommand.Id); + + await _consumer.AcknowledgeAsync(message); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + ((IAmAMessageConsumerSync)_consumer).Dispose(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _consumer.DisposeAsync(); + await _messageProducer.DisposeAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_posting_a_message_via_the_messaging_gateway.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_posting_a_message_via_the_messaging_gateway.cs new file mode 100644 index 0000000000..93a8331163 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_posting_a_message_via_the_messaging_gateway.cs @@ -0,0 +1,110 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; + +[Trait("Category", "AWS")] +public class SqsMessageProducerSendTests : IDisposable, IAsyncDisposable +{ + private readonly Message _message; + private readonly IAmAChannelSync _channel; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + private readonly string _correlationId; + private readonly string _replyTo; + private readonly string _contentType; + private readonly string _topicName; + + public SqsMessageProducerSendTests() + { + _myCommand = new MyCommand{Value = "Test"}; + _correlationId = Guid.NewGuid().ToString(); + _replyTo = "http:\\queueUrl"; + _contentType = "text\\plain"; + var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(_topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + messagePumpType: MessagePumpType.Reactor, + rawMessageDelivery: false + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: _correlationId, + replyTo: new RoutingKey(_replyTo), contentType: _contentType), + new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + ); + + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); + + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication{Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create}); + } + + [Fact] + public async Task When_posting_a_message_via_the_producer() + { + //arrange + _message.Header.Subject = "test subject"; + _messageProducer.Send(_message); + + await Task.Delay(1000); + + var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + //clear the queue + _channel.Acknowledge(message); + + //should_send_the_message_to_aws_sqs + message.Header.MessageType.Should().Be(MessageType.MT_COMMAND); + + message.Id.Should().Be(_myCommand.Id); + message.Redelivered.Should().BeFalse(); + message.Header.MessageId.Should().Be(_myCommand.Id); + message.Header.Topic.Value.Should().Contain(_topicName); + message.Header.CorrelationId.Should().Be(_correlationId); + message.Header.ReplyTo.Should().Be(_replyTo); + message.Header.ContentType.Should().Be(_contentType); + message.Header.HandledCount.Should().Be(0); + message.Header.Subject.Should().Be(_message.Header.Subject); + //allow for clock drift in the following test, more important to have a contemporary timestamp than anything + message.Header.TimeStamp.Should().BeAfter(RoundToSeconds(DateTime.UtcNow.AddMinutes(-1))); + message.Header.Delayed.Should().Be(TimeSpan.Zero); + //{"Id":"cd581ced-c066-4322-aeaf-d40944de8edd","Value":"Test","WasCancelled":false,"TaskCompleted":false} + message.Body.Value.Should().Be(_message.Body.Value); + } + + public void Dispose() + { + //Clean up resources that we have created + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + _messageProducer.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + await _messageProducer.DisposeAsync(); + } + + private static DateTime RoundToSeconds(DateTime dateTime) + { + return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind); + } + +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_assume_throws.cs similarity index 89% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_assume_throws.cs index ae3bf19364..00ea6e34e9 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_assume_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_assume_throws.cs @@ -6,7 +6,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; [Trait("Category", "AWS")] public class AWSAssumeQueuesTests : IDisposable, IAsyncDisposable @@ -25,8 +25,7 @@ public AWSAssumeQueuesTests() channelName: new ChannelName(channelName), routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Assume, - sqsType: SnsSqsType.Fifo + makeChannels: OnMissingChannel.Assume ); var awsConnection = GatewayFactory.CreateFactory(); @@ -36,8 +35,7 @@ public AWSAssumeQueuesTests() var producer = new SnsMessageProducer(awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo + MakeChannels = OnMissingChannel.Create }); producer.ConfirmTopicExistsAsync(topicName).Wait(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_verify_throws.cs similarity index 88% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_verify_throws.cs index dc329e6d64..fa42559bed 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_queues_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_verify_throws.cs @@ -6,7 +6,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; [Trait("Category", "AWS")] public class AWSValidateQueuesTests : IDisposable, IAsyncDisposable @@ -26,8 +26,7 @@ public AWSValidateQueuesTests() channelName: new ChannelName(channelName), routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Validate, - sqsType: SnsSqsType.Fifo + makeChannels: OnMissingChannel.Validate ); _awsConnection = GatewayFactory.CreateFactory(); @@ -36,8 +35,7 @@ public AWSValidateQueuesTests() var producer = new SnsMessageProducer(_awsConnection, new SnsPublication { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo + MakeChannels = OnMissingChannel.Create }); producer.ConfirmTopicExistsAsync(topicName).Wait(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_raw_message_delivery_disabled.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_raw_message_delivery_disabled.cs new file mode 100644 index 0000000000..903ef014a8 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_raw_message_delivery_disabled.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsRawMessageDeliveryTests : IDisposable, IAsyncDisposable +{ + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly IAmAChannelSync _channel; + private readonly RoutingKey _routingKey; + + public SqsRawMessageDeliveryTests() + { + var awsConnection = GatewayFactory.CreateFactory(); + + _channelFactory = new ChannelFactory(awsConnection); + var channelName = $"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey($"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); + + var bufferSize = 10; + + //Set rawMessageDelivery to false + _channel = _channelFactory.CreateSyncChannel(new SqsSubscription( + name: new SubscriptionName(channelName), + channelName:new ChannelName(channelName), + routingKey:_routingKey, + bufferSize: bufferSize, + makeChannels: OnMissingChannel.Create, + messagePumpType: MessagePumpType.Reactor, + rawMessageDelivery: false)); + + _messageProducer = new SnsMessageProducer(awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Create + }); + } + + [Fact] + public void When_raw_message_delivery_disabled() + { + //arrange + var messageHeader = new MessageHeader( + Guid.NewGuid().ToString(), + _routingKey, + MessageType.MT_COMMAND, + correlationId: Guid.NewGuid().ToString(), + replyTo: RoutingKey.Empty, + contentType: "text\\plain"); + + var customHeaderItem = new KeyValuePair("custom-header-item", "custom-header-item-value"); + messageHeader.Bag.Add(customHeaderItem.Key, customHeaderItem.Value); + + var messageToSent = new Message(messageHeader, new MessageBody("test content one")); + + //act + _messageProducer.Send(messageToSent); + + var messageReceived = _channel.Receive(TimeSpan.FromMilliseconds(10000)); + + _channel.Acknowledge(messageReceived); + + //assert + messageReceived.Id.Should().Be(messageToSent.Id); + messageReceived.Header.Topic.Should().Be(messageToSent.Header.Topic); + messageReceived.Header.MessageType.Should().Be(messageToSent.Header.MessageType); + messageReceived.Header.CorrelationId.Should().Be(messageToSent.Header.CorrelationId); + messageReceived.Header.ReplyTo.Should().Be(messageToSent.Header.ReplyTo); + messageReceived.Header.ContentType.Should().Be(messageToSent.Header.ContentType); + messageReceived.Header.Bag.Should().ContainKey(customHeaderItem.Key).And.ContainValue(customHeaderItem.Value); + messageReceived.Body.Value.Should().Be(messageToSent.Body.Value); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs new file mode 100644 index 0000000000..890dd22723 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs @@ -0,0 +1,87 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageConsumerRequeueTests : IDisposable +{ + private readonly Message _message; + private readonly IAmAChannelSync _channel; + private readonly SnsMessageProducer _messageProducer; + private readonly ChannelFactory _channelFactory; + private readonly MyCommand _myCommand; + + public SqsMessageConsumerRequeueTests() + { + _myCommand = new MyCommand{Value = "Test"}; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + messagePumpType: MessagePumpType.Reactor, + routingKey: routingKey + ); + + _message = new Message( + new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + ); + + //Must have credentials stored in the SDK Credentials store or shared credentials file + var awsConnection = GatewayFactory.CreateFactory(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(awsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); + + _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); + } + + [Fact] + public void When_rejecting_a_message_through_gateway_with_requeue() + { + _messageProducer.Send(_message); + + var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + _channel.Reject(message); + + //Let the timeout change + Task.Delay(TimeSpan.FromMilliseconds(3000)); + + //should requeue_the_message + message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + + //clear the queue + _channel.Acknowledge(message); + + message.Id.Should().Be(_myCommand.Id); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_requeueing_a_message.cs similarity index 80% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_requeueing_a_message.cs index 9a37c5a721..62748204d0 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Fifo/When_requeueing_a_message.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_requeueing_a_message.cs @@ -8,7 +8,7 @@ using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Fifo; +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; [Trait("Category", "AWS")] public class SqsMessageProducerRequeueTests : IDisposable, IAsyncDisposable @@ -23,12 +23,11 @@ public class SqsMessageProducerRequeueTests : IDisposable, IAsyncDisposable public SqsMessageProducerRequeueTests() { MyCommand myCommand = new MyCommand{Value = "Test"}; - const string replyTo = "http:\\queueUrl"; - const string contentType = "text\\plain"; - var correlationId = Guid.NewGuid().ToString(); + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; var channelName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var messageGroupId = $"MessageGroup{Guid.NewGuid():N}"; + string topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); var routingKey = new RoutingKey(topicName); var subscription = new SqsSubscription( @@ -39,20 +38,16 @@ public SqsMessageProducerRequeueTests() _message = new Message( new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType, partitionKey: messageGroupId), + replyTo: new RoutingKey(replyTo), contentType: contentType), new MessageBody(JsonSerializer.Serialize((object) myCommand, JsonSerialisationOptions.Options)) ); //Must have credentials stored in the SDK Credentials store or shared credentials file + new CredentialProfileStoreChain(); + var awsConnection = GatewayFactory.CreateFactory(); - _sender = new SnsMessageProducer(awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create, - SnsType = SnsSqsType.Fifo, - Deduplication = true - }); + _sender = new SnsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); //We need to do this manually in a test - will create the channel from subscriber parameters _channelFactory = new ChannelFactory(awsConnection); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_requeueing_redrives_to_the_dlq.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_requeueing_redrives_to_the_dlq.cs new file mode 100644 index 0000000000..239824a23e --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_requeueing_redrives_to_the_dlq.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.SQS; +using Amazon.SQS.Model; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SqsMessageProducerDlqTests : IDisposable, IAsyncDisposable +{ + private readonly SnsMessageProducer _sender; + private readonly IAmAChannelSync _channel; + private readonly ChannelFactory _channelFactory; + private readonly Message _message; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly string _dlqChannelName; + + public SqsMessageProducerDlqTests() + { + MyCommand myCommand = new MyCommand { Value = "Test" }; + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _dlqChannelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + SqsSubscription subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + redrivePolicy: new RedrivePolicy(_dlqChannelName, 2) + ); + + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + //Must have credentials stored in the SDK Credentials store or shared credentials file + _awsConnection = GatewayFactory.CreateFactory(); + + _sender = new SnsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + + _sender.ConfirmTopicExistsAsync(topicName).Wait(); + + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateSyncChannel(subscription); + } + + [Fact] + public void When_requeueing_redrives_to_the_queue() + { + _sender.Send(_message); + var receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(receivedMessage); + + receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(receivedMessage); + + //should force us into the dlq + receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _channel.Requeue(receivedMessage); + + Task.Delay(5000); + + //inspect the dlq + GetDLQCount(_dlqChannelName).Should().Be(1); + } + + private int GetDLQCount(string queueName) + { + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); + var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); + var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest + { + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageAttributeNames = new List { "All", "ApproximateReceiveCount" } + }).GetAwaiter().GetResult(); + + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new AmazonSQSException( + $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + } + + return response.Messages.Count; + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_throwing_defer_action_respect_redrive.cs new file mode 100644 index 0000000000..a20f4b1151 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_throwing_defer_action_respect_redrive.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.SQS; +using Amazon.SQS.Model; +using FluentAssertions; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.AWS.Tests.TestDoubles; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Paramore.Brighter.ServiceActivator; +using Polly.Registry; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; + +[Trait("Category", "AWS")] +[Trait("Fragile", "CI")] +public class SnsReDrivePolicySDlqTests : IDisposable, IAsyncDisposable +{ + private readonly IAmAMessagePump _messagePump; + private readonly Message _message; + private readonly string _dlqChannelName; + private readonly IAmAChannelSync _channel; + private readonly SnsMessageProducer _sender; + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly SqsSubscription _subscription; + private readonly ChannelFactory _channelFactory; + + public SnsReDrivePolicySDlqTests() + { + string correlationId = Guid.NewGuid().ToString(); + string replyTo = "http:\\queueUrl"; + string contentType = "text\\plain"; + var channelName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _dlqChannelName = $"Redrive-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + string topicName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(topicName); + + //how are we consuming + _subscription = new SqsSubscription( + name: new SubscriptionName(channelName), + channelName: new ChannelName(channelName), + routingKey: routingKey, + //don't block the redrive policy from owning retry management + requeueCount: -1, + //delay before requeuing + requeueDelay: TimeSpan.FromMilliseconds(50), + messagePumpType: MessagePumpType.Reactor, + //we want our SNS subscription to manage requeue limits using the DLQ for 'too many requeues' + redrivePolicy: new RedrivePolicy + ( + deadLetterQueueName: new ChannelName(_dlqChannelName), + maxReceiveCount: 2 + )); + + //what do we send + var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; + _message = new Message( + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + replyTo: new RoutingKey(replyTo), contentType: contentType), + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) + ); + + //Must have credentials stored in the SDK Credentials store or shared credentials file + _awsConnection = GatewayFactory.CreateFactory(); + + //how do we send to the queue + _sender = new SnsMessageProducer( + _awsConnection, + new SnsPublication + { + Topic = routingKey, RequestType = typeof(MyDeferredCommand), MakeChannels = OnMissingChannel.Create + } + ); + + //We need to do this manually in a test - will create the channel from subscriber parameters + _channelFactory = new ChannelFactory(_awsConnection); + _channel = _channelFactory.CreateSyncChannel(_subscription); + + //how do we handle a command + IHandleRequests handler = new MyDeferredCommandHandler(); + + //hook up routing for the command processor + var subscriberRegistry = new SubscriberRegistry(); + subscriberRegistry.Register(); + + //once we read, how do we dispatch to a handler. N.B. we don't use this for reading here + IAmACommandProcessor commandProcessor = new CommandProcessor( + subscriberRegistry: subscriberRegistry, + handlerFactory: new QuickHandlerFactory(() => handler), + requestContextFactory: new InMemoryRequestContextFactory(), + policyRegistry: new PolicyRegistry() + ); + var provider = new CommandProcessorProvider(commandProcessor); + + var messageMapperRegistry = new MessageMapperRegistry( + new SimpleMessageMapperFactory(_ => new MyDeferredCommandMessageMapper()), + null + ); + messageMapperRegistry.Register(); + + //pump messages from a channel to a handler - in essence we are building our own dispatcher in this test + _messagePump = new Reactor(provider, messageMapperRegistry, + null, new InMemoryRequestContextFactory(), _channel) + { + Channel = _channel, TimeOut = TimeSpan.FromMilliseconds(5000), RequeueCount = 3 + }; + } + + private int GetDLQCount(string queueName) + { + using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); + var queueUrlResponse = sqsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); + var response = sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest + { + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 5, + MessageSystemAttributeNames = ["ApproximateReceiveCount"], + MessageAttributeNames = new List { "All" } + }).GetAwaiter().GetResult(); + + if (response.HttpStatusCode != HttpStatusCode.OK) + { + throw new AmazonSQSException( + $"Failed to GetMessagesAsync for queue {queueName}. Response: {response.HttpStatusCode}"); + } + + return response.Messages.Count; + } + + + [Fact] + public async Task When_throwing_defer_action_respect_redrive() + { + //put something on an SNS topic, which will be delivered to our SQS queue + _sender.Send(_message); + + //start a message pump, let it process messages + var task = Task.Factory.StartNew(() => _messagePump.Run(), TaskCreationOptions.LongRunning); + await Task.Delay(5000); + + //send a quit message to the pump to terminate it + var quitMessage = MessageFactory.CreateQuitMessage(_subscription.RoutingKey); + _channel.Enqueue(quitMessage); + + //wait for the pump to stop once it gets a quit message + await Task.WhenAll(task); + + await Task.Delay(5000); + + //inspect the dlq + GetDLQCount(_dlqChannelName).Should().Be(1); + } + + public void Dispose() + { + _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteQueueAsync().Wait(); + } + + public async ValueTask DisposeAsync() + { + await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteQueueAsync(); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_topic_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_topic_missing_verify_throws.cs new file mode 100644 index 0000000000..c98b654bc4 --- /dev/null +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_topic_missing_verify_throws.cs @@ -0,0 +1,39 @@ +using System; +using Paramore.Brighter.AWS.Tests.Helpers; +using Paramore.Brighter.MessagingGateway.AWSSQS; +using Xunit; + +namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; + +[Trait("Category", "AWS")] +public class AWSValidateMissingTopicTests +{ + private readonly AWSMessagingGatewayConnection _awsConnection; + private readonly RoutingKey _routingKey; + + public AWSValidateMissingTopicTests() + { + string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey(topicName); + + _awsConnection = GatewayFactory.CreateFactory(); + + //Because we don't use channel factory to create the infrastructure -it won't exist + } + + [Fact] + public void When_topic_missing_verify_throws() + { + //arrange + var producer = new SnsMessageProducer(_awsConnection, + new SnsPublication + { + MakeChannels = OnMissingChannel.Validate, + }); + + //act && assert + Assert.Throws(() => producer.Send(new Message( + new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text"), + new MessageBody("Test")))); + } +} diff --git a/tests/Paramore.Brighter.AWS.Tests/Paramore.Brighter.AWS.Tests.csproj b/tests/Paramore.Brighter.AWS.Tests/Paramore.Brighter.AWS.Tests.csproj index a2039e9a67..6efd2fd56d 100644 --- a/tests/Paramore.Brighter.AWS.Tests/Paramore.Brighter.AWS.Tests.csproj +++ b/tests/Paramore.Brighter.AWS.Tests/Paramore.Brighter.AWS.Tests.csproj @@ -28,4 +28,8 @@ + + + + diff --git a/tests/Paramore.Brighter.AWS.Tests/TestDoubles/MyDeferredCommandHandlerAsync.cs b/tests/Paramore.Brighter.AWS.Tests/TestDoubles/MyDeferredCommandHandlerAsync.cs index a6228a8a5f..12500f660e 100644 --- a/tests/Paramore.Brighter.AWS.Tests/TestDoubles/MyDeferredCommandHandlerAsync.cs +++ b/tests/Paramore.Brighter.AWS.Tests/TestDoubles/MyDeferredCommandHandlerAsync.cs @@ -1,8 +1,8 @@ using System.Threading; using System.Threading.Tasks; -using Paramore.Brighter; using Paramore.Brighter.Actions; -using Paramore.Brighter.AWS.Tests.TestDoubles; + +namespace Paramore.Brighter.AWS.Tests.TestDoubles; internal class MyDeferredCommandHandlerAsync : RequestHandlerAsync { @@ -19,4 +19,4 @@ public override async Task HandleAsync(MyDeferredCommand comm return await base.HandleAsync(command, cancellationToken); } -} +} \ No newline at end of file diff --git a/tests/Paramore.Brighter.AWS.Tests/TestDoubles/QuickHandlerFactoryAsync.cs b/tests/Paramore.Brighter.AWS.Tests/TestDoubles/QuickHandlerFactoryAsync.cs index e2a2cc74a5..5e12ab869d 100644 --- a/tests/Paramore.Brighter.AWS.Tests/TestDoubles/QuickHandlerFactoryAsync.cs +++ b/tests/Paramore.Brighter.AWS.Tests/TestDoubles/QuickHandlerFactoryAsync.cs @@ -1,5 +1,6 @@ using System; -using Paramore.Brighter; + +namespace Paramore.Brighter.AWS.Tests.TestDoubles; public class QuickHandlerFactoryAsync : IAmAHandlerFactoryAsync { @@ -19,4 +20,4 @@ public void Release(IHandleRequestsAsync handler) { // Implement any necessary cleanup logic here } -} +} \ No newline at end of file From e849a3f4b0b8ee8a47e98eb30a0c706fedee52bd Mon Sep 17 00:00:00 2001 From: Rafael Lillo Date: Sat, 4 Jan 2025 16:42:49 +0000 Subject: [PATCH 14/18] Fixes SQS unit tests --- .../ChannelFactory.cs | 4 +- .../SnsMessageProducer.cs | 2 +- .../SqsMessageProducer.cs | 2 +- .../SqsMessageSender.cs | 2 +- .../SqsSubscription.cs | 14 +-- .../CodeAnalysis/NotNullWhenAttribute.cs | 17 +++ src/Paramore.Brighter/RoutingKey.cs | 4 +- ...tructure_exists_can_verify_by_url_async.cs | 2 +- ..._consumer_reads_multiple_messages_async.cs | 23 ++-- ...hen_customising_aws_client_config_async.cs | 23 ++-- ...n_infastructure_exists_can_assume_async.cs | 32 +++--- ...infastructure_exists_can_verify_by_url.cs} | 53 +++++---- ..._infrastructure_exists_can_verify_async.cs | 40 +++---- ...ructure_exists_can_verify_by_url_async.cs} | 56 +++++---- ...message_via_the_messaging_gateway_async.cs | 22 ++-- ...When_queues_missing_assume_throws_async.cs | 11 +- ...When_queues_missing_verify_throws_async.cs | 21 ++-- ...hen_raw_message_delivery_disabled_async.cs | 93 --------------- ...sage_through_gateway_with_requeue_async.cs | 28 ++--- .../When_requeueing_a_message_async.cs | 13 ++- ...en_requeueing_redrives_to_the_dlq_async.cs | 34 +++--- ...wing_defer_action_respect_redrive_async.cs | 28 ++--- .../When_topic_missing_verify_throws_async.cs | 11 +- ...essage_consumer_reads_multiple_messages.cs | 23 ++-- .../When_customising_aws_client_config.cs | 31 ++--- .../When_infastructure_exists_can_assume.cs | 25 ++-- .../When_infastructure_exists_can_verify.cs | 39 ++++--- ...ructure_exists_can_verify_by_convention.cs | 107 ------------------ ...ructure_exists_can_verify_by_convention.cs | 104 ----------------- ...ing_a_message_via_the_messaging_gateway.cs | 23 ++-- .../When_queues_missing_assume_throws.cs | 23 ++-- .../When_queues_missing_verify_throws.cs | 22 ++-- .../When_raw_message_delivery_disabled.cs | 94 --------------- ..._a_message_through_gateway_with_requeue.cs | 50 ++++---- .../Reactor/When_requeueing_a_message.cs | 48 ++++---- .../When_requeueing_redrives_to_the_dlq.cs | 27 +++-- ...n_throwing_defer_action_respect_redrive.cs | 24 ++-- .../When_topic_missing_verify_throws.cs | 7 +- 38 files changed, 401 insertions(+), 781 deletions(-) create mode 100644 src/Paramore.Brighter/CodeAnalysis/NotNullWhenAttribute.cs rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/{When_infastructure_exists_can_verify_by_arn.cs => When_infastructure_exists_can_verify_by_url.cs} (67%) rename tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/{When_infrastructure_exists_can_verify_by_arn_async.cs => When_infrastructure_exists_can_verify_by_url_async.cs} (63%) delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_raw_message_delivery_disabled_async.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify_by_convention.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infrastructure_exists_can_verify_by_convention.cs delete mode 100644 tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_raw_message_delivery_disabled.cs diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs index fa0b718a06..d5c7c2c9d1 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs @@ -122,7 +122,7 @@ await EnsureTopicAsync(_subscription.RoutingKey, await EnsureQueueAsync( _subscription.ChannelName.Value, - _subscription.QueueFindBy, + _subscription.FindQueueBy, SqsAttributes.From(_subscription), _subscription.MakeChannels, ct); @@ -219,7 +219,7 @@ await EnsureTopicAsync(_subscription.RoutingKey, await EnsureQueueAsync( _subscription.ChannelName.Value, - _subscription.QueueFindBy, + _subscription.FindQueueBy, SqsAttributes.From(_subscription), _subscription.MakeChannels); diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs index 571b1fd99f..1a8c222882 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SnsMessageProducer.cs @@ -99,7 +99,7 @@ public async Task ConfirmTopicExistsAsync(string? topic = null, routingKey = _publication.Topic; } - if (routingKey is null) + if (RoutingKey.IsNullOrEmpty(routingKey)) { throw new ConfigurationException("No topic specified for producer"); } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs index df4eece12b..47350bf90f 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs @@ -115,7 +115,7 @@ public async Task ConfirmQueueExistsAsync(string? queue = null, Cancellati routingKey = _publication.Topic; } - if (routingKey is null) + if (RoutingKey.IsNullOrEmpty(routingKey)) { throw new ConfigurationException("No topic specified for producer"); } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageSender.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageSender.cs index f0695aef99..31af946dec 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageSender.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageSender.cs @@ -85,7 +85,7 @@ public SqsMessageSender(string queueUrl, SnsSqsType queueType, AmazonSQSClient c request.MessageAttributes = messageAttributes; var response = await _client.SendMessageAsync(request, cancellationToken); - if (response.HttpStatusCode is System.Net.HttpStatusCode.OK or HttpStatusCode.Created + if (response.HttpStatusCode is HttpStatusCode.OK or HttpStatusCode.Created or HttpStatusCode.Accepted) { return response.MessageId; diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs index 5d9f83cb21..3524fa760b 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs @@ -63,7 +63,7 @@ public class SqsSubscription : Subscription /// QueueFindBy.Url -> the routing key is an URL /// TopicFindBy.Name -> Treat the routing key as a name & use GetQueueUrl to find it /// - public QueueFindBy QueueFindBy { get; } + public QueueFindBy FindQueueBy { get; } /// /// Indicates how we should treat the routing key @@ -154,7 +154,7 @@ public class SqsSubscription : Subscription /// Specifies whether message deduplication occurs at the message group or queue level /// Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group /// Specifies the routing key type - /// How the queue should be found when is point-to-point. + /// How the queue should be found when is point-to-point. public SqsSubscription( Type dataType, SubscriptionName? name = null, @@ -185,7 +185,7 @@ public SqsSubscription( DeduplicationScope? deduplicationScope = null, int? fifoThroughputLimit = null, RoutingKeyType routingKeyType = RoutingKeyType.PubSub, - QueueFindBy queueFindBy = QueueFindBy.Name + QueueFindBy findQueueBy = QueueFindBy.Name ) : base(dataType, name, channelName, routingKey, bufferSize, noOfPerformers, timeOut, requeueCount, requeueDelay, unacceptableMessageLimit, messagePumpType, channelFactory, makeChannels, emptyChannelDelay, @@ -205,7 +205,7 @@ public SqsSubscription( DeduplicationScope = deduplicationScope; FifoThroughputLimit = fifoThroughputLimit; RoutingKeyType = routingKeyType; - QueueFindBy = queueFindBy; + FindQueueBy = findQueueBy; } } @@ -249,7 +249,7 @@ public class SqsSubscription : SqsSubscription where T : IRequest /// Specifies whether message deduplication occurs at the message group or queue level /// Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group /// Specifies the routing key type - /// How the queue should be found when is point-to-point. + /// How the queue should be found when is point-to-point. public SqsSubscription( SubscriptionName? name = null, ChannelName? channelName = null, @@ -279,7 +279,7 @@ public SqsSubscription( DeduplicationScope? deduplicationScope = null, int? fifoThroughputLimit = null, RoutingKeyType routingKeyType = RoutingKeyType.PubSub, - QueueFindBy queueFindBy = QueueFindBy.Name + QueueFindBy findQueueBy = QueueFindBy.Name ) : base(typeof(T), name, channelName, routingKey, bufferSize, noOfPerformers, timeOut, requeueCount, requeueDelay, @@ -287,7 +287,7 @@ public SqsSubscription( messageRetentionPeriod, findTopicBy, iAmPolicy, redrivePolicy, snsAttributes, tags, makeChannels, rawMessageDelivery, emptyChannelDelay, channelFailureDelay, sqsType, contentBasedDeduplication, deduplicationScope, fifoThroughputLimit, - routingKeyType) + routingKeyType, findQueueBy) { } } diff --git a/src/Paramore.Brighter/CodeAnalysis/NotNullWhenAttribute.cs b/src/Paramore.Brighter/CodeAnalysis/NotNullWhenAttribute.cs new file mode 100644 index 0000000000..0bdcb6353a --- /dev/null +++ b/src/Paramore.Brighter/CodeAnalysis/NotNullWhenAttribute.cs @@ -0,0 +1,17 @@ +#if NETSTANDARD +namespace System.Diagnostics.CodeAnalysis; + +[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +public sealed class NotNullWhenAttribute : Attribute +{ + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } +} + +#endif diff --git a/src/Paramore.Brighter/RoutingKey.cs b/src/Paramore.Brighter/RoutingKey.cs index 89832d97b8..ef7f6546ba 100644 --- a/src/Paramore.Brighter/RoutingKey.cs +++ b/src/Paramore.Brighter/RoutingKey.cs @@ -22,6 +22,8 @@ THE SOFTWARE. */ #endregion +using System.Diagnostics.CodeAnalysis; + namespace Paramore.Brighter { /// @@ -49,7 +51,7 @@ public RoutingKey(string name) /// /// The routing key to test /// - public static bool IsNullOrEmpty(RoutingKey? routingKey) + public static bool IsNullOrEmpty([NotNullWhen(false)]RoutingKey? routingKey) { return routingKey is null || string.IsNullOrEmpty(routingKey.Value); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs index 89adfe2cd1..8c5eb924f2 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs @@ -57,7 +57,7 @@ public AWSValidateInfrastructureByUrlTestsAsync() name: new SubscriptionName(queueName), channelName: channel.Name, routingKey: routingKey, - queueFindBy: QueueFindBy.Url, + findQueueBy: QueueFindBy.Url, makeChannels: OnMissingChannel.Validate, sqsType: SnsSqsType.Fifo, routingKeyType: RoutingKeyType.PointToPoint diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs index 2730234f9e..ff21bdbd65 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs @@ -14,9 +14,9 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; [Trait("Fragile", "CI")] public class SQSBufferedConsumerTestsAsync : IDisposable, IAsyncDisposable { - private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageProducer _messageProducer; private readonly SqsMessageConsumer _consumer; - private readonly string _topicName; + private readonly string _queueName; private readonly ChannelFactory _channelFactory; private const string ContentType = "text\\plain"; private const int BufferSize = 3; @@ -27,31 +27,32 @@ public SQSBufferedConsumerTestsAsync() var awsConnection = GatewayFactory.CreateFactory(); _channelFactory = new ChannelFactory(awsConnection); - var channelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _topicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var subscriptionName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _queueName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); //we need the channel to create the queues and notifications - var routingKey = new RoutingKey(_topicName); + var routingKey = new RoutingKey(_queueName); var channel = _channelFactory.CreateAsyncChannelAsync(new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(_queueName), routingKey: routingKey, bufferSize: BufferSize, - makeChannels: OnMissingChannel.Create + makeChannels: OnMissingChannel.Create, + routingKeyType: RoutingKeyType.PointToPoint )).GetAwaiter().GetResult(); //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel //just for the tests, so create a new consumer from the properties _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), BufferSize); - _messageProducer = new SnsMessageProducer(awsConnection, - new SnsPublication { MakeChannels = OnMissingChannel.Create }); + _messageProducer = new SqsMessageProducer(awsConnection, + new SqsPublication { MakeChannels = OnMissingChannel.Create }); } [Fact] public async Task When_a_message_consumer_reads_multiple_messages_async() { - var routingKey = new RoutingKey(_topicName); + var routingKey = new RoutingKey(_queueName); var messageOne = new Message( new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_customising_aws_client_config_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_customising_aws_client_config_async.cs index d5a033cd7d..3540a063ad 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_customising_aws_client_config_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_customising_aws_client_config_async.cs @@ -14,7 +14,7 @@ public class CustomisingAwsClientConfigTestsAsync : IDisposable, IAsyncDisposabl { private readonly Message _message; private readonly IAmAChannelAsync _channel; - private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; public CustomisingAwsClientConfigTestsAsync() @@ -23,15 +23,16 @@ public CustomisingAwsClientConfigTestsAsync() const string replyTo = "http:\\queueUrl"; const string contentType = "text\\plain"; string correlationId = Guid.NewGuid().ToString(); - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); var subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), messagePumpType: MessagePumpType.Proactor, - routingKey: routingKey + routingKey: routingKey, + routingKeyType: RoutingKeyType.PointToPoint ); _message = new Message( @@ -43,7 +44,7 @@ public CustomisingAwsClientConfigTestsAsync() var subscribeAwsConnection = GatewayFactory.CreateFactory(config => { config.HttpClientFactory = - new InterceptingHttpClientFactory(new InterceptingDelegatingHandler("async_sub")); + new InterceptingHttpClientFactory(new InterceptingDelegatingHandler("sqs_async_sub")); }); _channelFactory = new ChannelFactory(subscribeAwsConnection); @@ -52,11 +53,11 @@ public CustomisingAwsClientConfigTestsAsync() var publishAwsConnection = GatewayFactory.CreateFactory(config => { config.HttpClientFactory = - new InterceptingHttpClientFactory(new InterceptingDelegatingHandler("async_pub")); + new InterceptingHttpClientFactory(new InterceptingDelegatingHandler("sqs_async_pub")); }); - _messageProducer = new SnsMessageProducer(publishAwsConnection, - new SnsPublication { Topic = new RoutingKey(topicName), MakeChannels = OnMissingChannel.Create }); + _messageProducer = new SqsMessageProducer(publishAwsConnection, + new SqsPublication { Topic = new RoutingKey(queueName), MakeChannels = OnMissingChannel.Create }); } [Fact] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_assume_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_assume_async.cs index cac101dd76..695efbff16 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_assume_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_assume_async.cs @@ -13,28 +13,30 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] public class AWSAssumeInfrastructureTestsAsync : IDisposable, IAsyncDisposable -{ private readonly Message _message; +{ + private readonly Message _message; private readonly SqsMessageConsumer _consumer; - private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; public AWSAssumeInfrastructureTestsAsync() { _myCommand = new MyCommand{Value = "Test"}; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + var subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Create + makeChannels: OnMissingChannel.Create, + routingKeyType: RoutingKeyType.PointToPoint ); _message = new Message( @@ -53,14 +55,14 @@ public AWSAssumeInfrastructureTestsAsync() //Now change the subscription to validate, just check what we made subscription = new( - name: new SubscriptionName(channelName), + name: new SubscriptionName(subscriptionName), channelName: channel.Name, routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, makeChannels: OnMissingChannel.Assume ); - _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Assume}); + _messageProducer = new SqsMessageProducer(awsConnection, new SqsPublication{MakeChannels = OnMissingChannel.Assume}); _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); } @@ -69,7 +71,7 @@ public AWSAssumeInfrastructureTestsAsync() public async Task When_infastructure_exists_can_assume() { //arrange - await _messageProducer.SendAsync(_message); + await _messageProducer.SendAsync(_message); var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_verify_by_arn.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_verify_by_url.cs similarity index 67% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_verify_by_arn.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_verify_by_url.cs index b400bdb6c3..2538451f27 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_verify_by_arn.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_verify_by_url.cs @@ -14,29 +14,31 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class AWSValidateInfrastructureByArnTests : IDisposable, IAsyncDisposable +public class AWSValidateInfrastructureByUrlTests : IDisposable, IAsyncDisposable { private readonly Message _message; private readonly IAmAMessageConsumerSync _consumer; - private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; - public AWSValidateInfrastructureByArnTests() + public AWSValidateInfrastructureByUrlTests() { _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey($"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Create + makeChannels: OnMissingChannel.Create, + routingKeyType: RoutingKeyType.PointToPoint ); _message = new Message( @@ -45,9 +47,7 @@ public AWSValidateInfrastructureByArnTests() new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); - - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = GatewayFactory.CreateFactory(credentials, region); + var awsConnection = GatewayFactory.CreateFactory(); //We need to do this manually in a test - will create the channel from subscriber parameters //This doesn't look that different from our create tests - this is because we create using the channel factory in @@ -55,26 +55,25 @@ public AWSValidateInfrastructureByArnTests() _channelFactory = new ChannelFactory(awsConnection); var channel = _channelFactory.CreateSyncChannel(subscription); - var topicArn = FindTopicArn(awsConnection, routingKey.Value); - var routingKeyArn = new RoutingKey(topicArn); + var queueUrl = FindQueueUrl(awsConnection, routingKey.Value); //Now change the subscription to validate, just check what we made subscription = new( - name: new SubscriptionName(channelName), + name: new SubscriptionName(subscriptionName), channelName: channel.Name, - routingKey: routingKeyArn, - findTopicBy: TopicFindBy.Arn, + routingKey: routingKey, + findQueueBy: QueueFindBy.Url, messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Validate ); - _messageProducer = new SnsMessageProducer( + _messageProducer = new SqsMessageProducer( awsConnection, - new SnsPublication + new SqsPublication { Topic = routingKey, - TopicArn = topicArn, - FindTopicBy = TopicFindBy.Arn, + QueueUrl= queueUrl, + FindQueueBy = QueueFindBy.Url, MakeChannels = OnMissingChannel.Validate }); @@ -116,10 +115,10 @@ public async ValueTask DisposeAsync() await _messageProducer.DisposeAsync(); } - private static string FindTopicArn(AWSMessagingGatewayConnection connection, string topicName) + private static string FindQueueUrl(AWSMessagingGatewayConnection connection, string queueName) { - using var snsClient = new AWSClientFactory(connection).CreateSnsClient(); - var topicResponse = snsClient.FindTopicAsync(topicName).GetAwaiter().GetResult(); - return topicResponse.TopicArn; + using var snsClient = new AWSClientFactory(connection).CreateSqsClient(); + var topicResponse = snsClient.GetQueueUrlAsync(queueName).GetAwaiter().GetResult(); + return topicResponse.QueueUrl; } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_async.cs index c6be3f81ac..83ae2757ba 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_async.cs @@ -16,26 +16,27 @@ public class AWSValidateInfrastructureTestsAsync : IDisposable, IAsyncDisposable { private readonly Message _message; private readonly IAmAMessageConsumerAsync _consumer; - private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; public AWSValidateInfrastructureTestsAsync() { _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Create + makeChannels: OnMissingChannel.Create, + routingKeyType: RoutingKeyType.PointToPoint ); _message = new Message( @@ -50,21 +51,22 @@ public AWSValidateInfrastructureTestsAsync() var channel = _channelFactory.CreateAsyncChannel(subscription); subscription = new( - name: new SubscriptionName(channelName), + name: new SubscriptionName(subscriptionName), channelName: channel.Name, routingKey: routingKey, findTopicBy: TopicFindBy.Name, messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Validate + makeChannels: OnMissingChannel.Validate, + routingKeyType: RoutingKeyType.PointToPoint ); - _messageProducer = new SnsMessageProducer( + _messageProducer = new SqsMessageProducer( awsConnection, - new SnsPublication + new SqsPublication { - FindTopicBy = TopicFindBy.Name, + FindQueueBy = QueueFindBy.Name, MakeChannels = OnMissingChannel.Validate, - Topic = new RoutingKey(topicName) + Topic = new RoutingKey(queueName) } ); @@ -85,7 +87,7 @@ public async Task When_infrastructure_exists_can_verify_async() await _consumer.AcknowledgeAsync(message); } - + public void Dispose() { //Clean up resources that we have created diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_by_arn_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs similarity index 63% rename from tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_by_arn_async.cs rename to tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs index 6b7fb5f886..d63838a017 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_by_arn_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs @@ -2,8 +2,6 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using Amazon; -using Amazon.Runtime; using FluentAssertions; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.AWS.Tests.TestDoubles; @@ -14,29 +12,31 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; [Trait("Category", "AWS")] [Trait("Fragile", "CI")] -public class AWSValidateInfrastructureByArnTestsAsync : IAsyncDisposable, IDisposable +public class AWSValidateInfrastructureByUrlTestsAsync : IAsyncDisposable, IDisposable { private readonly Message _message; private readonly IAmAMessageConsumerAsync _consumer; - private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; - public AWSValidateInfrastructureByArnTestsAsync() + public AWSValidateInfrastructureByUrlTestsAsync() { _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey($"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Create + makeChannels: OnMissingChannel.Create, + routingKeyType: RoutingKeyType.PointToPoint ); _message = new Message( @@ -45,30 +45,28 @@ public AWSValidateInfrastructureByArnTestsAsync() new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); - (AWSCredentials credentials, RegionEndpoint region) = CredentialsChain.GetAwsCredentials(); - var awsConnection = GatewayFactory.CreateFactory(credentials, region); + var awsConnection = GatewayFactory.CreateFactory(); _channelFactory = new ChannelFactory(awsConnection); var channel = _channelFactory.CreateAsyncChannel(subscription); - var topicArn = FindTopicArn(awsConnection, routingKey.Value).Result; - var routingKeyArn = new RoutingKey(topicArn); + var queueUrl = FindQueueUrl(awsConnection, routingKey.Value).Result; subscription = new( - name: new SubscriptionName(channelName), + name: new SubscriptionName(subscriptionName), channelName: channel.Name, - routingKey: routingKeyArn, + routingKey: routingKey, findTopicBy: TopicFindBy.Arn, makeChannels: OnMissingChannel.Validate ); - _messageProducer = new SnsMessageProducer( + _messageProducer = new SqsMessageProducer( awsConnection, - new SnsPublication + new SqsPublication { Topic = routingKey, - TopicArn = topicArn, - FindTopicBy = TopicFindBy.Arn, + QueueUrl = queueUrl, + FindQueueBy = QueueFindBy.Url, MakeChannels = OnMissingChannel.Validate }); @@ -90,11 +88,11 @@ public async Task When_infrastructure_exists_can_verify_async() await _consumer.AcknowledgeAsync(message); } - private static async Task FindTopicArn(AWSMessagingGatewayConnection connection, string topicName) + private static async Task FindQueueUrl(AWSMessagingGatewayConnection connection, string queueName) { - using var snsClient = new AWSClientFactory(connection).CreateSnsClient(); - var topicResponse = await snsClient.FindTopicAsync(topicName); - return topicResponse.TopicArn; + using var snsClient = new AWSClientFactory(connection).CreateSqsClient(); + var response = await snsClient.GetQueueUrlAsync(queueName); + return response.QueueUrl; } public void Dispose() diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs index 69db8bb110..845d14e139 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs @@ -14,13 +14,13 @@ public class SqsMessageProducerSendAsyncTests : IAsyncDisposable, IDisposable { private readonly Message _message; private readonly IAmAChannelAsync _channel; - private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; private readonly string _correlationId; private readonly string _replyTo; private readonly string _contentType; - private readonly string _topicName; + private readonly string _queueName; public SqsMessageProducerSendAsyncTests() { @@ -28,16 +28,16 @@ public SqsMessageProducerSendAsyncTests() _correlationId = Guid.NewGuid().ToString(); _replyTo = "http:\\queueUrl"; _contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(_topicName); + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(_queueName); - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + var subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(_queueName), routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, - rawMessageDelivery: false + routingKeyType: RoutingKeyType.PointToPoint ); _message = new Message( @@ -51,7 +51,7 @@ public SqsMessageProducerSendAsyncTests() _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateAsyncChannel(subscription); - _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create }); + _messageProducer = new SqsMessageProducer(awsConnection, new SqsPublication { Topic = new RoutingKey(_queueName), MakeChannels = OnMissingChannel.Create }); } [Fact] @@ -74,7 +74,7 @@ public async Task When_posting_a_message_via_the_producer_async() message.Id.Should().Be(_myCommand.Id); message.Redelivered.Should().BeFalse(); message.Header.MessageId.Should().Be(_myCommand.Id); - message.Header.Topic.Value.Should().Contain(_topicName); + message.Header.Topic.Value.Should().Contain(_queueName); message.Header.CorrelationId.Should().Be(_correlationId); message.Header.ReplyTo.Should().Be(_replyTo); message.Header.ContentType.Should().Be(_contentType); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_assume_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_assume_throws_async.cs index 4c32ca9043..5911a3f620 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_assume_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_assume_throws_async.cs @@ -17,15 +17,16 @@ public class AWSAssumeQueuesTestsAsync : IAsyncDisposable, IDisposable public AWSAssumeQueuesTestsAsync() { var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); var subscription = new SqsSubscription( name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + channelName: new ChannelName(queueName), routingKey: routingKey, makeChannels: OnMissingChannel.Assume, - messagePumpType: MessagePumpType.Proactor + messagePumpType: MessagePumpType.Proactor, + routingKeyType: RoutingKeyType.PointToPoint ); var awsConnection = GatewayFactory.CreateFactory(); @@ -38,7 +39,7 @@ public AWSAssumeQueuesTestsAsync() MakeChannels = OnMissingChannel.Create }); - producer.ConfirmTopicExistsAsync(topicName).Wait(); + producer.ConfirmTopicExistsAsync(queueName).Wait(); _channelFactory = new ChannelFactory(awsConnection); var channel = _channelFactory.CreateAsyncChannel(subscription); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_verify_throws_async.cs index 39da97ce59..3edb7fb752 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_verify_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_verify_throws_async.cs @@ -17,26 +17,19 @@ public class AWSValidateQueuesTestsAsync : IAsyncDisposable public AWSValidateQueuesTestsAsync() { - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); _subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), routingKey: routingKey, - makeChannels: OnMissingChannel.Validate + makeChannels: OnMissingChannel.Validate, + routingKeyType: RoutingKeyType.PointToPoint ); _awsConnection = GatewayFactory.CreateFactory(); - - // We need to create the topic at least, to check the queues - var producer = new SnsMessageProducer(_awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create - }); - producer.ConfirmTopicExistsAsync(topicName).Wait(); } [Fact] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_raw_message_delivery_disabled_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_raw_message_delivery_disabled_async.cs deleted file mode 100644 index ea13c2872e..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_raw_message_delivery_disabled_async.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; - -[Trait("Category", "AWS")] -[Trait("Fragile", "CI")] -public class SqsRawMessageDeliveryTestsAsync : IAsyncDisposable, IDisposable -{ - private readonly SnsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly IAmAChannelAsync _channel; - private readonly RoutingKey _routingKey; - - public SqsRawMessageDeliveryTestsAsync() - { - var awsConnection = GatewayFactory.CreateFactory(); - - _channelFactory = new ChannelFactory(awsConnection); - var channelName = $"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _routingKey = new RoutingKey($"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); - - var bufferSize = 10; - - // Set rawMessageDelivery to false - _channel = _channelFactory.CreateAsyncChannel(new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: _routingKey, - bufferSize: bufferSize, - makeChannels: OnMissingChannel.Create, - rawMessageDelivery: false)); - - _messageProducer = new SnsMessageProducer(awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create - }); - } - - [Fact] - public async Task When_raw_message_delivery_disabled_async() - { - // Arrange - var messageHeader = new MessageHeader( - Guid.NewGuid().ToString(), - _routingKey, - MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), - replyTo: RoutingKey.Empty, - contentType: "text\\plain"); - - var customHeaderItem = new KeyValuePair("custom-header-item", "custom-header-item-value"); - messageHeader.Bag.Add(customHeaderItem.Key, customHeaderItem.Value); - - var messageToSend = new Message(messageHeader, new MessageBody("test content one")); - - // Act - await _messageProducer.SendAsync(messageToSend); - - var messageReceived = await _channel.ReceiveAsync(TimeSpan.FromMilliseconds(10000)); - - await _channel.AcknowledgeAsync(messageReceived); - - // Assert - messageReceived.Id.Should().Be(messageToSend.Id); - messageReceived.Header.Topic.Should().Be(messageToSend.Header.Topic); - messageReceived.Header.MessageType.Should().Be(messageToSend.Header.MessageType); - messageReceived.Header.CorrelationId.Should().Be(messageToSend.Header.CorrelationId); - messageReceived.Header.ReplyTo.Should().Be(messageToSend.Header.ReplyTo); - messageReceived.Header.ContentType.Should().Be(messageToSend.Header.ContentType); - messageReceived.Header.Bag.Should().ContainKey(customHeaderItem.Key).And.ContainValue(customHeaderItem.Value); - messageReceived.Body.Value.Should().Be(messageToSend.Body.Value); - } - - public void Dispose() - { - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs index 558c295c61..c8d1cf7653 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs @@ -15,26 +15,27 @@ public class SqsMessageConsumerRequeueTestsAsync : IDisposable, IAsyncDisposable { private readonly Message _message; private readonly IAmAChannelAsync _channel; - private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; public SqsMessageConsumerRequeueTestsAsync() { _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Create + makeChannels: OnMissingChannel.Create, + routingKeyType: RoutingKeyType.PointToPoint ); _message = new Message( @@ -48,7 +49,8 @@ public SqsMessageConsumerRequeueTestsAsync() _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateAsyncChannel(subscription); - _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); + _messageProducer = + new SqsMessageProducer(awsConnection, new SqsPublication { MakeChannels = OnMissingChannel.Create }); } [Fact] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_a_message_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_a_message_async.cs index 9a1101624f..9c6e7b9bf2 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_a_message_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_a_message_async.cs @@ -23,19 +23,20 @@ public class SqsMessageProducerRequeueTestsAsync : IDisposable, IAsyncDisposable public SqsMessageProducerRequeueTestsAsync() { MyCommand myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); var channelName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + var queueName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); var subscription = new SqsSubscription( name: new SubscriptionName(channelName), channelName: new ChannelName(channelName), routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Create + makeChannels: OnMissingChannel.Create, + routingKeyType: RoutingKeyType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_redrives_to_the_dlq_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_redrives_to_the_dlq_async.cs index 376d9d5510..ff6bba6c3a 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_redrives_to_the_dlq_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_redrives_to_the_dlq_async.cs @@ -17,7 +17,7 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Proactor; [Trait("Fragile", "CI")] public class SqsMessageProducerDlqTestsAsync : IDisposable, IAsyncDisposable { - private readonly SnsMessageProducer _sender; + private readonly SqsMessageProducer _sender; private readonly IAmAChannelAsync _channel; private readonly ChannelFactory _channelFactory; private readonly Message _message; @@ -27,20 +27,22 @@ public class SqsMessageProducerDlqTestsAsync : IDisposable, IAsyncDisposable public SqsMessageProducerDlqTestsAsync() { MyCommand myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + _dlqChannelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, - redrivePolicy: new RedrivePolicy(_dlqChannelName, 2) + redrivePolicy: new RedrivePolicy(_dlqChannelName, 2), + routingKeyType: RoutingKeyType.PointToPoint ); _message = new Message( @@ -51,10 +53,8 @@ public SqsMessageProducerDlqTestsAsync() _awsConnection = GatewayFactory.CreateFactory(); - _sender = new SnsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); - - _sender.ConfirmTopicExistsAsync(topicName).Wait(); - + _sender = new SqsMessageProducer(_awsConnection, new SqsPublication { MakeChannels = OnMissingChannel.Create }); + _channelFactory = new ChannelFactory(_awsConnection); _channel = _channelFactory.CreateAsyncChannel(subscription); } @@ -86,7 +86,7 @@ private async Task GetDLQCountAsync(string queueName) { QueueUrl = queueUrlResponse.QueueUrl, WaitTimeSeconds = 5, - MessageAttributeNames = new List { "All", "ApproximateReceiveCount" } + MessageAttributeNames = ["All", "ApproximateReceiveCount"] }); if (response.HttpStatusCode != HttpStatusCode.OK) diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_throwing_defer_action_respect_redrive_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_throwing_defer_action_respect_redrive_async.cs index e0bfd2de3e..843684e393 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_throwing_defer_action_respect_redrive_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_throwing_defer_action_respect_redrive_async.cs @@ -23,29 +23,31 @@ public class SnsReDrivePolicySDlqTestsAsync : IDisposable, IAsyncDisposable private readonly Message _message; private readonly string _dlqChannelName; private readonly IAmAChannelAsync _channel; - private readonly SnsMessageProducer _sender; + private readonly SqsMessageProducer _sender; private readonly AWSMessagingGatewayConnection _awsConnection; private readonly SqsSubscription _subscription; private readonly ChannelFactory _channelFactory; public SnsReDrivePolicySDlqTestsAsync() { - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + _dlqChannelName = $"Redrive-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); _subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), routingKey: routingKey, requeueCount: -1, requeueDelay: TimeSpan.FromMilliseconds(50), messagePumpType: MessagePumpType.Proactor, - redrivePolicy: new RedrivePolicy(new ChannelName(_dlqChannelName), 2) + redrivePolicy: new RedrivePolicy(new ChannelName(_dlqChannelName), 2), + routingKeyType: RoutingKeyType.PointToPoint ); var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; @@ -57,9 +59,9 @@ public SnsReDrivePolicySDlqTestsAsync() _awsConnection = GatewayFactory.CreateFactory(); - _sender = new SnsMessageProducer( + _sender = new SqsMessageProducer( _awsConnection, - new SnsPublication + new SqsPublication { Topic = routingKey, RequestType = typeof(MyDeferredCommand), @@ -96,7 +98,7 @@ public SnsReDrivePolicySDlqTestsAsync() }; } - public async Task GetDLQCountAsync(string queueName) + private async Task GetDLQCountAsync(string queueName) { using var sqsClient = new AWSClientFactory(_awsConnection).CreateSqsClient(); var queueUrlResponse = await sqsClient.GetQueueUrlAsync(queueName); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_topic_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_topic_missing_verify_throws_async.cs index 561fb2cebc..fd0c7cb261 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_topic_missing_verify_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_topic_missing_verify_throws_async.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Amazon.SQS.Model; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; @@ -14,8 +15,8 @@ public class AWSValidateMissingTopicTestsAsync public AWSValidateMissingTopicTestsAsync() { - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _routingKey = new RoutingKey(topicName); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _routingKey = new RoutingKey(queueName); _awsConnection = GatewayFactory.CreateFactory(); @@ -26,14 +27,14 @@ public AWSValidateMissingTopicTestsAsync() public async Task When_topic_missing_verify_throws_async() { // arrange - var producer = new SnsMessageProducer(_awsConnection, - new SnsPublication + var producer = new SqsMessageProducer(_awsConnection, + new SqsPublication { MakeChannels = OnMissingChannel.Validate }); // act & assert - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAsync(async () => await producer.SendAsync(new Message( new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text"), new MessageBody("Test")))); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_a_message_consumer_reads_multiple_messages.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_a_message_consumer_reads_multiple_messages.cs index 135a00d434..444f631d9b 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_a_message_consumer_reads_multiple_messages.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_a_message_consumer_reads_multiple_messages.cs @@ -14,9 +14,9 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; [Trait("Fragile", "CI")] public class SQSBufferedConsumerTests : IDisposable, IAsyncDisposable { - private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageProducer _messageProducer; private readonly SqsMessageConsumer _consumer; - private readonly string _topicName; + private readonly string _queueName; private readonly ChannelFactory _channelFactory; private const string ContentType = "text\\plain"; private const int BufferSize = 3; @@ -27,25 +27,26 @@ public SQSBufferedConsumerTests() var awsConnection = GatewayFactory.CreateFactory(); _channelFactory = new ChannelFactory(awsConnection); - var channelName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _topicName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + _queueName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var subscriptionName = $"Buffered-Consumer-Tests-{Guid.NewGuid().ToString()}".Truncate(45); //we need the channel to create the queues and notifications - var routingKey = new RoutingKey(_topicName); + var routingKey = new RoutingKey(_queueName); var channel = _channelFactory.CreateSyncChannel(new SqsSubscription( - name: new SubscriptionName(channelName), - channelName:new ChannelName(channelName), + name: new SubscriptionName(subscriptionName), + channelName:new ChannelName(_queueName), routingKey:routingKey, bufferSize: BufferSize, - makeChannels: OnMissingChannel.Create + makeChannels: OnMissingChannel.Create, + routingKeyType: RoutingKeyType.PointToPoint )); //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel //just for the tests, so create a new consumer from the properties _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName(), BufferSize); - _messageProducer = new SnsMessageProducer(awsConnection, - new SnsPublication + _messageProducer = new SqsMessageProducer(awsConnection, + new SqsPublication { MakeChannels = OnMissingChannel.Create }); @@ -54,7 +55,7 @@ public SQSBufferedConsumerTests() [Fact] public async Task When_a_message_consumer_reads_multiple_messages() { - var routingKey = new RoutingKey(_topicName); + var routingKey = new RoutingKey(_queueName); var messageOne = new Message( new MessageHeader(Guid.NewGuid().ToString(), routingKey, MessageType.MT_COMMAND, diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_customising_aws_client_config.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_customising_aws_client_config.cs index 99ddd3f37a..07c969c687 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_customising_aws_client_config.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_customising_aws_client_config.cs @@ -14,7 +14,7 @@ public class CustomisingAwsClientConfigTests : IDisposable, IAsyncDisposable { private readonly Message _message; private readonly IAmAChannelSync _channel; - private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; public CustomisingAwsClientConfigTests() @@ -23,15 +23,16 @@ public CustomisingAwsClientConfigTests() const string contentType = "text\\plain"; MyCommand myCommand = new() { Value = "Test" }; var correlationId = Guid.NewGuid().ToString(); - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); var subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), messagePumpType: MessagePumpType.Reactor, - routingKey: routingKey + routingKey: routingKey, + routingKeyType: RoutingKeyType.PointToPoint ); _message = new Message( @@ -42,7 +43,7 @@ public CustomisingAwsClientConfigTests() var subscribeAwsConnection = GatewayFactory.CreateFactory(config => { - config.HttpClientFactory = new InterceptingHttpClientFactory(new InterceptingDelegatingHandler("sync_sub")); + config.HttpClientFactory = new InterceptingHttpClientFactory(new InterceptingDelegatingHandler("sqs_sync_sub")); }); _channelFactory = new ChannelFactory(subscribeAwsConnection); @@ -50,11 +51,11 @@ public CustomisingAwsClientConfigTests() var publishAwsConnection = GatewayFactory.CreateFactory(config => { - config.HttpClientFactory = new InterceptingHttpClientFactory(new InterceptingDelegatingHandler("sync_pub")); + config.HttpClientFactory = new InterceptingHttpClientFactory(new InterceptingDelegatingHandler("sqs_sync_pub")); }); - _messageProducer = new SnsMessageProducer(publishAwsConnection, - new SnsPublication { Topic = new RoutingKey(topicName), MakeChannels = OnMissingChannel.Create }); + _messageProducer = new SqsMessageProducer(publishAwsConnection, + new SqsPublication { Topic = new RoutingKey(queueName), MakeChannels = OnMissingChannel.Create }); } [Fact] @@ -71,11 +72,11 @@ public async Task When_customising_aws_client_config() _channel.Acknowledge(message); //publish_and_subscribe_should_use_custom_http_client_factory - InterceptingDelegatingHandler.RequestCount.Should().ContainKey("sync_sub"); - InterceptingDelegatingHandler.RequestCount["sync_sub"].Should().BeGreaterThan(0); + InterceptingDelegatingHandler.RequestCount.Should().ContainKey("sqs_sync_sub"); + InterceptingDelegatingHandler.RequestCount["sqs_sync_sub"].Should().BeGreaterThan(0); - InterceptingDelegatingHandler.RequestCount.Should().ContainKey("sync_pub"); - InterceptingDelegatingHandler.RequestCount["sync_pub"].Should().BeGreaterThan(0); + InterceptingDelegatingHandler.RequestCount.Should().ContainKey("sqs_sync_pub"); + InterceptingDelegatingHandler.RequestCount["sqs_sync_pub"].Should().BeGreaterThan(0); } public void Dispose() diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_assume.cs index d712fb48d9..ff19e223ca 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_assume.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_assume.cs @@ -16,7 +16,7 @@ public class AWSAssumeInfrastructureTests : IDisposable, IAsyncDisposable { private readonly Message _message; private readonly SqsMessageConsumer _consumer; - private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; @@ -25,17 +25,19 @@ public AWSAssumeInfrastructureTests() _myCommand = new MyCommand { Value = "Test" }; const string replyTo = "http:\\queueUrl"; const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); var subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Create + makeChannels: OnMissingChannel.Create, + routingKeyType: RoutingKeyType.PointToPoint ); _message = new Message( @@ -54,15 +56,16 @@ public AWSAssumeInfrastructureTests() //Now change the subscription to validate, just check what we made subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), routingKey: routingKey, + routingKeyType: RoutingKeyType.PointToPoint, messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Assume ); - _messageProducer = new SnsMessageProducer(awsConnection, - new SnsPublication { MakeChannels = OnMissingChannel.Assume }); + _messageProducer = new SqsMessageProducer(awsConnection, + new SqsPublication { MakeChannels = OnMissingChannel.Assume }); _consumer = new SqsMessageConsumer(awsConnection, channel.Name.ToValidSQSQueueName()); } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify.cs index c702a31062..0419c47900 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify.cs @@ -16,26 +16,28 @@ public class AWSValidateInfrastructureTests : IDisposable, IAsyncDisposable { private readonly Message _message; private readonly IAmAMessageConsumerSync _consumer; - private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; public AWSValidateInfrastructureTests() { _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Create + makeChannels: OnMissingChannel.Create, + routingKeyType: RoutingKeyType.PointToPoint ); _message = new Message( @@ -54,21 +56,22 @@ public AWSValidateInfrastructureTests() //Now change the subscription to validate, just check what we made subscription = new( - name: new SubscriptionName(channelName), + name: new SubscriptionName(subscriptionName), channelName: channel.Name, routingKey: routingKey, findTopicBy: TopicFindBy.Name, messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Validate + makeChannels: OnMissingChannel.Validate, + routingKeyType: RoutingKeyType.PointToPoint ); - _messageProducer = new SnsMessageProducer( + _messageProducer = new SqsMessageProducer( awsConnection, - new SnsPublication + new SqsPublication { - FindTopicBy = TopicFindBy.Name, + FindQueueBy = QueueFindBy.Name, MakeChannels = OnMissingChannel.Validate, - Topic = new RoutingKey(topicName) + Topic = new RoutingKey(queueName) } ); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify_by_convention.cs deleted file mode 100644 index 71a7be113d..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify_by_convention.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; - -[Trait("Category", "AWS")] -[Trait("Fragile", "CI")] -public class AWSValidateInfrastructureByConventionTests : IDisposable, IAsyncDisposable -{ - private readonly Message _message; - private readonly IAmAMessageConsumerSync _consumer; - private readonly SnsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly MyCommand _myCommand; - - public AWSValidateInfrastructureByConventionTests() - { - _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Create - ); - - _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) - ); - - var awsConnection = GatewayFactory.CreateFactory(); - - //We need to do this manually in a test - will create the channel from subscriber parameters - //This doesn't look that different from our create tests - this is because we create using the channel factory in - //our AWS transport, not the consumer (as it's a more likely to use infrastructure declared elsewhere) - _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateSyncChannel(subscription); - - //Now change the subscription to validate, just check what we made - will make the SNS Arn to prevent ListTopics call - subscription = new( - name: new SubscriptionName(channelName), - channelName: channel.Name, - routingKey: routingKey, - findTopicBy: TopicFindBy.Convention, - messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Validate - ); - - _messageProducer = new SnsMessageProducer( - awsConnection, - new SnsPublication { FindTopicBy = TopicFindBy.Convention, MakeChannels = OnMissingChannel.Validate } - ); - - _consumer = new SqsMessageConsumerFactory(awsConnection).Create(subscription); - } - - [Fact] - public async Task When_infrastructure_exists_can_verify() - { - //arrange - _messageProducer.Send(_message); - - await Task.Delay(1000); - - var messages = _consumer.Receive(TimeSpan.FromMilliseconds(5000)); - - //Assert - var message = messages.First(); - message.Id.Should().Be(_myCommand.Id); - - //clear the queue - _consumer.Acknowledge(message); - } - - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - _consumer.Dispose(); - _messageProducer.Dispose(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - await ((IAmAMessageConsumerAsync)_consumer).DisposeAsync(); - await _messageProducer.DisposeAsync(); - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infrastructure_exists_can_verify_by_convention.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infrastructure_exists_can_verify_by_convention.cs deleted file mode 100644 index faa4e7434d..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infrastructure_exists_can_verify_by_convention.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; - -[Trait("Category", "AWS")] -[Trait("Fragile", "CI")] -public class AWSValidateInfrastructureByConventionTestsAsync : IAsyncDisposable, IDisposable -{ - private readonly Message _message; - private readonly IAmAMessageConsumerAsync _consumer; - private readonly SnsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly MyCommand _myCommand; - - public AWSValidateInfrastructureByConventionTestsAsync() - { - _myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), - routingKey: routingKey, - messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Create - ); - - _message = new Message( - new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, - replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) - ); - - var awsConnection = GatewayFactory.CreateFactory(); - - _channelFactory = new ChannelFactory(awsConnection); - var channel = _channelFactory.CreateAsyncChannel(subscription); - - subscription = new( - name: new SubscriptionName(channelName), - channelName: channel.Name, - routingKey: routingKey, - findTopicBy: TopicFindBy.Convention, - messagePumpType: MessagePumpType.Proactor, - makeChannels: OnMissingChannel.Validate - ); - - _messageProducer = new SnsMessageProducer( - awsConnection, - new SnsPublication - { - FindTopicBy = TopicFindBy.Convention, - MakeChannels = OnMissingChannel.Validate - } - ); - - _consumer = new SqsMessageConsumerFactory(awsConnection).CreateAsync(subscription); - } - - [Fact] - public async Task When_infrastructure_exists_can_verify_async() - { - await _messageProducer.SendAsync(_message); - - await Task.Delay(1000); - - var messages = await _consumer.ReceiveAsync(TimeSpan.FromMilliseconds(5000)); - - var message = messages.First(); - message.Id.Should().Be(_myCommand.Id); - - await _consumer.AcknowledgeAsync(message); - } - - public void Dispose() - { - //Clean up resources that we have created - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - ((IAmAMessageConsumerSync)_consumer).Dispose(); - _messageProducer.Dispose(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - await _consumer.DisposeAsync(); - await _messageProducer.DisposeAsync(); - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_posting_a_message_via_the_messaging_gateway.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_posting_a_message_via_the_messaging_gateway.cs index 93a8331163..13e4a98391 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_posting_a_message_via_the_messaging_gateway.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_posting_a_message_via_the_messaging_gateway.cs @@ -14,13 +14,13 @@ public class SqsMessageProducerSendTests : IDisposable, IAsyncDisposable { private readonly Message _message; private readonly IAmAChannelSync _channel; - private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; private readonly string _correlationId; private readonly string _replyTo; private readonly string _contentType; - private readonly string _topicName; + private readonly string _queueName; public SqsMessageProducerSendTests() { @@ -28,16 +28,17 @@ public SqsMessageProducerSendTests() _correlationId = Guid.NewGuid().ToString(); _replyTo = "http:\\queueUrl"; _contentType = "text\\plain"; - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(_topicName); + _queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(_queueName); - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + var subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(_queueName), routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, - rawMessageDelivery: false + routingKeyType: RoutingKeyType.PointToPoint ); _message = new Message( @@ -51,7 +52,7 @@ public SqsMessageProducerSendTests() _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateSyncChannel(subscription); - _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication{Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create}); + _messageProducer = new SqsMessageProducer(awsConnection, new SqsPublication{Topic = new RoutingKey(_queueName), MakeChannels = OnMissingChannel.Create}); } [Fact] @@ -74,7 +75,7 @@ public async Task When_posting_a_message_via_the_producer() message.Id.Should().Be(_myCommand.Id); message.Redelivered.Should().BeFalse(); message.Header.MessageId.Should().Be(_myCommand.Id); - message.Header.Topic.Value.Should().Contain(_topicName); + message.Header.Topic.Value.Should().Contain(_queueName); message.Header.CorrelationId.Should().Be(_correlationId); message.Header.ReplyTo.Should().Be(_replyTo); message.Header.ContentType.Should().Be(_contentType); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_assume_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_assume_throws.cs index 00ea6e34e9..cffd913120 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_assume_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_assume_throws.cs @@ -16,30 +16,21 @@ public class AWSAssumeQueuesTests : IDisposable, IAsyncDisposable public AWSAssumeQueuesTests() { - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); var subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Assume + makeChannels: OnMissingChannel.Assume, + routingKeyType: RoutingKeyType.PointToPoint ); var awsConnection = GatewayFactory.CreateFactory(); - //create the topic, we want the queue to be the issue - //We need to create the topic at least, to check the queues - var producer = new SnsMessageProducer(awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create - }); - - producer.ConfirmTopicExistsAsync(topicName).Wait(); - _channelFactory = new ChannelFactory(awsConnection); var channel = _channelFactory.CreateSyncChannel(subscription); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_verify_throws.cs index fa42559bed..77fccb05fe 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_verify_throws.cs @@ -17,28 +17,20 @@ public class AWSValidateQueuesTests : IDisposable, IAsyncDisposable public AWSValidateQueuesTests() { - var channelName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + var subscriptionName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Send-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); _subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, - makeChannels: OnMissingChannel.Validate + makeChannels: OnMissingChannel.Validate, + routingKeyType: RoutingKeyType.PointToPoint ); _awsConnection = GatewayFactory.CreateFactory(); - - //We need to create the topic at least, to check the queues - var producer = new SnsMessageProducer(_awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create - }); - producer.ConfirmTopicExistsAsync(topicName).Wait(); - } [Fact] diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_raw_message_delivery_disabled.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_raw_message_delivery_disabled.cs deleted file mode 100644 index 903ef014a8..0000000000 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_raw_message_delivery_disabled.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using FluentAssertions; -using Paramore.Brighter.AWS.Tests.Helpers; -using Paramore.Brighter.AWS.Tests.TestDoubles; -using Paramore.Brighter.MessagingGateway.AWSSQS; -using Xunit; - -namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; - -[Trait("Category", "AWS")] -[Trait("Fragile", "CI")] -public class SqsRawMessageDeliveryTests : IDisposable, IAsyncDisposable -{ - private readonly SnsMessageProducer _messageProducer; - private readonly ChannelFactory _channelFactory; - private readonly IAmAChannelSync _channel; - private readonly RoutingKey _routingKey; - - public SqsRawMessageDeliveryTests() - { - var awsConnection = GatewayFactory.CreateFactory(); - - _channelFactory = new ChannelFactory(awsConnection); - var channelName = $"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _routingKey = new RoutingKey($"Raw-Msg-Delivery-Tests-{Guid.NewGuid().ToString()}".Truncate(45)); - - var bufferSize = 10; - - //Set rawMessageDelivery to false - _channel = _channelFactory.CreateSyncChannel(new SqsSubscription( - name: new SubscriptionName(channelName), - channelName:new ChannelName(channelName), - routingKey:_routingKey, - bufferSize: bufferSize, - makeChannels: OnMissingChannel.Create, - messagePumpType: MessagePumpType.Reactor, - rawMessageDelivery: false)); - - _messageProducer = new SnsMessageProducer(awsConnection, - new SnsPublication - { - MakeChannels = OnMissingChannel.Create - }); - } - - [Fact] - public void When_raw_message_delivery_disabled() - { - //arrange - var messageHeader = new MessageHeader( - Guid.NewGuid().ToString(), - _routingKey, - MessageType.MT_COMMAND, - correlationId: Guid.NewGuid().ToString(), - replyTo: RoutingKey.Empty, - contentType: "text\\plain"); - - var customHeaderItem = new KeyValuePair("custom-header-item", "custom-header-item-value"); - messageHeader.Bag.Add(customHeaderItem.Key, customHeaderItem.Value); - - var messageToSent = new Message(messageHeader, new MessageBody("test content one")); - - //act - _messageProducer.Send(messageToSent); - - var messageReceived = _channel.Receive(TimeSpan.FromMilliseconds(10000)); - - _channel.Acknowledge(messageReceived); - - //assert - messageReceived.Id.Should().Be(messageToSent.Id); - messageReceived.Header.Topic.Should().Be(messageToSent.Header.Topic); - messageReceived.Header.MessageType.Should().Be(messageToSent.Header.MessageType); - messageReceived.Header.CorrelationId.Should().Be(messageToSent.Header.CorrelationId); - messageReceived.Header.ReplyTo.Should().Be(messageToSent.Header.ReplyTo); - messageReceived.Header.ContentType.Should().Be(messageToSent.Header.ContentType); - messageReceived.Header.Bag.Should().ContainKey(customHeaderItem.Key).And.ContainValue(customHeaderItem.Value); - messageReceived.Body.Value.Should().Be(messageToSent.Body.Value); - } - - public void Dispose() - { - _channelFactory.DeleteTopicAsync().Wait(); - _channelFactory.DeleteQueueAsync().Wait(); - } - - public async ValueTask DisposeAsync() - { - await _channelFactory.DeleteTopicAsync(); - await _channelFactory.DeleteQueueAsync(); - } -} diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs index 890dd22723..8ad6d17847 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs @@ -15,41 +15,43 @@ public class SqsMessageConsumerRequeueTests : IDisposable { private readonly Message _message; private readonly IAmAChannelSync _channel; - private readonly SnsMessageProducer _messageProducer; + private readonly SqsMessageProducer _messageProducer; private readonly ChannelFactory _channelFactory; private readonly MyCommand _myCommand; public SqsMessageConsumerRequeueTests() { - _myCommand = new MyCommand{Value = "Test"}; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - - SqsSubscription subscription = new( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + _myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Consumer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), messagePumpType: MessagePumpType.Reactor, - routingKey: routingKey + routingKey: routingKey, + routingKeyType: RoutingKeyType.PointToPoint ); - + _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) _myCommand, JsonSerialisationOptions.Options)) + new MessageBody(JsonSerializer.Serialize((object)_myCommand, JsonSerialisationOptions.Options)) ); - + //Must have credentials stored in the SDK Credentials store or shared credentials file var awsConnection = GatewayFactory.CreateFactory(); - + //We need to do this manually in a test - will create the channel from subscriber parameters _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateSyncChannel(subscription); - - _messageProducer = new SnsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); + + _messageProducer = + new SqsMessageProducer(awsConnection, new SqsPublication { MakeChannels = OnMissingChannel.Create }); } [Fact] @@ -58,7 +60,7 @@ public void When_rejecting_a_message_through_gateway_with_requeue() _messageProducer.Send(_message); var message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - + _channel.Reject(message); //Let the timeout change @@ -66,7 +68,7 @@ public void When_rejecting_a_message_through_gateway_with_requeue() //should requeue_the_message message = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - + //clear the queue _channel.Acknowledge(message); @@ -75,13 +77,13 @@ public void When_rejecting_a_message_through_gateway_with_requeue() public void Dispose() { - _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteTopicAsync().Wait(); _channelFactory.DeleteQueueAsync().Wait(); } - + public async ValueTask DisposeAsync() { - await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteTopicAsync(); await _channelFactory.DeleteQueueAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_requeueing_a_message.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_requeueing_a_message.cs index 62748204d0..20fd2f15d6 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_requeueing_a_message.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_requeueing_a_message.cs @@ -22,33 +22,33 @@ public class SqsMessageProducerRequeueTests : IDisposable, IAsyncDisposable public SqsMessageProducerRequeueTests() { - MyCommand myCommand = new MyCommand{Value = "Test"}; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); - + MyCommand myCommand = new MyCommand { Value = "Test" }; + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-Requeue-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); + var subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), routingKey: routingKey ); - + _message = new Message( - new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, + new MessageHeader(myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, replyTo: new RoutingKey(replyTo), contentType: contentType), - new MessageBody(JsonSerializer.Serialize((object) myCommand, JsonSerialisationOptions.Options)) + new MessageBody(JsonSerializer.Serialize((object)myCommand, JsonSerialisationOptions.Options)) ); - + //Must have credentials stored in the SDK Credentials store or shared credentials file new CredentialProfileStoreChain(); - + var awsConnection = GatewayFactory.CreateFactory(); - - _sender = new SnsMessageProducer(awsConnection, new SnsPublication{MakeChannels = OnMissingChannel.Create}); - + + _sender = new SqsMessageProducer(awsConnection, new SqsPublication { MakeChannels = OnMissingChannel.Create }); + //We need to do this manually in a test - will create the channel from subscriber parameters _channelFactory = new ChannelFactory(awsConnection); _channel = _channelFactory.CreateSyncChannel(subscription); @@ -58,26 +58,26 @@ public SqsMessageProducerRequeueTests() public void When_requeueing_a_message() { _sender.Send(_message); - _receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); + _receivedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); _channel.Requeue(_receivedMessage); _requeuedMessage = _channel.Receive(TimeSpan.FromMilliseconds(5000)); - + //clear the queue - _channel.Acknowledge(_requeuedMessage ); + _channel.Acknowledge(_requeuedMessage); _requeuedMessage.Body.Value.Should().Be(_receivedMessage.Body.Value); } public void Dispose() { - _channelFactory.DeleteTopicAsync().Wait(); + _channelFactory.DeleteTopicAsync().Wait(); _channelFactory.DeleteQueueAsync().Wait(); } - + public async ValueTask DisposeAsync() { - await _channelFactory.DeleteTopicAsync(); + await _channelFactory.DeleteTopicAsync(); await _channelFactory.DeleteQueueAsync(); } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_requeueing_redrives_to_the_dlq.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_requeueing_redrives_to_the_dlq.cs index 239824a23e..484d77b894 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_requeueing_redrives_to_the_dlq.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_requeueing_redrives_to_the_dlq.cs @@ -17,7 +17,7 @@ namespace Paramore.Brighter.AWS.Tests.MessagingGateway.Sqs.Standard.Reactor; [Trait("Fragile", "CI")] public class SqsMessageProducerDlqTests : IDisposable, IAsyncDisposable { - private readonly SnsMessageProducer _sender; + private readonly SqsMessageProducer _sender; private readonly IAmAChannelSync _channel; private readonly ChannelFactory _channelFactory; private readonly Message _message; @@ -27,17 +27,18 @@ public class SqsMessageProducerDlqTests : IDisposable, IAsyncDisposable public SqsMessageProducerDlqTests() { MyCommand myCommand = new MyCommand { Value = "Test" }; - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - _dlqChannelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; - SqsSubscription subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + _dlqChannelName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Producer-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); + + var subscription = new SqsSubscription( + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), routingKey: routingKey, redrivePolicy: new RedrivePolicy(_dlqChannelName, 2) ); @@ -51,9 +52,7 @@ public SqsMessageProducerDlqTests() //Must have credentials stored in the SDK Credentials store or shared credentials file _awsConnection = GatewayFactory.CreateFactory(); - _sender = new SnsMessageProducer(_awsConnection, new SnsPublication { MakeChannels = OnMissingChannel.Create }); - - _sender.ConfirmTopicExistsAsync(topicName).Wait(); + _sender = new SqsMessageProducer(_awsConnection, new SqsPublication { MakeChannels = OnMissingChannel.Create }); //We need to do this manually in a test - will create the channel from subscriber parameters _channelFactory = new ChannelFactory(_awsConnection); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_throwing_defer_action_respect_redrive.cs index a20f4b1151..dfb60c7ac6 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_throwing_defer_action_respect_redrive.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_throwing_defer_action_respect_redrive.cs @@ -23,26 +23,28 @@ public class SnsReDrivePolicySDlqTests : IDisposable, IAsyncDisposable private readonly Message _message; private readonly string _dlqChannelName; private readonly IAmAChannelSync _channel; - private readonly SnsMessageProducer _sender; + private readonly SqsMessageProducer _sender; private readonly AWSMessagingGatewayConnection _awsConnection; private readonly SqsSubscription _subscription; private readonly ChannelFactory _channelFactory; public SnsReDrivePolicySDlqTests() { - string correlationId = Guid.NewGuid().ToString(); - string replyTo = "http:\\queueUrl"; - string contentType = "text\\plain"; - var channelName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + const string replyTo = "http:\\queueUrl"; + const string contentType = "text\\plain"; + _dlqChannelName = $"Redrive-DLQ-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - string topicName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); - var routingKey = new RoutingKey(topicName); + var correlationId = Guid.NewGuid().ToString(); + var subscriptionName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var queueName = $"Redrive-Tests-{Guid.NewGuid().ToString()}".Truncate(45); + var routingKey = new RoutingKey(queueName); //how are we consuming _subscription = new SqsSubscription( - name: new SubscriptionName(channelName), - channelName: new ChannelName(channelName), + name: new SubscriptionName(subscriptionName), + channelName: new ChannelName(queueName), routingKey: routingKey, + routingKeyType: RoutingKeyType.PointToPoint, //don't block the redrive policy from owning retry management requeueCount: -1, //delay before requeuing @@ -67,9 +69,9 @@ public SnsReDrivePolicySDlqTests() _awsConnection = GatewayFactory.CreateFactory(); //how do we send to the queue - _sender = new SnsMessageProducer( + _sender = new SqsMessageProducer( _awsConnection, - new SnsPublication + new SqsPublication { Topic = routingKey, RequestType = typeof(MyDeferredCommand), MakeChannels = OnMissingChannel.Create } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_topic_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_topic_missing_verify_throws.cs index c98b654bc4..8c41e6566b 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_topic_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_topic_missing_verify_throws.cs @@ -1,4 +1,5 @@ using System; +using Amazon.SQS.Model; using Paramore.Brighter.AWS.Tests.Helpers; using Paramore.Brighter.MessagingGateway.AWSSQS; using Xunit; @@ -25,14 +26,14 @@ public AWSValidateMissingTopicTests() public void When_topic_missing_verify_throws() { //arrange - var producer = new SnsMessageProducer(_awsConnection, - new SnsPublication + var producer = new SqsMessageProducer(_awsConnection, + new SqsPublication { MakeChannels = OnMissingChannel.Validate, }); //act && assert - Assert.Throws(() => producer.Send(new Message( + Assert.Throws(() => producer.Send(new Message( new MessageHeader("", _routingKey, MessageType.MT_EVENT, type: "plain/text"), new MessageBody("Test")))); } From 5abb8d6585ed197099c78b6f4336d3e6b98ca5d2 Mon Sep 17 00:00:00 2001 From: Rafael Lillo Date: Sat, 4 Jan 2025 17:04:43 +0000 Subject: [PATCH 15/18] GH-1294 Add Fifo sample --- .../CommandHandlers/FarewellEventHandler.cs | 24 ++++++++ .../Greetings/Ports/Commands/FarewellEvent.cs | 19 +++++++ .../Mappers/FarewellEventMessageMapper.cs | 51 +++++++++++++++++ .../AWSTaskQueue/GreetingsPumper/Program.cs | 57 ++++++++++++------- .../GreetingsReceiverConsole/Program.cs | 35 ++++++++---- .../AWSTaskQueue/GreetingsSender/Program.cs | 19 +++++-- 6 files changed, 168 insertions(+), 37 deletions(-) create mode 100644 samples/TaskQueue/AWSTaskQueue/Greetings/Ports/CommandHandlers/FarewellEventHandler.cs create mode 100644 samples/TaskQueue/AWSTaskQueue/Greetings/Ports/Commands/FarewellEvent.cs create mode 100644 samples/TaskQueue/AWSTaskQueue/Greetings/Ports/Mappers/FarewellEventMessageMapper.cs diff --git a/samples/TaskQueue/AWSTaskQueue/Greetings/Ports/CommandHandlers/FarewellEventHandler.cs b/samples/TaskQueue/AWSTaskQueue/Greetings/Ports/CommandHandlers/FarewellEventHandler.cs new file mode 100644 index 0000000000..ec8e17b48c --- /dev/null +++ b/samples/TaskQueue/AWSTaskQueue/Greetings/Ports/CommandHandlers/FarewellEventHandler.cs @@ -0,0 +1,24 @@ +using System; +using Greetings.Ports.Commands; +using Paramore.Brighter; + +namespace Greetings.Ports.CommandHandlers +{ + public class FarewellEventHandler : RequestHandler + { + public override FarewellEvent Handle(FarewellEvent @event) + { + Console.BackgroundColor = ConsoleColor.Blue; + Console.ForegroundColor = ConsoleColor.White; + + Console.WriteLine("Received Farewell. Message Follows"); + Console.WriteLine("----------------------------------"); + Console.WriteLine(@event.Farewell); + Console.WriteLine("----------------------------------"); + Console.WriteLine("Message Ends"); + + Console.ResetColor(); + return base.Handle(@event); + } + } +} diff --git a/samples/TaskQueue/AWSTaskQueue/Greetings/Ports/Commands/FarewellEvent.cs b/samples/TaskQueue/AWSTaskQueue/Greetings/Ports/Commands/FarewellEvent.cs new file mode 100644 index 0000000000..8f107f3f87 --- /dev/null +++ b/samples/TaskQueue/AWSTaskQueue/Greetings/Ports/Commands/FarewellEvent.cs @@ -0,0 +1,19 @@ +using System; +using Paramore.Brighter; + +namespace Greetings.Ports.Commands +{ + public class FarewellEvent : Event + { + public FarewellEvent() : base(Guid.NewGuid()) + { + } + + public FarewellEvent(string farewell) : base(Guid.NewGuid()) + { + Farewell = farewell; + } + + public string Farewell { get; set; } + } +} diff --git a/samples/TaskQueue/AWSTaskQueue/Greetings/Ports/Mappers/FarewellEventMessageMapper.cs b/samples/TaskQueue/AWSTaskQueue/Greetings/Ports/Mappers/FarewellEventMessageMapper.cs new file mode 100644 index 0000000000..f59152f38d --- /dev/null +++ b/samples/TaskQueue/AWSTaskQueue/Greetings/Ports/Mappers/FarewellEventMessageMapper.cs @@ -0,0 +1,51 @@ +#region Licence +/* The MIT License (MIT) +Copyright © 2014 Ian Cooper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion + +using System.Text.Json; +using Greetings.Ports.Commands; +using Paramore.Brighter; +using Paramore.Brighter.MessagingGateway.AWSSQS; + +namespace Greetings.Ports.Mappers +{ + public class FarewellEventMessageMapper : IAmAMessageMapper + { + public IRequestContext Context { get; set; } + + public Message MapToMessage(FarewellEvent request, Publication publication) + { + var header = new MessageHeader(messageId: request.Id, topic: publication.Topic, messageType: MessageType.MT_EVENT); + var body = new MessageBody(JsonSerializer.Serialize(request, JsonSerialisationOptions.Options)); + var message = new Message(header, body); + return message; + } + + public FarewellEvent MapToRequest(Message message) + { + var greetingCommand = JsonSerializer.Deserialize(message.Body.Value, JsonSerialisationOptions.Options); + + return greetingCommand; + } + } +} diff --git a/samples/TaskQueue/AWSTaskQueue/GreetingsPumper/Program.cs b/samples/TaskQueue/AWSTaskQueue/GreetingsPumper/Program.cs index 0478b71099..e52d0b8f77 100644 --- a/samples/TaskQueue/AWSTaskQueue/GreetingsPumper/Program.cs +++ b/samples/TaskQueue/AWSTaskQueue/GreetingsPumper/Program.cs @@ -27,32 +27,47 @@ private static async Task Main(string[] args) var host = new HostBuilder() .ConfigureServices((hostContext, services) => - { - if (new CredentialProfileStoreChain().TryGetAWSCredentials("default", out var credentials)) { - var awsConnection = new AWSMessagingGatewayConnection(credentials, RegionEndpoint.EUWest1); + if (new CredentialProfileStoreChain().TryGetAWSCredentials("default", out var credentials)) + { + var awsConnection = new AWSMessagingGatewayConnection(credentials, RegionEndpoint.USEast1, + cfg => + { + var serviceURL = Environment.GetEnvironmentVariable("LOCALSTACK_SERVICE_URL"); + if (!string.IsNullOrWhiteSpace(serviceURL)) + { + cfg.ServiceURL = serviceURL; + } + }); - var producerRegistry = new SnsProducerRegistryFactory( - awsConnection, - new SnsPublication[] - { - new SnsPublication + var producerRegistry = new SnsProducerRegistryFactory( + awsConnection, + new SnsPublication[] { - Topic = new RoutingKey(typeof(GreetingEvent).FullName.ToValidSNSTopicName()) + new SnsPublication + { + Topic = new RoutingKey(typeof(GreetingEvent).FullName + .ToValidSNSTopicName()) + }, + new SnsPublication + { + Topic = new RoutingKey( + typeof(FarewellEvent).FullName.ToValidSNSTopicName(true)), + SnsAttributes = new SnsAttributes { Type = SnsSqsType.Fifo } + } } - } - ).Create(); - - services.AddBrighter() - .UseExternalBus((configure) => - { - configure.ProducerRegistry = producerRegistry; - }) - .AutoFromAssemblies(typeof(GreetingEvent).Assembly); - } + ).Create(); - services.AddHostedService(); - } + services.AddBrighter() + .UseExternalBus((configure) => + { + configure.ProducerRegistry = producerRegistry; + }) + .AutoFromAssemblies(typeof(GreetingEvent).Assembly); + } + + services.AddHostedService(); + } ) .UseConsoleLifetime() .UseSerilog() diff --git a/samples/TaskQueue/AWSTaskQueue/GreetingsReceiverConsole/Program.cs b/samples/TaskQueue/AWSTaskQueue/GreetingsReceiverConsole/Program.cs index a55495f0c6..930f622958 100644 --- a/samples/TaskQueue/AWSTaskQueue/GreetingsReceiverConsole/Program.cs +++ b/samples/TaskQueue/AWSTaskQueue/GreetingsReceiverConsole/Program.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2014 Ian Cooper @@ -58,21 +59,36 @@ public static async Task Main(string[] args) new ChannelName(typeof(GreetingEvent).FullName.ToValidSNSTopicName()), new RoutingKey(typeof(GreetingEvent).FullName.ToValidSNSTopicName()), bufferSize: 10, - timeOut: TimeSpan.FromMilliseconds(20), + timeOut: TimeSpan.FromMilliseconds(20), + lockTimeout: 30), + new SqsSubscription(new SubscriptionName("paramore.example.farewell"), + new ChannelName(typeof(FarewellEvent).FullName.ToValidSNSTopicName(true)), + new RoutingKey(typeof(FarewellEvent).FullName.ToValidSNSTopicName(true)), + bufferSize: 10, + timeOut: TimeSpan.FromMilliseconds(20), lockTimeout: 30) }; //create the gateway if (new CredentialProfileStoreChain().TryGetAWSCredentials("default", out var credentials)) { - var awsConnection = new AWSMessagingGatewayConnection(credentials, RegionEndpoint.EUWest1); + var serviceURL = Environment.GetEnvironmentVariable("LOCALSTACK_SERVICE_URL"); + var region = !string.IsNullOrWhiteSpace(serviceURL) ? RegionEndpoint.USEast1 : RegionEndpoint.USEast2; + var awsConnection = new AWSMessagingGatewayConnection(credentials, region, + cfg => + { + if (!string.IsNullOrWhiteSpace(serviceURL)) + { + cfg.ServiceURL = serviceURL; + } + }); services.AddServiceActivator(options => - { - options.Subscriptions = subscriptions; - options.DefaultChannelFactory = new ChannelFactory(awsConnection); - }) - .AutoFromAssemblies(); + { + options.Subscriptions = subscriptions; + options.DefaultChannelFactory = new ChannelFactory(awsConnection); + }) + .AutoFromAssemblies(); } services.AddHostedService(); @@ -82,11 +98,6 @@ public static async Task Main(string[] args) .Build(); await host.RunAsync(); - - - - } } } - diff --git a/samples/TaskQueue/AWSTaskQueue/GreetingsSender/Program.cs b/samples/TaskQueue/AWSTaskQueue/GreetingsSender/Program.cs index ff96fb3590..a98b1e415d 100644 --- a/samples/TaskQueue/AWSTaskQueue/GreetingsSender/Program.cs +++ b/samples/TaskQueue/AWSTaskQueue/GreetingsSender/Program.cs @@ -1,4 +1,5 @@ #region Licence + /* The MIT License (MIT) Copyright © 2017 Ian Cooper @@ -22,6 +23,7 @@ THE SOFTWARE. */ #endregion +using System; using System.Transactions; using Amazon; using Amazon.Runtime.CredentialManagement; @@ -48,10 +50,18 @@ static void Main(string[] args) var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(new SerilogLoggerFactory()); - + if (new CredentialProfileStoreChain().TryGetAWSCredentials("default", out var credentials)) { - var awsConnection = new AWSMessagingGatewayConnection(credentials, RegionEndpoint.EUWest1); + var serviceURL = Environment.GetEnvironmentVariable("LOCALSTACK_SERVICE_URL"); + var region = !string.IsNullOrWhiteSpace(serviceURL) ? RegionEndpoint.USEast1 : RegionEndpoint.USEast2; + var awsConnection = new AWSMessagingGatewayConnection(credentials, region, cfg => + { + if (!string.IsNullOrWhiteSpace(serviceURL)) + { + cfg.ServiceURL = serviceURL; + } + }); var producerRegistry = new SnsProducerRegistryFactory( awsConnection, @@ -64,7 +74,7 @@ static void Main(string[] args) } } ).Create(); - + serviceCollection.AddBrighter() .UseExternalBus((configure) => { @@ -76,7 +86,8 @@ static void Main(string[] args) var commandProcessor = serviceProvider.GetService(); - commandProcessor.Post(new GreetingEvent("Ian")); + commandProcessor.Post(new GreetingEvent("Ian says: Hi there!")); + commandProcessor.Post(new FarewellEvent("Ian says: See you later!")); } } } From 3f02f030a5a0442d87ff155229f0dc132a4a51c5 Mon Sep 17 00:00:00 2001 From: Rafael Lillo Date: Sun, 5 Jan 2025 21:06:22 +0000 Subject: [PATCH 16/18] Improvements --- .../GreetingsReceiverConsole/Program.cs | 2 +- .../AWSTaskQueue/GreetingsSender/Program.cs | 2 +- .../SqsInlineMessageCreator.cs | 15 +++++++-------- .../SqsMessageCreator.cs | 16 +++++++--------- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/samples/TaskQueue/AWSTaskQueue/GreetingsReceiverConsole/Program.cs b/samples/TaskQueue/AWSTaskQueue/GreetingsReceiverConsole/Program.cs index 930f622958..b694dc12e6 100644 --- a/samples/TaskQueue/AWSTaskQueue/GreetingsReceiverConsole/Program.cs +++ b/samples/TaskQueue/AWSTaskQueue/GreetingsReceiverConsole/Program.cs @@ -73,7 +73,7 @@ public static async Task Main(string[] args) if (new CredentialProfileStoreChain().TryGetAWSCredentials("default", out var credentials)) { var serviceURL = Environment.GetEnvironmentVariable("LOCALSTACK_SERVICE_URL"); - var region = !string.IsNullOrWhiteSpace(serviceURL) ? RegionEndpoint.USEast1 : RegionEndpoint.USEast2; + var region = string.IsNullOrWhiteSpace(serviceURL) ? RegionEndpoint.EUWest1 : RegionEndpoint.USEast1; var awsConnection = new AWSMessagingGatewayConnection(credentials, region, cfg => { diff --git a/samples/TaskQueue/AWSTaskQueue/GreetingsSender/Program.cs b/samples/TaskQueue/AWSTaskQueue/GreetingsSender/Program.cs index a98b1e415d..632152f373 100644 --- a/samples/TaskQueue/AWSTaskQueue/GreetingsSender/Program.cs +++ b/samples/TaskQueue/AWSTaskQueue/GreetingsSender/Program.cs @@ -54,7 +54,7 @@ static void Main(string[] args) if (new CredentialProfileStoreChain().TryGetAWSCredentials("default", out var credentials)) { var serviceURL = Environment.GetEnvironmentVariable("LOCALSTACK_SERVICE_URL"); - var region = !string.IsNullOrWhiteSpace(serviceURL) ? RegionEndpoint.USEast1 : RegionEndpoint.USEast2; + var region = string.IsNullOrWhiteSpace(serviceURL) ? RegionEndpoint.EUWest1 : RegionEndpoint.USEast1; var awsConnection = new AWSMessagingGatewayConnection(credentials, region, cfg => { if (!string.IsNullOrWhiteSpace(serviceURL)) diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs index 1d09aca44b..d3f7eb7e6c 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsInlineMessageCreator.cs @@ -241,19 +241,18 @@ private HeaderResult ReadTopic() if (_messageAttributes.TryGetValue(HeaderNames.Topic, out var topicArn)) { var topic = topicArn.GetValueInString() ?? string.Empty; + if (Arn.TryParse(topic, out var arn)) { - topic = arn.Resource; + return new HeaderResult(new RoutingKey(arn.Resource), true); } - else + + var indexOf = topic.LastIndexOf('/'); + if (indexOf != -1) { - var indexOf = topic.LastIndexOf('/'); - if (indexOf != -1) - { - topic = topic.Substring(indexOf + 1); - } + return new HeaderResult(new RoutingKey(topic.Substring(indexOf + 1)), true); } - + return new HeaderResult(new RoutingKey(topic), true); } diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs index f7890cb529..fea74f1a53 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageCreator.cs @@ -237,18 +237,16 @@ private static HeaderResult ReadTopic(Amazon.SQS.Model.Message sqsMe if (sqsMessage.MessageAttributes.TryGetValue(HeaderNames.Topic, out MessageAttributeValue? value)) { //we have an arn, and we want the topic - var topic = value.StringValue; - if (Arn.TryParse(value.StringValue, out var arn)) + var topic = value.StringValue ?? string.Empty; + if (Arn.TryParse(topic, out var arn)) { - topic = arn.Resource; + return new HeaderResult(new RoutingKey(arn.Resource), true); } - else + + var indexOf = topic.LastIndexOf('/'); + if (indexOf != -1) { - var indexOf = value.StringValue.LastIndexOf('/'); - if (indexOf != -1) - { - topic = value.StringValue.Substring(indexOf + 1); - } + return new HeaderResult(new RoutingKey(topic.Substring(indexOf + 1)), true); } return new HeaderResult(new RoutingKey(topic), true); From 7a868d7c1ad0ccff0c7d087f2d4808b9ecd9c644 Mon Sep 17 00:00:00 2001 From: Rafael Andrade Date: Mon, 6 Jan 2025 09:25:47 +0000 Subject: [PATCH 17/18] Rename ChannelType --- .../AWSMessagingGateway.cs | 2 +- .../ChannelFactory.cs | 8 +++----- .../{RoutingKeyType.cs => ChannelType.cs} | 4 ++-- .../SqsAttributes.cs | 4 ++-- .../SqsMessageProducer.cs | 2 +- .../SqsSubscription.cs | 18 +++++++++--------- ...e_consumer_reads_multiple_messages_async.cs | 2 +- ...en_infastructure_exists_can_assume_async.cs | 4 ++-- ...n_infrastructure_exists_can_verify_async.cs | 2 +- ...structure_exists_can_verify_by_url_async.cs | 4 ++-- ..._message_via_the_messaging_gateway_async.cs | 2 +- .../When_queues_missing_assume_throws_async.cs | 2 +- .../When_queues_missing_verify_throws_async.cs | 2 +- ...When_raw_message_delivery_disabled_async.cs | 2 +- ...ssage_through_gateway_with_requeue_async.cs | 2 +- .../When_requeueing_a_message_async.cs | 2 +- ...hen_requeueing_redrives_to_the_dlq_async.cs | 2 +- ...owing_defer_action_respect_redrive_async.cs | 2 +- ...message_consumer_reads_multiple_messages.cs | 2 +- .../When_infastructure_exists_can_assume.cs | 4 ++-- .../When_infastructure_exists_can_verify.cs | 2 +- ...n_infastructure_exists_can_verify_by_url.cs | 4 ++-- ...ting_a_message_via_the_messaging_gateway.cs | 2 +- .../When_queues_missing_verify_throws.cs | 2 +- ...g_a_message_through_gateway_with_requeue.cs | 2 +- .../Fifo/Reactor/When_requeueing_a_message.cs | 2 +- ...en_throwing_defer_action_respect_redrive.cs | 2 +- ...e_consumer_reads_multiple_messages_async.cs | 2 +- ...When_customising_aws_client_config_async.cs | 2 +- ...en_infastructure_exists_can_assume_async.cs | 2 +- ...n_infastructure_exists_can_verify_by_url.cs | 2 +- ...n_infrastructure_exists_can_verify_async.cs | 4 ++-- ...structure_exists_can_verify_by_url_async.cs | 2 +- ..._message_via_the_messaging_gateway_async.cs | 2 +- .../When_queues_missing_assume_throws_async.cs | 2 +- .../When_queues_missing_verify_throws_async.cs | 2 +- ...ssage_through_gateway_with_requeue_async.cs | 2 +- .../When_requeueing_a_message_async.cs | 2 +- ...hen_requeueing_redrives_to_the_dlq_async.cs | 2 +- ...owing_defer_action_respect_redrive_async.cs | 2 +- ...message_consumer_reads_multiple_messages.cs | 2 +- .../When_customising_aws_client_config.cs | 2 +- .../When_infastructure_exists_can_assume.cs | 4 ++-- .../When_infastructure_exists_can_verify.cs | 4 ++-- ...ting_a_message_via_the_messaging_gateway.cs | 2 +- .../When_queues_missing_assume_throws.cs | 2 +- .../When_queues_missing_verify_throws.cs | 2 +- ...g_a_message_through_gateway_with_requeue.cs | 2 +- ...en_throwing_defer_action_respect_redrive.cs | 2 +- 49 files changed, 68 insertions(+), 70 deletions(-) rename src/Paramore.Brighter.MessagingGateway.AWSSQS/{RoutingKeyType.cs => ChannelType.cs} (85%) diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs index 0b6c6ea055..88a62924bc 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/AWSMessagingGateway.cs @@ -270,7 +270,7 @@ private async Task CreateQueueAsync( throw new InvalidOperationException($"Could not create Queue queue: {queueName} on {AwsConnection.Region}"); } - if (sqsAttributes == null || sqsAttributes.RoutingKeyType == RoutingKeyType.PubSub) + if (sqsAttributes == null || sqsAttributes.ChannelType == ChannelType.PubSub) { using var snsClient = _awsClientFactory.CreateSnsClient(); await CheckSubscriptionAsync(makeChannel, ChannelTopicArn!, queueUrl, sqsAttributes, sqsClient, snsClient); diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs index d5c7c2c9d1..94f2d2ae9a 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelFactory.cs @@ -106,7 +106,7 @@ public async Task CreateAsyncChannelAsync(Subscription subscri var isFifo = _subscription.SqsType == SnsSqsType.Fifo; var routingKey = _subscription.ChannelName.Value.ToValidSQSQueueName(isFifo); - if (_subscription.RoutingKeyType == RoutingKeyType.PubSub) + if (_subscription.ChannelType == ChannelType.PubSub) { var snsAttributes = _subscription.SnsAttributes ?? new SnsAttributes(); snsAttributes.Type = _subscription.SqsType; @@ -155,9 +155,7 @@ await QueueExistsAsync(sqsClient, { try { - sqsClient.DeleteQueueAsync(queueExists.queueUrl) - .GetAwaiter() - .GetResult(); + await sqsClient.DeleteQueueAsync(queueExists.queueUrl); } catch (Exception) { @@ -204,7 +202,7 @@ private async Task CreateSyncChannelAsync(Subscription subscrip var routingKey = _subscription.ChannelName.Value; var isFifo = _subscription.SqsType == SnsSqsType.Fifo; - if (_subscription.RoutingKeyType == RoutingKeyType.PubSub) + if (_subscription.ChannelType == ChannelType.PubSub) { var snsAttributes = _subscription.SnsAttributes ?? new SnsAttributes(); snsAttributes.Type = _subscription.SqsType; diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/RoutingKeyType.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelType.cs similarity index 85% rename from src/Paramore.Brighter.MessagingGateway.AWSSQS/RoutingKeyType.cs rename to src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelType.cs index 91effe8d9d..c36f80d6d9 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/RoutingKeyType.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/ChannelType.cs @@ -1,9 +1,9 @@ namespace Paramore.Brighter.MessagingGateway.AWSSQS; /// -/// The routing key type +/// The Channel type /// -public enum RoutingKeyType +public enum ChannelType { /// /// Use the Pub/Sub for routing key, aka SNS diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsAttributes.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsAttributes.cs index 145cea16b0..2e0a8b2310 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsAttributes.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsAttributes.cs @@ -11,7 +11,7 @@ public class SqsAttributes /// /// The routing key type. /// - public RoutingKeyType RoutingKeyType { get; set; } + public ChannelType ChannelType { get; set; } /// /// This governs how long, in seconds, a 'lock' is held on a message for one consumer @@ -82,7 +82,7 @@ public static SqsAttributes From(SqsSubscription subscription) { return new SqsAttributes { - RoutingKeyType = subscription.RoutingKeyType, + ChannelType = subscription.ChannelType, LockTimeout = subscription.LockTimeout, DelaySeconds = subscription.DelaySeconds, MessageRetentionPeriod = subscription.MessageRetentionPeriod, diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs index 47350bf90f..63bee0c50d 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageProducer.cs @@ -103,7 +103,7 @@ public async Task ConfirmQueueExistsAsync(string? queue = null, Cancellati _publication.SqsAttributes ??= new SqsAttributes(); // For SQS Publish, it should be always Point-to-Point - _publication.SqsAttributes.RoutingKeyType = RoutingKeyType.PointToPoint; + _publication.SqsAttributes.ChannelType = ChannelType.PointToPoint; RoutingKey? routingKey = null; if (queue is not null) diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs index 3524fa760b..51e2275971 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsSubscription.cs @@ -56,7 +56,7 @@ public class SqsSubscription : Subscription /// /// The routing key type. /// - public RoutingKeyType RoutingKeyType { get; } + public ChannelType ChannelType { get; } /// /// Indicates how we should treat the routing key @@ -153,8 +153,8 @@ public class SqsSubscription : Subscription /// Enables or disable content-based deduplication /// Specifies whether message deduplication occurs at the message group or queue level /// Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group - /// Specifies the routing key type - /// How the queue should be found when is point-to-point. + /// Specifies the routing key type + /// How the queue should be found when is point-to-point. public SqsSubscription( Type dataType, SubscriptionName? name = null, @@ -184,7 +184,7 @@ public SqsSubscription( bool contentBasedDeduplication = true, DeduplicationScope? deduplicationScope = null, int? fifoThroughputLimit = null, - RoutingKeyType routingKeyType = RoutingKeyType.PubSub, + ChannelType channelType = ChannelType.PubSub, QueueFindBy findQueueBy = QueueFindBy.Name ) : base(dataType, name, channelName, routingKey, bufferSize, noOfPerformers, timeOut, requeueCount, @@ -204,7 +204,7 @@ public SqsSubscription( ContentBasedDeduplication = contentBasedDeduplication; DeduplicationScope = deduplicationScope; FifoThroughputLimit = fifoThroughputLimit; - RoutingKeyType = routingKeyType; + ChannelType = channelType; FindQueueBy = findQueueBy; } } @@ -248,8 +248,8 @@ public class SqsSubscription : SqsSubscription where T : IRequest /// Enables or disable content-based deduplication /// Specifies whether message deduplication occurs at the message group or queue level /// Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group - /// Specifies the routing key type - /// How the queue should be found when is point-to-point. + /// Specifies the routing key type + /// How the queue should be found when is point-to-point. public SqsSubscription( SubscriptionName? name = null, ChannelName? channelName = null, @@ -278,7 +278,7 @@ public SqsSubscription( bool contentBasedDeduplication = true, DeduplicationScope? deduplicationScope = null, int? fifoThroughputLimit = null, - RoutingKeyType routingKeyType = RoutingKeyType.PubSub, + ChannelType channelType = ChannelType.PubSub, QueueFindBy findQueueBy = QueueFindBy.Name ) : base(typeof(T), name, channelName, routingKey, bufferSize, noOfPerformers, timeOut, requeueCount, @@ -287,7 +287,7 @@ public SqsSubscription( messageRetentionPeriod, findTopicBy, iAmPolicy, redrivePolicy, snsAttributes, tags, makeChannels, rawMessageDelivery, emptyChannelDelay, channelFailureDelay, sqsType, contentBasedDeduplication, deduplicationScope, fifoThroughputLimit, - routingKeyType, findQueueBy) + channelType, findQueueBy) { } } diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs index 6b29deafab..70ae15e034 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs @@ -41,7 +41,7 @@ public SQSBufferedConsumerTestsAsync() sqsType: SnsSqsType.Fifo, deduplicationScope: DeduplicationScope.MessageGroup, fifoThroughputLimit: 1, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint )).GetAwaiter().GetResult(); //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infastructure_exists_can_assume_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infastructure_exists_can_assume_async.cs index 0a19cd4592..ebb1d2e0a5 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infastructure_exists_can_assume_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infastructure_exists_can_assume_async.cs @@ -37,7 +37,7 @@ public AWSAssumeInfrastructureTestsAsync() messagePumpType: MessagePumpType.Proactor, makeChannels: OnMissingChannel.Create, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint); + channelType: ChannelType.PointToPoint); _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, @@ -61,7 +61,7 @@ public AWSAssumeInfrastructureTestsAsync() messagePumpType: MessagePumpType.Proactor, makeChannels: OnMissingChannel.Assume, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint); + channelType: ChannelType.PointToPoint); _messageProducer = new SqsMessageProducer(awsConnection, new SqsPublication diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_async.cs index b4789433c1..1f62312e1e 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_async.cs @@ -37,7 +37,7 @@ public AWSValidateInfrastructureTestsAsync() messagePumpType: MessagePumpType.Proactor, makeChannels: OnMissingChannel.Create, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs index 8c5eb924f2..7cbee17699 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs @@ -37,7 +37,7 @@ public AWSValidateInfrastructureByUrlTestsAsync() messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Create, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( @@ -60,7 +60,7 @@ public AWSValidateInfrastructureByUrlTestsAsync() findQueueBy: QueueFindBy.Url, makeChannels: OnMissingChannel.Validate, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _messageProducer = new SqsMessageProducer( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs index c75f25a17d..2a3f90765b 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs @@ -42,7 +42,7 @@ public SqsMessageProducerSendAsyncTests() messagePumpType: MessagePumpType.Proactor, rawMessageDelivery: true, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_queues_missing_assume_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_queues_missing_assume_throws_async.cs index 11893c79cb..ceec7ce2bb 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_queues_missing_assume_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_queues_missing_assume_throws_async.cs @@ -27,7 +27,7 @@ public AWSAssumeQueuesTestsAsync() makeChannels: OnMissingChannel.Assume, messagePumpType: MessagePumpType.Proactor, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); var awsConnection = GatewayFactory.CreateFactory(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_queues_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_queues_missing_verify_throws_async.cs index a437dbc79d..c1a5400917 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_queues_missing_verify_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_queues_missing_verify_throws_async.cs @@ -27,7 +27,7 @@ public AWSValidateQueuesTestsAsync() routingKey: routingKey, makeChannels: OnMissingChannel.Validate, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _awsConnection = GatewayFactory.CreateFactory(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_raw_message_delivery_disabled_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_raw_message_delivery_disabled_async.cs index 962c7a7eb9..fd9715be16 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_raw_message_delivery_disabled_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_raw_message_delivery_disabled_async.cs @@ -37,7 +37,7 @@ public SqsRawMessageDeliveryTestsAsync() makeChannels: OnMissingChannel.Create, rawMessageDelivery: true, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint)); + channelType: ChannelType.PointToPoint)); _messageProducer = new SqsMessageProducer(awsConnection, new SqsPublication diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs index ee6c7e8ab0..ba3b111d1c 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs @@ -36,7 +36,7 @@ public SqsMessageConsumerRequeueTestsAsync() messagePumpType: MessagePumpType.Proactor, makeChannels: OnMissingChannel.Create, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_requeueing_a_message_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_requeueing_a_message_async.cs index 99dbb921e3..bf3aacd4b9 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_requeueing_a_message_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_requeueing_a_message_async.cs @@ -37,7 +37,7 @@ public SqsMessageProducerRequeueTestsAsync() messagePumpType: MessagePumpType.Proactor, makeChannels: OnMissingChannel.Create, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_requeueing_redrives_to_the_dlq_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_requeueing_redrives_to_the_dlq_async.cs index 23483c3134..a2f008a575 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_requeueing_redrives_to_the_dlq_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_requeueing_redrives_to_the_dlq_async.cs @@ -43,7 +43,7 @@ public SqsMessageProducerDlqTestsAsync() messagePumpType: MessagePumpType.Proactor, redrivePolicy: new RedrivePolicy(_dlqChannelName, 2), sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_throwing_defer_action_respect_redrive_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_throwing_defer_action_respect_redrive_async.cs index 6a370316d4..9b2181f88b 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_throwing_defer_action_respect_redrive_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Proactor/When_throwing_defer_action_respect_redrive_async.cs @@ -47,7 +47,7 @@ public SnsReDrivePolicySDlqTestsAsync() requeueDelay: TimeSpan.FromMilliseconds(50), messagePumpType: MessagePumpType.Proactor, redrivePolicy: new RedrivePolicy(new ChannelName(_dlqChannelName), 2), - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_a_message_consumer_reads_multiple_messages.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_a_message_consumer_reads_multiple_messages.cs index febb854ad4..22769410a5 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_a_message_consumer_reads_multiple_messages.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_a_message_consumer_reads_multiple_messages.cs @@ -43,7 +43,7 @@ public SQSBufferedConsumerTests() contentBasedDeduplication: true, deduplicationScope: DeduplicationScope.MessageGroup, fifoThroughputLimit: 1, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint )); //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_assume.cs index d08c5eb964..18ef09c53c 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_assume.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_assume.cs @@ -37,7 +37,7 @@ public AWSAssumeInfrastructureTests() messagePumpType: MessagePumpType.Proactor, makeChannels: OnMissingChannel.Create, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint); + channelType: ChannelType.PointToPoint); _message = new Message( new MessageHeader(_myCommand.Id, routingKey, MessageType.MT_COMMAND, correlationId: correlationId, @@ -61,7 +61,7 @@ public AWSAssumeInfrastructureTests() messagePumpType: MessagePumpType.Proactor, makeChannels: OnMissingChannel.Assume, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint); + channelType: ChannelType.PointToPoint); _messageProducer = new SqsMessageProducer(awsConnection, new SqsPublication diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_verify.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_verify.cs index dce16c2e0d..a028e346ab 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_verify.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_verify.cs @@ -38,7 +38,7 @@ public AWSValidateInfrastructureTests() messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Create, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_verify_by_url.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_verify_by_url.cs index 92a53c64e8..6e38749717 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_verify_by_url.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_infastructure_exists_can_verify_by_url.cs @@ -39,7 +39,7 @@ public AWSValidateInfrastructureByUrlTests () messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Create, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( @@ -67,7 +67,7 @@ public AWSValidateInfrastructureByUrlTests () messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Validate, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _messageProducer = new SqsMessageProducer( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_posting_a_message_via_the_messaging_gateway.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_posting_a_message_via_the_messaging_gateway.cs index ab5d48d695..db1956fe59 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_posting_a_message_via_the_messaging_gateway.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_posting_a_message_via_the_messaging_gateway.cs @@ -42,7 +42,7 @@ public SqsMessageProducerSendAsyncTests() messagePumpType: MessagePumpType.Proactor, rawMessageDelivery: true, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_queues_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_queues_missing_verify_throws.cs index e83a9855ba..2a36c8afe0 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_queues_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_queues_missing_verify_throws.cs @@ -27,7 +27,7 @@ public AWSValidateQueuesTests() routingKey: routingKey, makeChannels: OnMissingChannel.Validate, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _awsConnection = GatewayFactory.CreateFactory(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs index 1f70bf6309..99e6c67c80 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs @@ -36,7 +36,7 @@ public SqsMessageConsumerRequeueTests() messagePumpType: MessagePumpType.Proactor, makeChannels: OnMissingChannel.Create, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_requeueing_a_message.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_requeueing_a_message.cs index d93ea1c6ae..812de8f1a0 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_requeueing_a_message.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_requeueing_a_message.cs @@ -37,7 +37,7 @@ public SqsMessageProducerRequeueTests() messagePumpType: MessagePumpType.Proactor, makeChannels: OnMissingChannel.Create, sqsType: SnsSqsType.Fifo, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_throwing_defer_action_respect_redrive.cs index 672a81e83d..b0c51c684f 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_throwing_defer_action_respect_redrive.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Fifo/Reactor/When_throwing_defer_action_respect_redrive.cs @@ -47,7 +47,7 @@ public SnsReDrivePolicySDlqTests() requeueDelay: TimeSpan.FromMilliseconds(50), messagePumpType: MessagePumpType.Proactor, redrivePolicy: new RedrivePolicy(new ChannelName(_dlqChannelName), 2), - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs index ff21bdbd65..a0ab410623 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_a_message_consumer_reads_multiple_messages_async.cs @@ -39,7 +39,7 @@ public SQSBufferedConsumerTestsAsync() routingKey: routingKey, bufferSize: BufferSize, makeChannels: OnMissingChannel.Create, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint )).GetAwaiter().GetResult(); //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_customising_aws_client_config_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_customising_aws_client_config_async.cs index 3540a063ad..2503dc24fe 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_customising_aws_client_config_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_customising_aws_client_config_async.cs @@ -32,7 +32,7 @@ public CustomisingAwsClientConfigTestsAsync() channelName: new ChannelName(queueName), messagePumpType: MessagePumpType.Proactor, routingKey: routingKey, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_assume_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_assume_async.cs index 695efbff16..acac11ba89 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_assume_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_assume_async.cs @@ -36,7 +36,7 @@ public AWSAssumeInfrastructureTestsAsync() routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, makeChannels: OnMissingChannel.Create, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_verify_by_url.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_verify_by_url.cs index 2538451f27..1773f0c639 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_verify_by_url.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infastructure_exists_can_verify_by_url.cs @@ -38,7 +38,7 @@ public AWSValidateInfrastructureByUrlTests() routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Create, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_async.cs index 83ae2757ba..4f04418877 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_async.cs @@ -36,7 +36,7 @@ public AWSValidateInfrastructureTestsAsync() routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, makeChannels: OnMissingChannel.Create, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( @@ -57,7 +57,7 @@ public AWSValidateInfrastructureTestsAsync() findTopicBy: TopicFindBy.Name, messagePumpType: MessagePumpType.Proactor, makeChannels: OnMissingChannel.Validate, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _messageProducer = new SqsMessageProducer( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs index d63838a017..6cc9c67b39 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_infrastructure_exists_can_verify_by_url_async.cs @@ -36,7 +36,7 @@ public AWSValidateInfrastructureByUrlTestsAsync() routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Create, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs index 845d14e139..af0d7d660b 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_posting_a_message_via_the_messaging_gateway_async.cs @@ -37,7 +37,7 @@ public SqsMessageProducerSendAsyncTests() channelName: new ChannelName(_queueName), routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_assume_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_assume_throws_async.cs index 5911a3f620..13715e4d37 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_assume_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_assume_throws_async.cs @@ -26,7 +26,7 @@ public AWSAssumeQueuesTestsAsync() routingKey: routingKey, makeChannels: OnMissingChannel.Assume, messagePumpType: MessagePumpType.Proactor, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); var awsConnection = GatewayFactory.CreateFactory(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_verify_throws_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_verify_throws_async.cs index 3edb7fb752..c5e4f55296 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_verify_throws_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_queues_missing_verify_throws_async.cs @@ -26,7 +26,7 @@ public AWSValidateQueuesTestsAsync() channelName: new ChannelName(queueName), routingKey: routingKey, makeChannels: OnMissingChannel.Validate, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _awsConnection = GatewayFactory.CreateFactory(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs index c8d1cf7653..4b2ed34a43 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_rejecting_a_message_through_gateway_with_requeue_async.cs @@ -35,7 +35,7 @@ public SqsMessageConsumerRequeueTestsAsync() routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, makeChannels: OnMissingChannel.Create, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_a_message_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_a_message_async.cs index 9c6e7b9bf2..3a0acb74f1 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_a_message_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_a_message_async.cs @@ -36,7 +36,7 @@ public SqsMessageProducerRequeueTestsAsync() routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, makeChannels: OnMissingChannel.Create, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_redrives_to_the_dlq_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_redrives_to_the_dlq_async.cs index ff6bba6c3a..600b09c73a 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_redrives_to_the_dlq_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_requeueing_redrives_to_the_dlq_async.cs @@ -42,7 +42,7 @@ public SqsMessageProducerDlqTestsAsync() routingKey: routingKey, messagePumpType: MessagePumpType.Proactor, redrivePolicy: new RedrivePolicy(_dlqChannelName, 2), - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_throwing_defer_action_respect_redrive_async.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_throwing_defer_action_respect_redrive_async.cs index 843684e393..07b2f31032 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_throwing_defer_action_respect_redrive_async.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Proactor/When_throwing_defer_action_respect_redrive_async.cs @@ -47,7 +47,7 @@ public SnsReDrivePolicySDlqTestsAsync() requeueDelay: TimeSpan.FromMilliseconds(50), messagePumpType: MessagePumpType.Proactor, redrivePolicy: new RedrivePolicy(new ChannelName(_dlqChannelName), 2), - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); var myCommand = new MyDeferredCommand { Value = "Hello Redrive" }; diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_a_message_consumer_reads_multiple_messages.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_a_message_consumer_reads_multiple_messages.cs index 444f631d9b..33fb5dc3fe 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_a_message_consumer_reads_multiple_messages.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_a_message_consumer_reads_multiple_messages.cs @@ -39,7 +39,7 @@ public SQSBufferedConsumerTests() routingKey:routingKey, bufferSize: BufferSize, makeChannels: OnMissingChannel.Create, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint )); //we want to access via a consumer, to receive multiple messages - we don't want to expose on channel diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_customising_aws_client_config.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_customising_aws_client_config.cs index 07c969c687..b19d3f0331 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_customising_aws_client_config.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_customising_aws_client_config.cs @@ -32,7 +32,7 @@ public CustomisingAwsClientConfigTests() channelName: new ChannelName(queueName), messagePumpType: MessagePumpType.Reactor, routingKey: routingKey, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_assume.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_assume.cs index ff19e223ca..f1ffd72f73 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_assume.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_assume.cs @@ -37,7 +37,7 @@ public AWSAssumeInfrastructureTests() routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Create, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( @@ -59,7 +59,7 @@ public AWSAssumeInfrastructureTests() name: new SubscriptionName(subscriptionName), channelName: new ChannelName(queueName), routingKey: routingKey, - routingKeyType: RoutingKeyType.PointToPoint, + channelType: ChannelType.PointToPoint, messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Assume ); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify.cs index 0419c47900..fda5e3d035 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_infastructure_exists_can_verify.cs @@ -37,7 +37,7 @@ public AWSValidateInfrastructureTests() routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Create, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( @@ -62,7 +62,7 @@ public AWSValidateInfrastructureTests() findTopicBy: TopicFindBy.Name, messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Validate, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _messageProducer = new SqsMessageProducer( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_posting_a_message_via_the_messaging_gateway.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_posting_a_message_via_the_messaging_gateway.cs index 13e4a98391..0d7ee2cf97 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_posting_a_message_via_the_messaging_gateway.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_posting_a_message_via_the_messaging_gateway.cs @@ -38,7 +38,7 @@ public SqsMessageProducerSendTests() channelName: new ChannelName(_queueName), routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_assume_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_assume_throws.cs index cffd913120..03d4d60f88 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_assume_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_assume_throws.cs @@ -26,7 +26,7 @@ public AWSAssumeQueuesTests() routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Assume, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); var awsConnection = GatewayFactory.CreateFactory(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_verify_throws.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_verify_throws.cs index 77fccb05fe..2f28f078a7 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_verify_throws.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_queues_missing_verify_throws.cs @@ -27,7 +27,7 @@ public AWSValidateQueuesTests() routingKey: routingKey, messagePumpType: MessagePumpType.Reactor, makeChannels: OnMissingChannel.Validate, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _awsConnection = GatewayFactory.CreateFactory(); diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs index 8ad6d17847..513eceb157 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_rejecting_a_message_through_gateway_with_requeue.cs @@ -34,7 +34,7 @@ public SqsMessageConsumerRequeueTests() channelName: new ChannelName(queueName), messagePumpType: MessagePumpType.Reactor, routingKey: routingKey, - routingKeyType: RoutingKeyType.PointToPoint + channelType: ChannelType.PointToPoint ); _message = new Message( diff --git a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_throwing_defer_action_respect_redrive.cs b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_throwing_defer_action_respect_redrive.cs index dfb60c7ac6..1c29d24c19 100644 --- a/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_throwing_defer_action_respect_redrive.cs +++ b/tests/Paramore.Brighter.AWS.Tests/MessagingGateway/Sqs/Standard/Reactor/When_throwing_defer_action_respect_redrive.cs @@ -44,7 +44,7 @@ public SnsReDrivePolicySDlqTests() name: new SubscriptionName(subscriptionName), channelName: new ChannelName(queueName), routingKey: routingKey, - routingKeyType: RoutingKeyType.PointToPoint, + channelType: ChannelType.PointToPoint, //don't block the redrive policy from owning retry management requeueCount: -1, //delay before requeuing From 9d619e489a7ad70ada6cc36977d6a8a78312efb9 Mon Sep 17 00:00:00 2001 From: Rafael Andrade Date: Mon, 6 Jan 2025 13:27:05 +0000 Subject: [PATCH 18/18] Set max delay --- .../SqsMessageSender.cs | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageSender.cs b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageSender.cs index 31af946dec..028d2b17e3 100644 --- a/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageSender.cs +++ b/src/Paramore.Brighter.MessagingGateway.AWSSQS/SqsMessageSender.cs @@ -6,6 +6,8 @@ using System.Threading.Tasks; using Amazon.SQS; using Amazon.SQS.Model; +using Microsoft.Extensions.Logging; +using Paramore.Brighter.Logging; namespace Paramore.Brighter.MessagingGateway.AWSSQS; @@ -14,6 +16,9 @@ namespace Paramore.Brighter.MessagingGateway.AWSSQS; /// public class SqsMessageSender { + private static readonly ILogger s_logger = ApplicationLogging.CreateLogger(); + private static readonly TimeSpan s_maxDelay = TimeSpan.FromSeconds(900); + private readonly string _queueUrl; private readonly SnsSqsType _queueType; private readonly AmazonSQSClient _client; @@ -30,13 +35,32 @@ public SqsMessageSender(string queueUrl, SnsSqsType queueType, AmazonSQSClient c _queueType = queueType; _client = client; } - + + /// + /// Sending message via SQS + /// + /// The message. + /// The delay in ms. 0 is no delay. Defaults to 0 + /// A that cancels the Publish operation + /// The message id. public async Task SendAsync(Message message, TimeSpan? delay, CancellationToken cancellationToken) { - var request = new SendMessageRequest { QueueUrl = _queueUrl, MessageBody = message.Body.Value, }; + var request = new SendMessageRequest + { + QueueUrl = _queueUrl, + MessageBody = message.Body.Value + }; - if (delay != null) + delay ??= TimeSpan.Zero; + if (delay > TimeSpan.Zero) { + // SQS has a hard limit of 15min for Delay in Seconds + if (delay.Value > s_maxDelay) + { + delay = s_maxDelay; + s_logger.LogWarning("Set delay from {CurrentDelay} to 15min (SQS support up to 15min)", delay); + } + request.DelaySeconds = (int)delay.Value.TotalSeconds; } @@ -52,11 +76,11 @@ public SqsMessageSender(string queueUrl, SnsSqsType queueType, AmazonSQSClient c var messageAttributes = new Dictionary { [HeaderNames.Id] = - new() { StringValue = Convert.ToString(message.Header.MessageId), DataType = "String" }, + new() { StringValue = message.Header.MessageId, DataType = "String" }, [HeaderNames.Topic] = new() { StringValue = _queueUrl, DataType = "String" }, [HeaderNames.ContentType] = new() { StringValue = message.Header.ContentType, DataType = "String" }, [HeaderNames.CorrelationId] = - new() { StringValue = Convert.ToString(message.Header.CorrelationId), DataType = "String" }, + new() { StringValue = message.Header.CorrelationId, DataType = "String" }, [HeaderNames.HandledCount] = new() { StringValue = Convert.ToString(message.Header.HandledCount), DataType = "String" }, [HeaderNames.MessageType] = @@ -81,7 +105,7 @@ public SqsMessageSender(string queueUrl, SnsSqsType queueType, AmazonSQSClient c // we can set up to 10 attributes; we have set 6 above, so use a single JSON object as the bag var bagJson = JsonSerializer.Serialize(message.Header.Bag, JsonSerialisationOptions.Options); - messageAttributes[HeaderNames.Bag] = new() { StringValue = Convert.ToString(bagJson), DataType = "String" }; + messageAttributes[HeaderNames.Bag] = new() { StringValue = bagJson, DataType = "String" }; request.MessageAttributes = messageAttributes; var response = await _client.SendMessageAsync(request, cancellationToken);