diff --git a/MessagingService.BusinessLogic.Tests/RequestHandlers/MessagingRequestHandlerTests.cs b/MessagingService.BusinessLogic.Tests/RequestHandlers/MessagingRequestHandlerTests.cs index 2f5145c..98aa3e6 100644 --- a/MessagingService.BusinessLogic.Tests/RequestHandlers/MessagingRequestHandlerTests.cs +++ b/MessagingService.BusinessLogic.Tests/RequestHandlers/MessagingRequestHandlerTests.cs @@ -31,6 +31,19 @@ public void MessagingRequestHandler_SendEmailRequest_IsHandled() } + [Fact] + public void MessagingRequestHandler_ResendEmailRequest_IsHandled() + { + Mock messagingDomainService = new Mock(); + MessagingRequestHandler handler = new MessagingRequestHandler(messagingDomainService.Object); + + ResendEmailRequest command = TestData.ResendEmailRequest; + + Should.NotThrow(async () => { + await handler.Handle(command, CancellationToken.None); + }); + } + [Fact] public void MessagingRequestHandler_SendSMSRequest_IsHandled() { diff --git a/MessagingService.BusinessLogic.Tests/Requests/RequestTests.cs b/MessagingService.BusinessLogic.Tests/Requests/RequestTests.cs index 6b51976..3665d7f 100644 --- a/MessagingService.BusinessLogic.Tests/Requests/RequestTests.cs +++ b/MessagingService.BusinessLogic.Tests/Requests/RequestTests.cs @@ -55,5 +55,13 @@ public void SendSMSRequest_CanBeCreated_IsCreated() request.Destination.ShouldBe(TestData.Destination); request.Message.ShouldBe(TestData.Message); } + + [Fact] + public void ResendEmailRequest_CanBeCreated_IsCreated() { + ResendEmailRequest request = ResendEmailRequest.Create(TestData.ConnectionIdentifier, TestData.MessageId); + request.ShouldNotBeNull(); + request.ConnectionIdentifier.ShouldBe(TestData.ConnectionIdentifier); + request.MessageId.ShouldBe(TestData.MessageId); + } } } diff --git a/MessagingService.BusinessLogic.Tests/Services/MessagingDomainServiceTests.cs b/MessagingService.BusinessLogic.Tests/Services/MessagingDomainServiceTests.cs index 2811080..5f688fc 100644 --- a/MessagingService.BusinessLogic.Tests/Services/MessagingDomainServiceTests.cs +++ b/MessagingService.BusinessLogic.Tests/Services/MessagingDomainServiceTests.cs @@ -52,6 +52,32 @@ await messagingDomainService.SendEmailMessage(TestData.ConnectionIdentifier, CancellationToken.None); } + [Fact] + public async Task MessagingDomainService_ResendEmailMessage_MessageSent() + { + Mock> emailAggregateRepository = new Mock>(); + emailAggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetSentEmailAggregate); + Mock> smsAggregateRepository = new Mock>(); + Mock emailServiceProxy = new Mock(); + emailServiceProxy + .Setup(e => e.SendEmail(It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())).ReturnsAsync(TestData.SuccessfulEmailServiceProxyResponse); + Mock smsServiceProxy = new Mock(); + + MessagingDomainService messagingDomainService = + new MessagingDomainService(emailAggregateRepository.Object, smsAggregateRepository.Object, emailServiceProxy.Object, smsServiceProxy.Object); + + await messagingDomainService.ResendEmailMessage(TestData.ConnectionIdentifier, + TestData.MessageId, + CancellationToken.None); + } + [Fact] public async Task MessagingDomainService_SendSMSMessage_MessageSent() { diff --git a/MessagingService.BusinessLogic/RequestHandlers/MessagingRequestHandler.cs b/MessagingService.BusinessLogic/RequestHandlers/MessagingRequestHandler.cs index 9c40c0b..3e1939a 100644 --- a/MessagingService.BusinessLogic/RequestHandlers/MessagingRequestHandler.cs +++ b/MessagingService.BusinessLogic/RequestHandlers/MessagingRequestHandler.cs @@ -11,7 +11,9 @@ /// /// /// - public class MessagingRequestHandler : IRequestHandler, IRequestHandler + public class MessagingRequestHandler : IRequestHandler, + IRequestHandler, + IRequestHandler { #region Fields @@ -74,5 +76,12 @@ await this.MessagingDomainService.SendSMSMessage(request.ConnectionIdentifier, } #endregion + + public async Task Handle(ResendEmailRequest request, + CancellationToken cancellationToken) { + await this.MessagingDomainService.ResendEmailMessage(request.ConnectionIdentifier, request.MessageId, cancellationToken); + + return String.Empty; + } } } \ No newline at end of file diff --git a/MessagingService.BusinessLogic/Requests/EmailAttachment.cs b/MessagingService.BusinessLogic/Requests/EmailAttachment.cs new file mode 100644 index 0000000..30281e4 --- /dev/null +++ b/MessagingService.BusinessLogic/Requests/EmailAttachment.cs @@ -0,0 +1,12 @@ +namespace MessagingService.BusinessLogic.Requests; + +using System; + +public class EmailAttachment +{ + public String Filename { get; set; } + + public String FileData { get; set; } + + public FileType FileType { get; set; } +} \ No newline at end of file diff --git a/MessagingService.BusinessLogic/Requests/FileType.cs b/MessagingService.BusinessLogic/Requests/FileType.cs new file mode 100644 index 0000000..b74263a --- /dev/null +++ b/MessagingService.BusinessLogic/Requests/FileType.cs @@ -0,0 +1,7 @@ +namespace MessagingService.BusinessLogic.Requests; + +public enum FileType +{ + None = 0, + PDF +} \ No newline at end of file diff --git a/MessagingService.BusinessLogic/Requests/ResendEmailRequest.cs b/MessagingService.BusinessLogic/Requests/ResendEmailRequest.cs new file mode 100644 index 0000000..8a4b2fa --- /dev/null +++ b/MessagingService.BusinessLogic/Requests/ResendEmailRequest.cs @@ -0,0 +1,48 @@ +namespace MessagingService.BusinessLogic.Requests; + +using System; +using MediatR; + +public class ResendEmailRequest : IRequest +{ + #region Constructors + + private ResendEmailRequest(Guid connectionIdentifier, + Guid messageId) + { + this.ConnectionIdentifier = connectionIdentifier; + this.MessageId = messageId; + } + + #endregion + + #region Properties + + /// + /// Gets the connection identifier. + /// + /// + /// The connection identifier. + /// + public Guid ConnectionIdentifier { get; } + + /// + /// Gets the message identifier. + /// + /// + /// The message identifier. + /// + public Guid MessageId { get; } + + #endregion + + #region Methods + + public static ResendEmailRequest Create(Guid connectionIdentifier, + Guid messageId) + { + return new ResendEmailRequest(connectionIdentifier, messageId); + } + + #endregion +} \ No newline at end of file diff --git a/MessagingService.BusinessLogic/Requests/SendEmailRequest.cs b/MessagingService.BusinessLogic/Requests/SendEmailRequest.cs index 4e08f2f..e04f268 100644 --- a/MessagingService.BusinessLogic/Requests/SendEmailRequest.cs +++ b/MessagingService.BusinessLogic/Requests/SendEmailRequest.cs @@ -141,19 +141,4 @@ public static SendEmailRequest Create(Guid connectionIdentifier, #endregion } - - public class EmailAttachment - { - public String Filename { get; set; } - - public String FileData { get; set; } - - public FileType FileType { get; set; } - } - - public enum FileType - { - None = 0, - PDF - } } \ No newline at end of file diff --git a/MessagingService.BusinessLogic/Services/IMessagingDomainService.cs b/MessagingService.BusinessLogic/Services/IMessagingDomainService.cs index b49e40d..69f4195 100644 --- a/MessagingService.BusinessLogic/Services/IMessagingDomainService.cs +++ b/MessagingService.BusinessLogic/Services/IMessagingDomainService.cs @@ -53,6 +53,10 @@ Task SendSMSMessage(Guid connectionIdentifier, String message, CancellationToken cancellationToken); + Task ResendEmailMessage(Guid connectionIdentifier, + Guid messageId, + CancellationToken cancellationToken); + #endregion } } \ No newline at end of file diff --git a/MessagingService.BusinessLogic/Services/MessagingDomainService.cs b/MessagingService.BusinessLogic/Services/MessagingDomainService.cs index 813b5cd..718d6ad 100644 --- a/MessagingService.BusinessLogic/Services/MessagingDomainService.cs +++ b/MessagingService.BusinessLogic/Services/MessagingDomainService.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Net.Mail; using System.Threading; using System.Threading.Tasks; using EmailMessage.DomainEvents; @@ -139,6 +140,31 @@ public async Task SendSMSMessage(Guid connectionIdentifier, // Save Changes to persistance await this.SmsAggregateRepository.SaveChanges(smsAggregate, cancellationToken); } + + public async Task ResendEmailMessage(Guid connectionIdentifier, + Guid messageId, + CancellationToken cancellationToken) { + // Rehydrate Email Message aggregate + EmailAggregate emailAggregate = await this.EmailAggregateRepository.GetLatestVersion(messageId, cancellationToken); + + // re-send message to provider (record event) + emailAggregate.ResendRequestToProvider(); + + // Make call to Email provider here + EmailServiceProxyResponse emailResponse = + await this.EmailServiceProxy.SendEmail(messageId, emailAggregate.FromAddress, + emailAggregate.GetToAddresses(), + emailAggregate.Subject, + emailAggregate.Body, + emailAggregate.IsHtml, null, + cancellationToken); + + // response message from provider (record event) + emailAggregate.ReceiveResponseFromProvider(emailResponse.RequestIdentifier, emailResponse.EmailIdentifier); + + // Save Changes to persistance + await this.EmailAggregateRepository.SaveChanges(emailAggregate, cancellationToken); + } } #endregion diff --git a/MessagingService.Client/IMessagingServiceClient.cs b/MessagingService.Client/IMessagingServiceClient.cs index 2fae8ac..2b95e2a 100644 --- a/MessagingService.Client/IMessagingServiceClient.cs +++ b/MessagingService.Client/IMessagingServiceClient.cs @@ -23,6 +23,10 @@ Task SendEmail(String accessToken, SendEmailRequest request, CancellationToken cancellationToken); + Task ResendEmail(String accessToken, + ResendEmailRequest request, + CancellationToken cancellationToken); + /// /// Sends the SMS. /// diff --git a/MessagingService.Client/MessagingServiceClient.cs b/MessagingService.Client/MessagingServiceClient.cs index bf66440..349f186 100644 --- a/MessagingService.Client/MessagingServiceClient.cs +++ b/MessagingService.Client/MessagingServiceClient.cs @@ -92,6 +92,32 @@ public async Task SendEmail(String accessToken, return response; } + public async Task ResendEmail(String accessToken, + ResendEmailRequest resendEmailRequest, + CancellationToken cancellationToken) { + String requestUri = this.BuildRequestUrl("/api/email/resend"); + + try { + String requestSerialised = JsonConvert.SerializeObject(resendEmailRequest); + + StringContent httpContent = new StringContent(requestSerialised, Encoding.UTF8, "application/json"); + + // Add the access token to the client headers + this.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + // Make the Http Call here + HttpResponseMessage httpResponse = await this.HttpClient.PostAsync(requestUri, httpContent, cancellationToken); + + httpResponse.EnsureSuccessStatusCode(); + } + catch(Exception ex) { + // An exception has occurred, add some additional information to the message + Exception exception = new Exception("Error re-sending email message.", ex); + + throw exception; + } + } + /// /// Sends the SMS. /// diff --git a/MessagingService.DataTransferObjects/ResendEmailRequest.cs b/MessagingService.DataTransferObjects/ResendEmailRequest.cs new file mode 100644 index 0000000..cd2a89f --- /dev/null +++ b/MessagingService.DataTransferObjects/ResendEmailRequest.cs @@ -0,0 +1,16 @@ +namespace MessagingService.DataTransferObjects +{ + using System; + using System.Diagnostics.CodeAnalysis; + using Newtonsoft.Json; + + [ExcludeFromCodeCoverage] + public class ResendEmailRequest + { + [JsonProperty("message_id")] + public Guid MessageId { get; set; } + + [JsonProperty("connection_identifier")] + public Guid ConnectionIdentifier { get; set; } + } +} \ No newline at end of file diff --git a/MessagingService.EmailAggregate.Tests/EmailAggregateDomainEventTests.cs b/MessagingService.EmailAggregate.Tests/EmailAggregateDomainEventTests.cs index 0f5f82b..ae29194 100644 --- a/MessagingService.EmailAggregate.Tests/EmailAggregateDomainEventTests.cs +++ b/MessagingService.EmailAggregate.Tests/EmailAggregateDomainEventTests.cs @@ -108,5 +108,15 @@ public void EmailMessageMarkedAsSpamEvent_CanBeCreated_IsCreated() messageMarkedAsSpamEvent.ProviderStatus.ShouldBe(TestData.ProviderStatusDescription); messageMarkedAsSpamEvent.SpamDateTime.ShouldBe(TestData.SpamDateTime); } + + [Fact] + public void RequestResentToEmailProviderEvent_CanBeCreated_IsCreated() { + RequestResentToEmailProviderEvent requestResentToEmailProviderEvent = new RequestResentToEmailProviderEvent(TestData.MessageId); + + requestResentToEmailProviderEvent.ShouldNotBeNull(); + requestResentToEmailProviderEvent.AggregateId.ShouldBe(TestData.MessageId); + requestResentToEmailProviderEvent.EventId.ShouldNotBe(Guid.Empty); + requestResentToEmailProviderEvent.MessageId.ShouldBe(TestData.MessageId); + } } } \ No newline at end of file diff --git a/MessagingService.EmailAggregate.Tests/EmailAggregateTests.cs b/MessagingService.EmailAggregate.Tests/EmailAggregateTests.cs index ea6821e..98c7741 100644 --- a/MessagingService.EmailAggregate.Tests/EmailAggregateTests.cs +++ b/MessagingService.EmailAggregate.Tests/EmailAggregateTests.cs @@ -4,22 +4,21 @@ namespace MessagingService.EmailAggregate.Tests { using System; using EmailMessageAggregate; + using Shared.Logger; using Shouldly; using Testing; public class EmailAggregateTests { [Fact] - public void EmailAggregate_CanBeCreated_IsCreated() - { + public void EmailAggregate_CanBeCreated_IsCreated() { EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); emailAggregate.MessageId.ShouldBe(TestData.MessageId); } [Fact] - public void EmailAggregate_SendRequestToProvider_RequestSent() - { + public void EmailAggregate_SendRequestToProvider_RequestSent() { EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); @@ -28,19 +27,18 @@ public void EmailAggregate_SendRequestToProvider_RequestSent() emailAggregate.Subject.ShouldBe(TestData.Subject); emailAggregate.Body.ShouldBe(TestData.Body); emailAggregate.IsHtml.ShouldBe(TestData.IsHtmlTrue); - emailAggregate.MessageStatus.ShouldBe(MessageStatus.InProgress); + MessageStatus messageStatus = emailAggregate.GetDeliveryStatus(); + messageStatus.ShouldBe(MessageStatus.InProgress); // TODO: Get Recipients } [Fact] - public void EmailAggregate_SendRequestToProvider_RequestAlreadySent_ErrorThrown() - { + public void EmailAggregate_SendRequestToProvider_RequestAlreadySent_ErrorThrown() { EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); - Should.Throw(() => - { + Should.Throw(() => { emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, @@ -50,8 +48,7 @@ public void EmailAggregate_SendRequestToProvider_RequestAlreadySent_ErrorThrown( } [Fact] - public void EmailAggregate_ReceiveResponseFromProvider_ResponseReceived() - { + public void EmailAggregate_ReceiveResponseFromProvider_ResponseReceived() { EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); @@ -59,20 +56,20 @@ public void EmailAggregate_ReceiveResponseFromProvider_ResponseReceived() emailAggregate.ProviderRequestReference.ShouldBe(TestData.ProviderRequestReference); emailAggregate.ProviderEmailReference.ShouldBe(TestData.ProviderEmailReference); - emailAggregate.MessageStatus.ShouldBe(MessageStatus.Sent); + MessageStatus messageStatus = emailAggregate.GetDeliveryStatus(); + messageStatus.ShouldBe(MessageStatus.Sent); } [Fact] - public void EmailAggregate_MarkMessageAsDelivered_MessageMarkedAsDelivered() - { + public void EmailAggregate_MarkMessageAsDelivered_MessageMarkedAsDelivered() { EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); emailAggregate.ReceiveResponseFromProvider(TestData.ProviderRequestReference, TestData.ProviderEmailReference); emailAggregate.MarkMessageAsDelivered(TestData.ProviderStatusDescription, TestData.DeliveredDateTime); - - emailAggregate.MessageStatus.ShouldBe(MessageStatus.Delivered); + MessageStatus messageStatus = emailAggregate.GetDeliveryStatus(); + messageStatus.ShouldBe(MessageStatus.Delivered); } [Theory] @@ -82,14 +79,12 @@ public void EmailAggregate_MarkMessageAsDelivered_MessageMarkedAsDelivered() [InlineData(MessageStatus.Failed)] [InlineData(MessageStatus.Spam)] [InlineData(MessageStatus.Bounced)] - public void EmailAggregate_MarkMessageAsDelivered_IncorrectState_ErrorThrown(MessageStatus messageStatus) - { + public void EmailAggregate_MarkMessageAsDelivered_IncorrectState_ErrorThrown(MessageStatus messageStatus) { EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); - - switch(messageStatus) - { + + switch(messageStatus) { case MessageStatus.Delivered: emailAggregate.ReceiveResponseFromProvider(TestData.ProviderRequestReference, TestData.ProviderEmailReference); emailAggregate.MarkMessageAsDelivered(TestData.ProviderStatusDescription, TestData.DeliveredDateTime); @@ -115,23 +110,19 @@ public void EmailAggregate_MarkMessageAsDelivered_IncorrectState_ErrorThrown(Mes } - Should.Throw(() => - { - emailAggregate.MarkMessageAsDelivered(TestData.ProviderStatusDescription, TestData.DeliveredDateTime); - }); + Should.Throw(() => { emailAggregate.MarkMessageAsDelivered(TestData.ProviderStatusDescription, TestData.DeliveredDateTime); }); } [Fact] - public void EmailAggregate_MarkMessageAsRejected_MessageMarkedAsRejected() - { + public void EmailAggregate_MarkMessageAsRejected_MessageMarkedAsRejected() { EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); emailAggregate.ReceiveResponseFromProvider(TestData.ProviderRequestReference, TestData.ProviderEmailReference); emailAggregate.MarkMessageAsRejected(TestData.ProviderStatusDescription, TestData.RejectedDateTime); - - emailAggregate.MessageStatus.ShouldBe(MessageStatus.Rejected); + MessageStatus messageStatus = emailAggregate.GetDeliveryStatus(); + messageStatus.ShouldBe(MessageStatus.Rejected); } [Theory] @@ -141,14 +132,12 @@ public void EmailAggregate_MarkMessageAsRejected_MessageMarkedAsRejected() [InlineData(MessageStatus.Failed)] [InlineData(MessageStatus.Spam)] [InlineData(MessageStatus.Bounced)] - public void EmailAggregate_MarkMessageAsRejected_IncorrectState_ErrorThrown(MessageStatus messageStatus) - { + public void EmailAggregate_MarkMessageAsRejected_IncorrectState_ErrorThrown(MessageStatus messageStatus) { EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); - - switch (messageStatus) - { + + switch(messageStatus) { case MessageStatus.Delivered: emailAggregate.ReceiveResponseFromProvider(TestData.ProviderRequestReference, TestData.ProviderEmailReference); emailAggregate.MarkMessageAsDelivered(TestData.ProviderStatusDescription, TestData.DeliveredDateTime); @@ -173,23 +162,19 @@ public void EmailAggregate_MarkMessageAsRejected_IncorrectState_ErrorThrown(Mess break; } - Should.Throw(() => - { - emailAggregate.MarkMessageAsRejected(TestData.ProviderStatusDescription, TestData.RejectedDateTime); - }); + Should.Throw(() => { emailAggregate.MarkMessageAsRejected(TestData.ProviderStatusDescription, TestData.RejectedDateTime); }); } [Fact] - public void EmailAggregate_MarkMessageAsFailed_MessageMarkedAsFailed() - { + public void EmailAggregate_MarkMessageAsFailed_MessageMarkedAsFailed() { EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); emailAggregate.ReceiveResponseFromProvider(TestData.ProviderRequestReference, TestData.ProviderEmailReference); emailAggregate.MarkMessageAsFailed(TestData.ProviderStatusDescription, TestData.FailedDateTime); - - emailAggregate.MessageStatus.ShouldBe(MessageStatus.Failed); + MessageStatus messageStatus = emailAggregate.GetDeliveryStatus(); + messageStatus.ShouldBe(MessageStatus.Failed); } [Theory] @@ -199,14 +184,12 @@ public void EmailAggregate_MarkMessageAsFailed_MessageMarkedAsFailed() [InlineData(MessageStatus.Failed)] [InlineData(MessageStatus.Spam)] [InlineData(MessageStatus.Bounced)] - public void EmailAggregate_MarkMessageAsFailed_IncorrectState_ErrorThrown(MessageStatus messageStatus) - { + public void EmailAggregate_MarkMessageAsFailed_IncorrectState_ErrorThrown(MessageStatus messageStatus) { EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); - switch (messageStatus) - { + switch(messageStatus) { case MessageStatus.Delivered: emailAggregate.ReceiveResponseFromProvider(TestData.ProviderRequestReference, TestData.ProviderEmailReference); emailAggregate.MarkMessageAsDelivered(TestData.ProviderStatusDescription, TestData.DeliveredDateTime); @@ -231,23 +214,19 @@ public void EmailAggregate_MarkMessageAsFailed_IncorrectState_ErrorThrown(Messag break; } - Should.Throw(() => - { - emailAggregate.MarkMessageAsFailed(TestData.ProviderStatusDescription, TestData.FailedDateTime); - }); + Should.Throw(() => { emailAggregate.MarkMessageAsFailed(TestData.ProviderStatusDescription, TestData.FailedDateTime); }); } [Fact] - public void EmailAggregate_MarkMessageAsBounced_MessageMarkedAsBounced() - { + public void EmailAggregate_MarkMessageAsBounced_MessageMarkedAsBounced() { EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); emailAggregate.ReceiveResponseFromProvider(TestData.ProviderRequestReference, TestData.ProviderEmailReference); emailAggregate.MarkMessageAsBounced(TestData.ProviderStatusDescription, TestData.BouncedDateTime); - - emailAggregate.MessageStatus.ShouldBe(MessageStatus.Bounced); + MessageStatus messageStatus = emailAggregate.GetDeliveryStatus(); + messageStatus.ShouldBe(MessageStatus.Bounced); } [Theory] @@ -257,14 +236,12 @@ public void EmailAggregate_MarkMessageAsBounced_MessageMarkedAsBounced() [InlineData(MessageStatus.Failed)] [InlineData(MessageStatus.Spam)] [InlineData(MessageStatus.Bounced)] - public void EmailAggregate_MarkMessageAsBounced_IncorrectState_ErrorThrown(MessageStatus messageStatus) - { + public void EmailAggregate_MarkMessageAsBounced_IncorrectState_ErrorThrown(MessageStatus messageStatus) { EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); - switch (messageStatus) - { + switch(messageStatus) { case MessageStatus.Delivered: emailAggregate.ReceiveResponseFromProvider(TestData.ProviderRequestReference, TestData.ProviderEmailReference); emailAggregate.MarkMessageAsDelivered(TestData.ProviderStatusDescription, TestData.DeliveredDateTime); @@ -289,23 +266,19 @@ public void EmailAggregate_MarkMessageAsBounced_IncorrectState_ErrorThrown(Messa break; } - Should.Throw(() => - { - emailAggregate.MarkMessageAsBounced(TestData.ProviderStatusDescription, TestData.BouncedDateTime); - }); + Should.Throw(() => { emailAggregate.MarkMessageAsBounced(TestData.ProviderStatusDescription, TestData.BouncedDateTime); }); } [Fact] - public void EmailAggregate_MarkMessageAsSpam_MessageMarkedAsSpam() - { + public void EmailAggregate_MarkMessageAsSpam_MessageMarkedAsSpam() { EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); emailAggregate.ReceiveResponseFromProvider(TestData.ProviderRequestReference, TestData.ProviderEmailReference); emailAggregate.MarkMessageAsSpam(TestData.ProviderStatusDescription, TestData.SpamDateTime); - - emailAggregate.MessageStatus.ShouldBe(MessageStatus.Spam); + MessageStatus messageStatus = emailAggregate.GetDeliveryStatus(); + messageStatus.ShouldBe(MessageStatus.Spam); } [Theory] @@ -315,14 +288,12 @@ public void EmailAggregate_MarkMessageAsSpam_MessageMarkedAsSpam() [InlineData(MessageStatus.Failed)] [InlineData(MessageStatus.Spam)] [InlineData(MessageStatus.Bounced)] - public void EmailAggregate_MarkMessageAsSpam_IncorrectState_ErrorThrown(MessageStatus messageStatus) - { + public void EmailAggregate_MarkMessageAsSpam_IncorrectState_ErrorThrown(MessageStatus messageStatus) { EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); - switch (messageStatus) - { + switch(messageStatus) { case MessageStatus.Delivered: emailAggregate.ReceiveResponseFromProvider(TestData.ProviderRequestReference, TestData.ProviderEmailReference); emailAggregate.MarkMessageAsDelivered(TestData.ProviderStatusDescription, TestData.DeliveredDateTime); @@ -347,10 +318,96 @@ public void EmailAggregate_MarkMessageAsSpam_IncorrectState_ErrorThrown(MessageS break; } - Should.Throw(() => - { - emailAggregate.MarkMessageAsSpam(TestData.ProviderStatusDescription, TestData.SpamDateTime); - }); + Should.Throw(() => { emailAggregate.MarkMessageAsSpam(TestData.ProviderStatusDescription, TestData.SpamDateTime); }); + } + + + + [Fact] + public void EmailAggregate_ResendRequestToProvider_IsSent_MessageIsResent() { + EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); + + emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); + emailAggregate.ReceiveResponseFromProvider(TestData.ProviderRequestReference, TestData.ProviderEmailReference); + emailAggregate.ResendRequestToProvider(); + + emailAggregate.ResendCount.ShouldBe(1); + emailAggregate.GetDeliveryStatus(1).ShouldBe(MessageStatus.InProgress); + } + + [Fact] + public void EmailAggregate_ResendRequestToProvider_IsDelivered_MessageIsResent() { + EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); + + emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); + emailAggregate.ReceiveResponseFromProvider(TestData.ProviderRequestReference, TestData.ProviderEmailReference); + emailAggregate.MarkMessageAsDelivered(TestData.ProviderStatusDescription, TestData.DeliveredDateTime); + emailAggregate.ResendRequestToProvider(); + + emailAggregate.ResendCount.ShouldBe(1); + emailAggregate.GetDeliveryStatus(1).ShouldBe(MessageStatus.InProgress); + } + + [Fact] + public void EmailAggregate_ResendRequestToProvider_NotSet_ErrorThrown() { + EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); + + Should.Throw(() => emailAggregate.ResendRequestToProvider()); + } + + [Fact] + public void EmailAggregate_ResendRequestToProvider_InProgress_ErrorThrown() { + EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); + + emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); + Should.Throw(() => emailAggregate.ResendRequestToProvider()); + } + + [Fact] + public void EmailAggregate_ResendRequestToProvider_Rejected_ErrorThrown() { + EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); + + emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); + emailAggregate.ReceiveResponseFromProvider(TestData.ProviderRequestReference, TestData.ProviderEmailReference); + emailAggregate.MarkMessageAsRejected(TestData.ProviderStatusDescription, TestData.RejectedDateTime); + Should.Throw(() => emailAggregate.ResendRequestToProvider()); + } + + [Fact] + public void EmailAggregate_ResendRequestToProvider_Failed_ErrorThrown() { + EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); + + emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); + emailAggregate.ReceiveResponseFromProvider(TestData.ProviderRequestReference, TestData.ProviderEmailReference); + emailAggregate.MarkMessageAsFailed(TestData.ProviderStatusDescription, TestData.FailedDateTime); + Should.Throw(() => emailAggregate.ResendRequestToProvider()); + } + + [Fact] + public void EmailAggregate_ResendRequestToProvider_Spam_ErrorThrown() { + EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); + + emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); + emailAggregate.ReceiveResponseFromProvider(TestData.ProviderRequestReference, TestData.ProviderEmailReference); + emailAggregate.MarkMessageAsSpam(TestData.ProviderStatusDescription, TestData.SpamDateTime); + Should.Throw(() => emailAggregate.ResendRequestToProvider()); + } + + [Fact] + public void EmailAggregate_ResendRequestToProvider_Bounced_ErrorThrown() { + EmailAggregate emailAggregate = EmailAggregate.Create(TestData.MessageId); + + emailAggregate.SendRequestToProvider(TestData.FromAddress, TestData.ToAddresses, TestData.Subject, TestData.Body, TestData.IsHtmlTrue); + emailAggregate.ReceiveResponseFromProvider(TestData.ProviderRequestReference, TestData.ProviderEmailReference); + emailAggregate.MarkMessageAsBounced(TestData.ProviderStatusDescription, TestData.BouncedDateTime); + Should.Throw(() => emailAggregate.ResendRequestToProvider()); + } + + [Fact] + public void EmailAggregate_PlayEvent_UnsupportedEvent_ErrorThrown() { + Logger.Initialise(NullLogger.Instance); + EmailAggregate emailAggregate = new EmailAggregate(); + Should.Throw(() => emailAggregate.PlayEvent(new TestEvent(Guid.NewGuid(), Guid.NewGuid()))); } } -} +} \ No newline at end of file diff --git a/MessagingService.EmailMessage.DomainEvents/RequestResentToEmailProviderEvent.cs b/MessagingService.EmailMessage.DomainEvents/RequestResentToEmailProviderEvent.cs new file mode 100644 index 0000000..4080872 --- /dev/null +++ b/MessagingService.EmailMessage.DomainEvents/RequestResentToEmailProviderEvent.cs @@ -0,0 +1,20 @@ +namespace MessagingService.EmailMessage.DomainEvents; + +using System; +using Shared.DomainDrivenDesign.EventSourcing; + +public record RequestResentToEmailProviderEvent : DomainEvent +{ + public RequestResentToEmailProviderEvent(Guid aggregateId) : base(aggregateId, Guid.NewGuid()) + { + this.MessageId = aggregateId; + } + + /// + /// Gets or sets the message identifier. + /// + /// + /// The message identifier. + /// + public Guid MessageId { get; init; } +} \ No newline at end of file diff --git a/MessagingService.EmailMessageAggregate/EmailAggregate.cs b/MessagingService.EmailMessageAggregate/EmailAggregate.cs index 6d0b42e..20a77e0 100644 --- a/MessagingService.EmailMessageAggregate/EmailAggregate.cs +++ b/MessagingService.EmailMessageAggregate/EmailAggregate.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Linq; using EmailMessage.DomainEvents; using Shared.DomainDrivenDesign.EventSourcing; using Shared.EventStore.Aggregate; @@ -18,6 +19,12 @@ public class EmailAggregate : Aggregate /// private readonly List Recipients; + private List ToAddresses; + + public List GetToAddresses() { + return this.ToAddresses; + } + #endregion #region Constructors @@ -29,6 +36,9 @@ public class EmailAggregate : Aggregate public EmailAggregate() { this.Recipients = new List(); + this.DeliveryStatusList = new List { + MessageStatus.NotSet + }; } /// @@ -42,6 +52,9 @@ private EmailAggregate(Guid aggregateId) this.AggregateId = aggregateId; this.MessageId = aggregateId; this.Recipients = new List(); + this.DeliveryStatusList = new List { + MessageStatus.NotSet + }; } #endregion @@ -80,14 +93,6 @@ private EmailAggregate(Guid aggregateId) /// public Guid MessageId { get; } - /// - /// Gets the message status. - /// - /// - /// The message status. - /// - public MessageStatus MessageStatus { get; private set; } - /// /// Gets the provider email reference. /// @@ -112,6 +117,10 @@ private EmailAggregate(Guid aggregateId) /// public String Subject { get; private set; } + public Int32 ResendCount { get; private set; } + + private List DeliveryStatusList; + #endregion #region Methods @@ -229,7 +238,7 @@ public void SendRequestToProvider(String fromAddress, String body, Boolean isHtml) { - if (this.MessageStatus != MessageStatus.NotSet) + if (this.DeliveryStatusList[this.ResendCount] != MessageStatus.NotSet) { throw new InvalidOperationException("Cannot send a message to provider that has already been sent"); } @@ -239,6 +248,19 @@ public void SendRequestToProvider(String fromAddress, this.ApplyAndAppend(requestSentToProviderEvent); } + public void ResendRequestToProvider() + { + if (this.DeliveryStatusList[this.ResendCount] != MessageStatus.Sent && + this.DeliveryStatusList[this.ResendCount] != MessageStatus.Delivered) + { + throw new InvalidOperationException($"Cannot re-send a message to provider that has not already been sent. Current Status [{this.DeliveryStatusList[this.ResendCount]}]"); + } + + RequestResentToEmailProviderEvent requestResentToEmailProviderEvent = new RequestResentToEmailProviderEvent(this.AggregateId); + + this.ApplyAndAppend(requestResentToEmailProviderEvent); + } + /// /// Gets the metadata. /// @@ -272,9 +294,9 @@ private void PlayEvent(Object @event) /// Message at status {this.MessageStatus} cannot be set to bounced private void CheckMessageCanBeSetToBounced() { - if (this.MessageStatus != MessageStatus.Sent) + if (this.DeliveryStatusList[this.ResendCount] != MessageStatus.Sent) { - throw new InvalidOperationException($"Message at status {this.MessageStatus} cannot be set to bounced"); + throw new InvalidOperationException($"Message at status {this.DeliveryStatusList[this.ResendCount]} cannot be set to bounced"); } } @@ -284,9 +306,9 @@ private void CheckMessageCanBeSetToBounced() /// Message at status {this.MessageStatus} cannot be set to delivered private void CheckMessageCanBeSetToDelivered() { - if (this.MessageStatus != MessageStatus.Sent) + if (this.DeliveryStatusList[this.ResendCount] != MessageStatus.Sent) { - throw new InvalidOperationException($"Message at status {this.MessageStatus} cannot be set to delivered"); + throw new InvalidOperationException($"Message at status {this.DeliveryStatusList[this.ResendCount]} cannot be set to delivered"); } } @@ -296,9 +318,9 @@ private void CheckMessageCanBeSetToDelivered() /// Message at status {this.MessageStatus} cannot be set to failed private void CheckMessageCanBeSetToFailed() { - if (this.MessageStatus != MessageStatus.Sent) + if (this.DeliveryStatusList[this.ResendCount] != MessageStatus.Sent) { - throw new InvalidOperationException($"Message at status {this.MessageStatus} cannot be set to failed"); + throw new InvalidOperationException($"Message at status {this.DeliveryStatusList[this.ResendCount]} cannot be set to failed"); } } @@ -308,9 +330,9 @@ private void CheckMessageCanBeSetToFailed() /// Message at status {this.MessageStatus} cannot be set to rejected private void CheckMessageCanBeSetToRejected() { - if (this.MessageStatus != MessageStatus.Sent) + if (this.DeliveryStatusList[this.ResendCount] != MessageStatus.Sent) { - throw new InvalidOperationException($"Message at status {this.MessageStatus} cannot be set to rejected"); + throw new InvalidOperationException($"Message at status {this.DeliveryStatusList[this.ResendCount]} cannot be set to rejected"); } } @@ -320,9 +342,9 @@ private void CheckMessageCanBeSetToRejected() /// Message at status {this.MessageStatus} cannot be set to spam private void CheckMessageCanBeSetToSpam() { - if (this.MessageStatus != MessageStatus.Sent) + if (this.DeliveryStatusList[this.ResendCount] != MessageStatus.Sent) { - throw new InvalidOperationException($"Message at status {this.MessageStatus} cannot be set to spam"); + throw new InvalidOperationException($"Message at status {this.DeliveryStatusList[this.ResendCount]} cannot be set to spam"); } } @@ -336,7 +358,9 @@ private void PlayEvent(RequestSentToEmailProviderEvent domainEvent) this.Subject = domainEvent.Subject; this.IsHtml = domainEvent.IsHtml; this.FromAddress = domainEvent.FromAddress; - this.MessageStatus = MessageStatus.InProgress; + this.ToAddresses = domainEvent.ToAddresses; + this.ResendCount = 0; + this.DeliveryStatusList[this.ResendCount] =MessageStatus.InProgress; foreach (String domainEventToAddress in domainEvent.ToAddresses) { @@ -354,7 +378,12 @@ private void PlayEvent(ResponseReceivedFromEmailProviderEvent domainEvent) { this.ProviderEmailReference = domainEvent.ProviderEmailReference; this.ProviderRequestReference = domainEvent.ProviderRequestReference; - this.MessageStatus = MessageStatus.Sent; + this.DeliveryStatusList[this.ResendCount] = MessageStatus.Sent; + } + + private void PlayEvent(RequestResentToEmailProviderEvent domainEvent) { + this.ResendCount++; + this.DeliveryStatusList.Add(MessageStatus.InProgress); } /// @@ -363,7 +392,14 @@ private void PlayEvent(ResponseReceivedFromEmailProviderEvent domainEvent) /// The domain event. private void PlayEvent(EmailMessageDeliveredEvent domainEvent) { - this.MessageStatus = MessageStatus.Delivered; + this.DeliveryStatusList[this.ResendCount] = MessageStatus.Delivered; + } + + public MessageStatus GetDeliveryStatus(Int32? resendAttempt = null) { + if (resendAttempt.HasValue == false) { + return this.DeliveryStatusList[this.ResendCount]; + } + return this.DeliveryStatusList[resendAttempt.Value]; } /// @@ -372,7 +408,7 @@ private void PlayEvent(EmailMessageDeliveredEvent domainEvent) /// The domain event. private void PlayEvent(EmailMessageFailedEvent domainEvent) { - this.MessageStatus = MessageStatus.Failed; + this.DeliveryStatusList[this.ResendCount] = MessageStatus.Failed; } /// @@ -381,7 +417,7 @@ private void PlayEvent(EmailMessageFailedEvent domainEvent) /// The domain event. private void PlayEvent(EmailMessageRejectedEvent domainEvent) { - this.MessageStatus = MessageStatus.Rejected; + this.DeliveryStatusList[this.ResendCount] = MessageStatus.Rejected; } /// @@ -390,7 +426,7 @@ private void PlayEvent(EmailMessageRejectedEvent domainEvent) /// The domain event. private void PlayEvent(EmailMessageBouncedEvent domainEvent) { - this.MessageStatus = MessageStatus.Bounced; + this.DeliveryStatusList[this.ResendCount] = MessageStatus.Bounced; } /// @@ -399,7 +435,7 @@ private void PlayEvent(EmailMessageBouncedEvent domainEvent) /// The domain event. private void PlayEvent(EmailMessageMarkedAsSpamEvent domainEvent) { - this.MessageStatus = MessageStatus.Spam; + this.DeliveryStatusList[this.ResendCount] = MessageStatus.Spam; } #endregion diff --git a/MessagingService.IntegrationTests/Common/DockerHelper.cs b/MessagingService.IntegrationTests/Common/DockerHelper.cs index 82a357b..30769d9 100644 --- a/MessagingService.IntegrationTests/Common/DockerHelper.cs +++ b/MessagingService.IntegrationTests/Common/DockerHelper.cs @@ -205,6 +205,15 @@ public override async Task StartContainersForScenarioRun(String scenarioName) HttpClient httpClient = new HttpClient(clientHandler); this.SecurityServiceClient = new SecurityServiceClient(SecurityServiceBaseAddressResolver, httpClient); this.MessagingServiceClient = new MessagingServiceClient(MessagingServiceBaseAddressResolver, httpClient); + + await PopulateSubscriptionServiceConfiguration(this.TestingContext.DockerHelper.IsSecureEventStore); + } + + public async Task PopulateSubscriptionServiceConfiguration(Boolean isSecureEventStore) + { + List<(String streamName, String groupName, Int32 maxRetries)> subscriptions = new List<(String streamName, String groupName, Int32 maxRetries)>(); + subscriptions.Add(($"$et-ResponseReceivedFromEmailProviderEvent", "Messaging", 2)); + await this.PopulateSubscriptionServiceConfiguration(this.EventStoreHttpPort, subscriptions, isSecureEventStore); } /// diff --git a/MessagingService.IntegrationTests/Common/TestingContext.cs b/MessagingService.IntegrationTests/Common/TestingContext.cs index 7e233c7..b19e39e 100644 --- a/MessagingService.IntegrationTests/Common/TestingContext.cs +++ b/MessagingService.IntegrationTests/Common/TestingContext.cs @@ -39,7 +39,15 @@ public ClientDetails GetClientDetails(String clientId) return clientDetails; } - public Dictionary EmailResponses; + public void AddEmailResponse(String identifier, SendEmailResponse response) { + this.EmailResponses.Add(identifier, response); + } + + public SendEmailResponse GetEmailResponse(String identifier) { + return this.EmailResponses[identifier]; + } + + private Dictionary EmailResponses; } public class ClientDetails diff --git a/MessagingService.IntegrationTests/Email/SendEmail.feature b/MessagingService.IntegrationTests/Email/SendEmail.feature index 4c97522..a6b423b 100644 --- a/MessagingService.IntegrationTests/Email/SendEmail.feature +++ b/MessagingService.IntegrationTests/Email/SendEmail.feature @@ -19,9 +19,19 @@ Background: | ClientId | | serviceClient | -@PRTest Scenario: Send Email Given I send the following Email Messages | FromAddress | ToAddresses | Subject | Body | IsHtml | | fromaddress@testemail.com | toaddress1@testemail.com | Test Email 1 | Test Body | true | | fromaddress@testemail.com | toaddress1@testemail.com,toaddress2@testemail.com | Test Email 1 | Test Body | true | + +@PRTest +Scenario: Resend Email + Given I send the following Email Messages + | FromAddress | ToAddresses | Subject | Body | IsHtml | + | fromaddress@testemail.com | toaddress1@testemail.com | Test Email 1 | Test Body | true | + | fromaddress@testemail.com | toaddress1@testemail.com,toaddress2@testemail.com | Test Email 1 | Test Body | true | + When I resend the following messages + | ToAddresses | + | toaddress1@testemail.com | + | toaddress1@testemail.com,toaddress2@testemail.com | \ No newline at end of file diff --git a/MessagingService.IntegrationTests/Email/SendEmail.feature.cs b/MessagingService.IntegrationTests/Email/SendEmail.feature.cs index 740cd5d..66b9bbb 100644 --- a/MessagingService.IntegrationTests/Email/SendEmail.feature.cs +++ b/MessagingService.IntegrationTests/Email/SendEmail.feature.cs @@ -143,14 +143,12 @@ void System.IDisposable.Dispose() [Xunit.SkippableFactAttribute(DisplayName="Send Email")] [Xunit.TraitAttribute("FeatureTitle", "SendEmail")] [Xunit.TraitAttribute("Description", "Send Email")] - [Xunit.TraitAttribute("Category", "PRTest")] public void SendEmail() { - string[] tagsOfScenario = new string[] { - "PRTest"}; + string[] tagsOfScenario = ((string[])(null)); System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Send Email", null, tagsOfScenario, argumentsOfScenario, featureTags); -#line 23 +#line 22 this.ScenarioInitialize(scenarioInfo); #line hidden if ((TagHelper.ContainsIgnoreTag(tagsOfScenario) || TagHelper.ContainsIgnoreTag(featureTags))) @@ -181,13 +179,70 @@ public void SendEmail() "Test Email 1", "Test Body", "true"}); -#line 24 +#line 23 testRunner.Given("I send the following Email Messages", ((string)(null)), table5, "Given "); #line hidden } this.ScenarioCleanup(); } + [Xunit.SkippableFactAttribute(DisplayName="Resend Email")] + [Xunit.TraitAttribute("FeatureTitle", "SendEmail")] + [Xunit.TraitAttribute("Description", "Resend Email")] + [Xunit.TraitAttribute("Category", "PRTest")] + public void ResendEmail() + { + string[] tagsOfScenario = new string[] { + "PRTest"}; + System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Resend Email", null, tagsOfScenario, argumentsOfScenario, featureTags); +#line 29 +this.ScenarioInitialize(scenarioInfo); +#line hidden + if ((TagHelper.ContainsIgnoreTag(tagsOfScenario) || TagHelper.ContainsIgnoreTag(featureTags))) + { + testRunner.SkipScenario(); + } + else + { + this.ScenarioStart(); +#line 4 +this.FeatureBackground(); +#line hidden + TechTalk.SpecFlow.Table table6 = new TechTalk.SpecFlow.Table(new string[] { + "FromAddress", + "ToAddresses", + "Subject", + "Body", + "IsHtml"}); + table6.AddRow(new string[] { + "fromaddress@testemail.com", + "toaddress1@testemail.com", + "Test Email 1", + "Test Body", + "true"}); + table6.AddRow(new string[] { + "fromaddress@testemail.com", + "toaddress1@testemail.com,toaddress2@testemail.com", + "Test Email 1", + "Test Body", + "true"}); +#line 30 + testRunner.Given("I send the following Email Messages", ((string)(null)), table6, "Given "); +#line hidden + TechTalk.SpecFlow.Table table7 = new TechTalk.SpecFlow.Table(new string[] { + "ToAddresses"}); + table7.AddRow(new string[] { + "toaddress1@testemail.com"}); + table7.AddRow(new string[] { + "toaddress1@testemail.com,toaddress2@testemail.com"}); +#line 34 + testRunner.When("I resend the following messages", ((string)(null)), table7, "When "); +#line hidden + } + this.ScenarioCleanup(); + } + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.9.0.0")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class FixtureData : System.IDisposable diff --git a/MessagingService.IntegrationTests/Email/SendEmailSteps.cs b/MessagingService.IntegrationTests/Email/SendEmailSteps.cs index 554d951..5f3bbe3 100644 --- a/MessagingService.IntegrationTests/Email/SendEmailSteps.cs +++ b/MessagingService.IntegrationTests/Email/SendEmailSteps.cs @@ -12,8 +12,10 @@ namespace MessagingService.IntegrationTests.Email using System.Threading.Tasks; using Common; using DataTransferObjects; + using global::Shared.IntegrationTesting; using Newtonsoft.Json; using Shouldly; + using SpecflowTableHelper = Common.SpecflowTableHelper; [Binding] [Scope(Tag = "email")] @@ -39,6 +41,15 @@ public async Task GivenISendTheFollowingEmailMessages(Table table) } } + [When(@"I resend the following messages")] + public async Task WhenIResendTheFollowingMessages(Table table) + { + foreach (TableRow tableRow in table.Rows) + { + await this.ResendEmail(tableRow); + } + } + private async Task SendEmail(TableRow tableRow) { String fromAddress = SpecflowTableHelper.GetStringRowValue(tableRow, "FromAddress"); @@ -60,6 +71,28 @@ private async Task SendEmail(TableRow tableRow) SendEmailResponse sendEmailResponse = await this.TestingContext.DockerHelper.MessagingServiceClient.SendEmail(this.TestingContext.AccessToken, request, CancellationToken.None).ConfigureAwait(false); sendEmailResponse.MessageId.ShouldNotBe(Guid.Empty); + + this.TestingContext.AddEmailResponse(toAddresses, sendEmailResponse); + } + + private async Task ResendEmail(TableRow tableRow) + { + String toAddresses = SpecflowTableHelper.GetStringRowValue(tableRow, "ToAddresses"); + SendEmailResponse sendEmailResponse = this.TestingContext.GetEmailResponse(toAddresses); + + ResendEmailRequest request = new ResendEmailRequest() + { + ConnectionIdentifier = Guid.NewGuid(), + MessageId = sendEmailResponse.MessageId + }; + + await Retry.For(async () => { + Should.NotThrow(async () => { + await this.TestingContext.DockerHelper.MessagingServiceClient.ResendEmail(this.TestingContext.AccessToken, + request, + CancellationToken.None); + }); + }); } } } diff --git a/MessagingService.IntegrationTests/SMS/SendSMS.feature.cs b/MessagingService.IntegrationTests/SMS/SendSMS.feature.cs index 864e5fc..74ea19e 100644 --- a/MessagingService.IntegrationTests/SMS/SendSMS.feature.cs +++ b/MessagingService.IntegrationTests/SMS/SendSMS.feature.cs @@ -85,53 +85,53 @@ public virtual void FeatureBackground() { #line 4 #line hidden - TechTalk.SpecFlow.Table table6 = new TechTalk.SpecFlow.Table(new string[] { + TechTalk.SpecFlow.Table table8 = new TechTalk.SpecFlow.Table(new string[] { "Name", "DisplayName", "Description"}); - table6.AddRow(new string[] { + table8.AddRow(new string[] { "messagingService", "Messaging REST Scope", "A scope for Messaging REST"}); #line 6 - testRunner.Given("I create the following api scopes", ((string)(null)), table6, "Given "); + testRunner.Given("I create the following api scopes", ((string)(null)), table8, "Given "); #line hidden - TechTalk.SpecFlow.Table table7 = new TechTalk.SpecFlow.Table(new string[] { + TechTalk.SpecFlow.Table table9 = new TechTalk.SpecFlow.Table(new string[] { "ResourceName", "DisplayName", "Secret", "Scopes", "UserClaims"}); - table7.AddRow(new string[] { + table9.AddRow(new string[] { "messagingService", "Messaging REST", "Secret1", "messagingService", ""}); #line 10 - testRunner.Given("the following api resources exist", ((string)(null)), table7, "Given "); + testRunner.Given("the following api resources exist", ((string)(null)), table9, "Given "); #line hidden - TechTalk.SpecFlow.Table table8 = new TechTalk.SpecFlow.Table(new string[] { + TechTalk.SpecFlow.Table table10 = new TechTalk.SpecFlow.Table(new string[] { "ClientId", "ClientName", "Secret", "AllowedScopes", "AllowedGrantTypes"}); - table8.AddRow(new string[] { + table10.AddRow(new string[] { "serviceClient", "Service Client", "Secret1", "messagingService", "client_credentials"}); #line 14 - testRunner.Given("the following clients exist", ((string)(null)), table8, "Given "); + testRunner.Given("the following clients exist", ((string)(null)), table10, "Given "); #line hidden - TechTalk.SpecFlow.Table table9 = new TechTalk.SpecFlow.Table(new string[] { + TechTalk.SpecFlow.Table table11 = new TechTalk.SpecFlow.Table(new string[] { "ClientId"}); - table9.AddRow(new string[] { + table11.AddRow(new string[] { "serviceClient"}); #line 18 - testRunner.Given("I have a token to access the messaging service resource", ((string)(null)), table9, "Given "); + testRunner.Given("I have a token to access the messaging service resource", ((string)(null)), table11, "Given "); #line hidden } @@ -163,16 +163,16 @@ public void SendSMS() #line 4 this.FeatureBackground(); #line hidden - TechTalk.SpecFlow.Table table10 = new TechTalk.SpecFlow.Table(new string[] { + TechTalk.SpecFlow.Table table12 = new TechTalk.SpecFlow.Table(new string[] { "Sender", "Destination", "Message"}); - table10.AddRow(new string[] { + table12.AddRow(new string[] { "TestSender", "07777777771", "TestSMSMessage"}); #line 24 - testRunner.Given("I send the following SMS Messages", ((string)(null)), table10, "Given "); + testRunner.Given("I send the following SMS Messages", ((string)(null)), table12, "Given "); #line hidden } this.ScenarioCleanup(); diff --git a/MessagingService.SMSAggregate.Tests/SMSAggregateTests.cs b/MessagingService.SMSAggregate.Tests/SMSAggregateTests.cs index 489e91b..7ddb2a4 100644 --- a/MessagingService.SMSAggregate.Tests/SMSAggregateTests.cs +++ b/MessagingService.SMSAggregate.Tests/SMSAggregateTests.cs @@ -5,6 +5,7 @@ namespace MessagingService.SMSAggregate.Tests { using EmailMessageAggregate; + using Shared.Logger; using Shouldly; using SMSMessageAggregate; using Testing; @@ -281,5 +282,13 @@ public void SMSAggregate_MarkMessageAsRejected_IncorrectState_ErrorThrown(Messag smsAggregate.MarkMessageAsRejected(TestData.ProviderStatusDescription, TestData.DeliveredDateTime); }); } + + [Fact] + public void EmailAggregate_PlayEvent_UnsupportedEvent_ErrorThrown() + { + Logger.Initialise(NullLogger.Instance); + SMSAggregate smsAggregate = new SMSAggregate(); + Should.Throw(() => smsAggregate.PlayEvent(new TestEvent(Guid.NewGuid(), Guid.NewGuid()))); + } } -} +} \ No newline at end of file diff --git a/MessagingService.Testing/TestData.cs b/MessagingService.Testing/TestData.cs index a23ba5a..5b199d9 100644 --- a/MessagingService.Testing/TestData.cs +++ b/MessagingService.Testing/TestData.cs @@ -9,6 +9,7 @@ using EmailMessage.DomainEvents; using EmailMessageAggregate; using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; + using Shared.DomainDrivenDesign.EventSourcing; using SMSMessage.DomainEvents; using SMSMessageAggregate; using EmailMessageStatus = BusinessLogic.Services.EmailServices.MessageStatus; @@ -201,15 +202,17 @@ public class TestData public static String FileName = "FileName.pdf"; public static FileType FileTypePDF = FileType.PDF; + public static ResendEmailRequest ResendEmailRequest => ResendEmailRequest.Create(TestData.ConnectionIdentifier, + TestData.MessageId); public static SendEmailRequest SendEmailRequestNoAttachments => SendEmailRequest.Create(TestData.ConnectionIdentifier, - TestData.MessageId, - TestData.FromAddress, - TestData.ToAddresses, - TestData.Subject, - TestData.Body, - TestData.IsHtmlTrue, - TestData.EmptyEmailAttachments); + TestData.MessageId, + TestData.FromAddress, + TestData.ToAddresses, + TestData.Subject, + TestData.Body, + TestData.IsHtmlTrue, + TestData.EmptyEmailAttachments); public static SendEmailRequest SendEmailRequestWithAttachments => SendEmailRequest.Create(TestData.ConnectionIdentifier, TestData.MessageId, @@ -264,4 +267,11 @@ public static SMSAggregate GetSentSMSAggregate() return smsAggregate; } } + + public record TestEvent : DomainEvent + { + public TestEvent(Guid aggregateId, + Guid eventId) : base(aggregateId, eventId) { + } + } } \ No newline at end of file diff --git a/MessagingService/Bootstrapper/MediatorRegistry.cs b/MessagingService/Bootstrapper/MediatorRegistry.cs index eb1ed58..86618c4 100644 --- a/MessagingService/Bootstrapper/MediatorRegistry.cs +++ b/MessagingService/Bootstrapper/MediatorRegistry.cs @@ -15,6 +15,7 @@ public MediatorRegistry() this.AddTransient(); this.AddSingleton, MessagingRequestHandler>(); this.AddSingleton, MessagingRequestHandler>(); + this.AddSingleton, MessagingRequestHandler>(); this.AddSingleton>(container => (serviceName) => { diff --git a/MessagingService/Common/Extensions.cs b/MessagingService/Common/Extensions.cs index 96d13db..1676351 100644 --- a/MessagingService/Common/Extensions.cs +++ b/MessagingService/Common/Extensions.cs @@ -1,12 +1,14 @@ namespace MessagingService.Common; using System; +using System.Diagnostics.CodeAnalysis; using System.Net.Http; using EventStore.Client; using Microsoft.Extensions.DependencyInjection; public static class Extensions { + [ExcludeFromCodeCoverage] public static IServiceCollection AddInSecureEventStoreClient( this IServiceCollection services, Uri address, diff --git a/MessagingService/Controllers/EmailController.cs b/MessagingService/Controllers/EmailController.cs index 264be97..536b988 100644 --- a/MessagingService/Controllers/EmailController.cs +++ b/MessagingService/Controllers/EmailController.cs @@ -1,5 +1,6 @@ using SendEmailRequestDTO = MessagingService.DataTransferObjects.SendEmailRequest; using SendEmailResponseDTO = MessagingService.DataTransferObjects.SendEmailResponse; +using ResendEmailRequestDTO = MessagingService.DataTransferObjects.ResendEmailRequest; namespace MessagingService.Controllers { @@ -105,6 +106,30 @@ public async Task SendEmail([FromBody] SendEmailRequestDTO sendEm }); } + [HttpPost] + [Route("resend")] + [SwaggerResponse(202, "Accepted", typeof(SendEmailResponseDTO))] + [SwaggerResponseExample(202, typeof(SendEmailResponseExample))] + public async Task ResendEmail([FromBody] ResendEmailRequestDTO resendEmailRequest, + CancellationToken cancellationToken) + { + // Reject password tokens + if (ClaimsHelper.IsPasswordToken(this.User)) + { + return this.Forbid(); + } + + // Create the command + ResendEmailRequest request = ResendEmailRequest.Create(resendEmailRequest.ConnectionIdentifier, + resendEmailRequest.MessageId); + + // Route the command + await this.Mediator.Send(request, cancellationToken); + + // return the result + return this.Accepted($"{EmailController.ControllerRoute}/{resendEmailRequest.MessageId}"); + } + private FileType ConvertFileType(DataTransferObjects.FileType emailAttachmentFileType) { switch(emailAttachmentFileType)