From 2e738d2610b0b880f43772802d02ee24c5a884bb Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Thu, 30 Mar 2023 19:17:09 +0100 Subject: [PATCH] Resend SMS Messages --- .../Mediator/DummyMessagingDomainService.cs | 3 + .../MessagingRequestHandlerTests.cs | 12 ++ .../Requests/RequestTests.cs | 9 ++ .../Services/MessagingDomainServiceTests.cs | 118 ++++++++++++++++++ .../MessagingRequestHandler.cs | 29 +++-- .../Requests/ResendEmailRequest.cs | 14 +-- .../Requests/ResendSMSRequest.cs | 36 ++++++ .../Requests/SendEmailRequest.cs | 2 +- .../Requests/SendSMSRequest.cs | 2 +- .../Services/IMessagingDomainService.cs | 4 + .../Services/MessagingDomainService.cs | 33 ++++- .../IntegrationTestSMSServiceProxy.cs | 2 +- .../SMSServices/SMSServiceProxyResponse.cs | 26 +--- .../TheSMSWorks/TheSmsWorksProxy.cs | 2 +- .../ResendSMSRequest.cs | 15 +++ .../SMSAggregateTests.cs | 95 ++++++++++++-- .../DomainEvents.cs | 4 +- .../SMSAggregate.cs | 69 +++++++--- MessagingService.Testing/TestData.cs | 22 +++- .../Bootstrapper/MediatorRegistry.cs | 7 +- MessagingService/Controllers/SMSController.cs | 24 ++++ 21 files changed, 442 insertions(+), 86 deletions(-) create mode 100644 MessagingService.BusinessLogic/Requests/ResendSMSRequest.cs create mode 100644 MessagingService.DataTransferObjects/ResendSMSRequest.cs diff --git a/MessagingService.BusinessLogic.Tests/Mediator/DummyMessagingDomainService.cs b/MessagingService.BusinessLogic.Tests/Mediator/DummyMessagingDomainService.cs index 4b4e764..be14b5a 100644 --- a/MessagingService.BusinessLogic.Tests/Mediator/DummyMessagingDomainService.cs +++ b/MessagingService.BusinessLogic.Tests/Mediator/DummyMessagingDomainService.cs @@ -35,5 +35,8 @@ public async Task ResendEmailMessage(Guid connectionIdentifier, Guid messageId, CancellationToken cancellationToken) { } + + public async Task ResendSMSMessage(Guid connectionIdentifier, Guid messageId, CancellationToken cancellationToken){ + } } } diff --git a/MessagingService.BusinessLogic.Tests/RequestHandlers/MessagingRequestHandlerTests.cs b/MessagingService.BusinessLogic.Tests/RequestHandlers/MessagingRequestHandlerTests.cs index 98aa3e6..ba887ff 100644 --- a/MessagingService.BusinessLogic.Tests/RequestHandlers/MessagingRequestHandlerTests.cs +++ b/MessagingService.BusinessLogic.Tests/RequestHandlers/MessagingRequestHandlerTests.cs @@ -56,7 +56,19 @@ public void MessagingRequestHandler_SendSMSRequest_IsHandled() { await handler.Handle(command, CancellationToken.None); }); + } + + [Fact] + public void MessagingRequestHandler_ResendSMSRequest_IsHandled() + { + Mock messagingDomainService = new Mock(); + MessagingRequestHandler handler = new MessagingRequestHandler(messagingDomainService.Object); + + ResendSMSRequest command = TestData.ResendSMSRequest; + Should.NotThrow(async () => { + await handler.Handle(command, CancellationToken.None); + }); } } } diff --git a/MessagingService.BusinessLogic.Tests/Requests/RequestTests.cs b/MessagingService.BusinessLogic.Tests/Requests/RequestTests.cs index c6b14a2..e2ddd0d 100644 --- a/MessagingService.BusinessLogic.Tests/Requests/RequestTests.cs +++ b/MessagingService.BusinessLogic.Tests/Requests/RequestTests.cs @@ -66,5 +66,14 @@ public void ResendEmailRequest_CanBeCreated_IsCreated() { request.ConnectionIdentifier.ShouldBe(TestData.ConnectionIdentifier); request.MessageId.ShouldBe(TestData.MessageId); } + + [Fact] + public void ResendSMSRequest_CanBeCreated_IsCreated() + { + ResendSMSRequest request = ResendSMSRequest.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 8f15cb4..b798b47 100644 --- a/MessagingService.BusinessLogic.Tests/Services/MessagingDomainServiceTests.cs +++ b/MessagingService.BusinessLogic.Tests/Services/MessagingDomainServiceTests.cs @@ -142,6 +142,58 @@ await messagingDomainService.ResendEmailMessage(TestData.ConnectionIdentifier, CancellationToken.None); } + [Fact] + public async Task MessagingDomainService_ResendEmailMessage_APICallFailed_MessageFailed() + { + 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.FailedAPICallEmailServiceProxyResponse); + 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_ResendEmailMessage_APIResponseError_MessageFailed() + { + 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.FailedEmailServiceProxyResponse); + 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() { @@ -167,5 +219,71 @@ await messagingDomainService.SendSMSMessage(TestData.ConnectionIdentifier, CancellationToken.None); } + [Fact] + public async Task MessagingDomainService_ReSendSMSMessage_MessageSent() + { + Mock> emailAggregateRepository = new Mock>(); + Mock> smsAggregateRepository = new Mock>(); + smsAggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetSentSMSAggregate); + Mock emailServiceProxy = new Mock(); + Mock smsServiceProxy = new Mock(); + smsServiceProxy + .Setup(e => e.SendSMS(It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())).ReturnsAsync(TestData.SuccessfulSMSServiceProxyResponse); + MessagingDomainService messagingDomainService = + new MessagingDomainService(emailAggregateRepository.Object, smsAggregateRepository.Object, emailServiceProxy.Object, smsServiceProxy.Object); + + await messagingDomainService.ResendSMSMessage(TestData.ConnectionIdentifier, + TestData.MessageId, + CancellationToken.None); + } + + [Fact] + public async Task MessagingDomainService_ReSendSMSMessage_APICallFailed_MessageFailed() + { + Mock> emailAggregateRepository = new Mock>(); + Mock> smsAggregateRepository = new Mock>(); + smsAggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetSentSMSAggregate); + Mock emailServiceProxy = new Mock(); + Mock smsServiceProxy = new Mock(); + smsServiceProxy + .Setup(e => e.SendSMS(It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())).ReturnsAsync(TestData.FailedAPICallSMSServiceProxyResponse); + MessagingDomainService messagingDomainService = + new MessagingDomainService(emailAggregateRepository.Object, smsAggregateRepository.Object, emailServiceProxy.Object, smsServiceProxy.Object); + + await messagingDomainService.ResendSMSMessage(TestData.ConnectionIdentifier, + TestData.MessageId, + CancellationToken.None); + } + + [Fact] + public async Task MessagingDomainService_ReSendSMSMessage_APIResponseError_MessageFailed() + { + Mock> emailAggregateRepository = new Mock>(); + Mock> smsAggregateRepository = new Mock>(); + smsAggregateRepository.Setup(a => a.GetLatestVersion(It.IsAny(), It.IsAny())).ReturnsAsync(TestData.GetSentSMSAggregate); + Mock emailServiceProxy = new Mock(); + Mock smsServiceProxy = new Mock(); + smsServiceProxy + .Setup(e => e.SendSMS(It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())).ReturnsAsync(TestData.FailedSMSServiceProxyResponse); + MessagingDomainService messagingDomainService = + new MessagingDomainService(emailAggregateRepository.Object, smsAggregateRepository.Object, emailServiceProxy.Object, smsServiceProxy.Object); + + await messagingDomainService.ResendSMSMessage(TestData.ConnectionIdentifier, + TestData.MessageId, + CancellationToken.None); + } + } } diff --git a/MessagingService.BusinessLogic/RequestHandlers/MessagingRequestHandler.cs b/MessagingService.BusinessLogic/RequestHandlers/MessagingRequestHandler.cs index b77a1a9..a220a57 100644 --- a/MessagingService.BusinessLogic/RequestHandlers/MessagingRequestHandler.cs +++ b/MessagingService.BusinessLogic/RequestHandlers/MessagingRequestHandler.cs @@ -13,9 +13,10 @@ /// /// /// - public class MessagingRequestHandler : IRequestHandler, - IRequestHandler, - IRequestHandler + public class MessagingRequestHandler : IRequestHandler, + IRequestHandler, + IRequestHandler, + IRequestHandler { #region Fields @@ -49,7 +50,7 @@ public MessagingRequestHandler(IMessagingDomainService messagingDomainService) /// /// Response from the request /// - public async Task Handle(SendEmailRequest request, + public async Task Handle(SendEmailRequest request, CancellationToken cancellationToken){ List attachments = new List(); @@ -71,11 +72,11 @@ await this.MessagingDomainService.SendEmailMessage(request.ConnectionIdentifier, attachments, cancellationToken); - return string.Empty; + return Unit.Value; } - public async Task Handle(SendSMSRequest request, - CancellationToken cancellationToken) + public async Task Handle(SendSMSRequest request, + CancellationToken cancellationToken) { await this.MessagingDomainService.SendSMSMessage(request.ConnectionIdentifier, request.MessageId, @@ -83,16 +84,16 @@ await this.MessagingDomainService.SendSMSMessage(request.ConnectionIdentifier, request.Destination, request.Message, cancellationToken); - return string.Empty; + return Unit.Value; } #endregion - public async Task Handle(ResendEmailRequest request, - CancellationToken cancellationToken) { + public async Task Handle(ResendEmailRequest request, + CancellationToken cancellationToken) { await this.MessagingDomainService.ResendEmailMessage(request.ConnectionIdentifier, request.MessageId, cancellationToken); - return String.Empty; + return Unit.Value; } private Models.FileType ConvertFileType(FileType emailAttachmentFileType) @@ -105,5 +106,11 @@ private Models.FileType ConvertFileType(FileType emailAttachmentFileType) return Models.FileType.None; } } + + public async Task Handle(ResendSMSRequest request, CancellationToken cancellationToken){ + await this.MessagingDomainService.ResendSMSMessage(request.ConnectionIdentifier, request.MessageId, cancellationToken); + + return Unit.Value; + } } } \ No newline at end of file diff --git a/MessagingService.BusinessLogic/Requests/ResendEmailRequest.cs b/MessagingService.BusinessLogic/Requests/ResendEmailRequest.cs index 8a4b2fa..c67172b 100644 --- a/MessagingService.BusinessLogic/Requests/ResendEmailRequest.cs +++ b/MessagingService.BusinessLogic/Requests/ResendEmailRequest.cs @@ -3,7 +3,7 @@ using System; using MediatR; -public class ResendEmailRequest : IRequest +public class ResendEmailRequest : IRequest { #region Constructors @@ -18,20 +18,8 @@ private ResendEmailRequest(Guid connectionIdentifier, #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 diff --git a/MessagingService.BusinessLogic/Requests/ResendSMSRequest.cs b/MessagingService.BusinessLogic/Requests/ResendSMSRequest.cs new file mode 100644 index 0000000..0ea0a6e --- /dev/null +++ b/MessagingService.BusinessLogic/Requests/ResendSMSRequest.cs @@ -0,0 +1,36 @@ +namespace MessagingService.BusinessLogic.Requests; + +using System; +using MediatR; + +public class ResendSMSRequest : IRequest +{ + #region Constructors + + private ResendSMSRequest(Guid connectionIdentifier, + Guid messageId) + { + this.ConnectionIdentifier = connectionIdentifier; + this.MessageId = messageId; + } + + #endregion + + #region Properties + + public Guid ConnectionIdentifier { get; } + + public Guid MessageId { get; } + + #endregion + + #region Methods + + public static ResendSMSRequest Create(Guid connectionIdentifier, + Guid messageId) + { + return new ResendSMSRequest(connectionIdentifier, messageId); + } + + #endregion +} \ No newline at end of file diff --git a/MessagingService.BusinessLogic/Requests/SendEmailRequest.cs b/MessagingService.BusinessLogic/Requests/SendEmailRequest.cs index e04f268..df1fcbc 100644 --- a/MessagingService.BusinessLogic/Requests/SendEmailRequest.cs +++ b/MessagingService.BusinessLogic/Requests/SendEmailRequest.cs @@ -9,7 +9,7 @@ /// /// /// - public class SendEmailRequest : IRequest + public class SendEmailRequest : IRequest { #region Constructors diff --git a/MessagingService.BusinessLogic/Requests/SendSMSRequest.cs b/MessagingService.BusinessLogic/Requests/SendSMSRequest.cs index fc7b8d2..cceb73b 100644 --- a/MessagingService.BusinessLogic/Requests/SendSMSRequest.cs +++ b/MessagingService.BusinessLogic/Requests/SendSMSRequest.cs @@ -7,7 +7,7 @@ /// /// /// - public class SendSMSRequest : IRequest + public class SendSMSRequest : IRequest { #region Constructors diff --git a/MessagingService.BusinessLogic/Services/IMessagingDomainService.cs b/MessagingService.BusinessLogic/Services/IMessagingDomainService.cs index e494223..1383a92 100644 --- a/MessagingService.BusinessLogic/Services/IMessagingDomainService.cs +++ b/MessagingService.BusinessLogic/Services/IMessagingDomainService.cs @@ -34,6 +34,10 @@ Task ResendEmailMessage(Guid connectionIdentifier, Guid messageId, CancellationToken cancellationToken); + Task ResendSMSMessage(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 1721fd2..ade3e0d 100644 --- a/MessagingService.BusinessLogic/Services/MessagingDomainService.cs +++ b/MessagingService.BusinessLogic/Services/MessagingDomainService.cs @@ -107,7 +107,7 @@ public async Task SendSMSMessage(Guid connectionIdentifier, await this.SmsAggregateRepository.SaveChanges(smsAggregate, cancellationToken); } - public async Task ResendEmailMessage(Guid connectionIdentifier, + public async Task ResendEmailMessage(Guid connectionIdentifier, Guid messageId, CancellationToken cancellationToken) { // Rehydrate Email Message aggregate @@ -125,13 +125,38 @@ await this.EmailServiceProxy.SendEmail(messageId, emailAggregate.FromAddress, emailAggregate.IsHtml, null, cancellationToken); - // response message from provider (record event) - emailAggregate.ReceiveResponseFromProvider(emailResponse.RequestIdentifier, emailResponse.EmailIdentifier); + if (emailResponse.ApiCallSuccessful) + { + // response message from provider (record event) + emailAggregate.ReceiveResponseFromProvider(emailResponse.RequestIdentifier, emailResponse.EmailIdentifier); + } + else + { + emailAggregate.ReceiveBadResponseFromProvider(emailResponse.Error, emailResponse.ErrorCode); + } // Save Changes to persistance await this.EmailAggregateRepository.SaveChanges(emailAggregate, cancellationToken); } + + public async Task ResendSMSMessage(Guid connectionIdentifier, Guid messageId, CancellationToken cancellationToken){ + // Rehydrate SMS Message aggregate + SMSAggregate smsAggregate = await this.SmsAggregateRepository.GetLatestVersion(messageId, cancellationToken); + + // re-send message to provider (record event) + smsAggregate.ResendRequestToProvider(); + + // Make call to SMS provider here + SMSServiceProxyResponse smsResponse = + await this.SmsServiceProxy.SendSMS(messageId, smsAggregate.Sender, smsAggregate.Destination, smsAggregate.Message, cancellationToken); + + // response message from provider (record event) + smsAggregate.ReceiveResponseFromProvider(smsResponse.SMSIdentifier); + + // Save Changes to persistance + await this.SmsAggregateRepository.SaveChanges(smsAggregate, cancellationToken); + } } - #endregion + #endregion } \ No newline at end of file diff --git a/MessagingService.BusinessLogic/Services/SMSServices/IntegrationTest/IntegrationTestSMSServiceProxy.cs b/MessagingService.BusinessLogic/Services/SMSServices/IntegrationTest/IntegrationTestSMSServiceProxy.cs index a84471f..b475933 100644 --- a/MessagingService.BusinessLogic/Services/SMSServices/IntegrationTest/IntegrationTestSMSServiceProxy.cs +++ b/MessagingService.BusinessLogic/Services/SMSServices/IntegrationTest/IntegrationTestSMSServiceProxy.cs @@ -51,7 +51,7 @@ public async Task SendSMS(Guid messageId, { return new SMSServiceProxyResponse { - ApiStatusCode = HttpStatusCode.OK, + ApiCallSuccessful = true, Error = string.Empty, ErrorCode = string.Empty, SMSIdentifier = "smsid" diff --git a/MessagingService.BusinessLogic/Services/SMSServices/SMSServiceProxyResponse.cs b/MessagingService.BusinessLogic/Services/SMSServices/SMSServiceProxyResponse.cs index 25b6951..ddf3062 100644 --- a/MessagingService.BusinessLogic/Services/SMSServices/SMSServiceProxyResponse.cs +++ b/MessagingService.BusinessLogic/Services/SMSServices/SMSServiceProxyResponse.cs @@ -7,36 +7,12 @@ [ExcludeFromCodeCoverage] public class SMSServiceProxyResponse { - /// - /// Gets or sets the API status code. - /// - /// - /// The API status code. - /// - public HttpStatusCode ApiStatusCode { get; set; } + public Boolean ApiCallSuccessful { get; set; } - /// - /// Gets or sets the email identifier. - /// - /// - /// The email identifier. - /// public String SMSIdentifier { get; set; } - /// - /// Gets or sets the error code. - /// - /// - /// The error code. - /// public String ErrorCode { get; set; } - /// - /// Gets or sets the error. - /// - /// - /// The error. - /// public String Error { get; set; } } } \ No newline at end of file diff --git a/MessagingService.BusinessLogic/Services/SMSServices/TheSMSWorks/TheSmsWorksProxy.cs b/MessagingService.BusinessLogic/Services/SMSServices/TheSMSWorks/TheSmsWorksProxy.cs index 40cea61..e30db77 100644 --- a/MessagingService.BusinessLogic/Services/SMSServices/TheSMSWorks/TheSmsWorksProxy.cs +++ b/MessagingService.BusinessLogic/Services/SMSServices/TheSMSWorks/TheSmsWorksProxy.cs @@ -82,7 +82,7 @@ public async Task SendSMS(Guid messageId, response = new SMSServiceProxyResponse { - ApiStatusCode = apiSendSMSMessageHttpResponse.StatusCode, + ApiCallSuccessful = true, SMSIdentifier = apiTheSmsWorksSendSmsResponse.MessageId, }; } diff --git a/MessagingService.DataTransferObjects/ResendSMSRequest.cs b/MessagingService.DataTransferObjects/ResendSMSRequest.cs new file mode 100644 index 0000000..4c16575 --- /dev/null +++ b/MessagingService.DataTransferObjects/ResendSMSRequest.cs @@ -0,0 +1,15 @@ +namespace MessagingService.DataTransferObjects +{ + using System; + using System.Diagnostics.CodeAnalysis; + using Newtonsoft.Json; + + [ExcludeFromCodeCoverage] + public class ResendSMSRequest{ + [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.SMSAggregate.Tests/SMSAggregateTests.cs b/MessagingService.SMSAggregate.Tests/SMSAggregateTests.cs index 7ddb2a4..d2f681c 100644 --- a/MessagingService.SMSAggregate.Tests/SMSAggregateTests.cs +++ b/MessagingService.SMSAggregate.Tests/SMSAggregateTests.cs @@ -12,7 +12,7 @@ namespace MessagingService.SMSAggregate.Tests using Xunit; using MessageStatus = SMSMessageAggregate.MessageStatus; - public class EmailAggregateTests + public class SMSAggregateTests { [Fact] public void SMSAggregate_CanBeCreated_IsCreated() @@ -31,7 +31,7 @@ public void SMSAggregate_SendRequestToProvider_RequestSent() smsAggregate.Sender.ShouldBe(TestData.Sender); smsAggregate.Destination.ShouldBe(TestData.Destination); smsAggregate.Message.ShouldBe(TestData.Message); - smsAggregate.MessageStatus.ShouldBe(MessageStatus.InProgress); + smsAggregate.GetDeliveryStatus().ShouldBe(MessageStatus.InProgress); } [Fact] @@ -56,7 +56,7 @@ public void SMSAggregate_ReceiveResponseFromProvider_ResponseReceived() smsAggregate.ReceiveResponseFromProvider(TestData.ProviderSMSReference); smsAggregate.ProviderReference.ShouldBe(TestData.ProviderSMSReference); - smsAggregate.MessageStatus.ShouldBe(MessageStatus.Sent); + smsAggregate.GetDeliveryStatus().ShouldBe(MessageStatus.Sent); } [Fact] @@ -69,7 +69,7 @@ public void SMSAggregate_MarkMessageAsDelivered_MessageMarkedAsDelivered() smsAggregate.MarkMessageAsDelivered(TestData.ProviderStatusDescription, TestData.DeliveredDateTime); - smsAggregate.MessageStatus.ShouldBe(MessageStatus.Delivered); + smsAggregate.GetDeliveryStatus().ShouldBe(MessageStatus.Delivered); } [Theory] @@ -125,7 +125,7 @@ public void SMSAggregate_MarkMessageAsExpired_MessageMarkedAsExpired() smsAggregate.MarkMessageAsExpired(TestData.ProviderStatusDescription, TestData.DeliveredDateTime); - smsAggregate.MessageStatus.ShouldBe(MessageStatus.Expired); + smsAggregate.GetDeliveryStatus().ShouldBe(MessageStatus.Expired); } [Theory] @@ -181,7 +181,7 @@ public void SMSAggregate_MarkMessageAsUndeliverable_MessageMarkedAsUndeliverable smsAggregate.MarkMessageAsUndeliverable(TestData.ProviderStatusDescription, TestData.DeliveredDateTime); - smsAggregate.MessageStatus.ShouldBe(MessageStatus.Undeliverable); + smsAggregate.GetDeliveryStatus().ShouldBe(MessageStatus.Undeliverable); } [Theory] @@ -237,7 +237,7 @@ public void SMSAggregate_MarkMessageAsRejected_MessageMarkedAsRejected() smsAggregate.MarkMessageAsRejected(TestData.ProviderStatusDescription, TestData.DeliveredDateTime); - smsAggregate.MessageStatus.ShouldBe(MessageStatus.Rejected); + smsAggregate.GetDeliveryStatus().ShouldBe(MessageStatus.Rejected); } [Theory] @@ -284,7 +284,86 @@ public void SMSAggregate_MarkMessageAsRejected_IncorrectState_ErrorThrown(Messag } [Fact] - public void EmailAggregate_PlayEvent_UnsupportedEvent_ErrorThrown() + public void SMSAggregate_ResendRequestToProvider_IsSent_MessageIsResent() + { + SMSAggregate smsAggregate = SMSAggregate.Create(TestData.MessageId); + + smsAggregate.SendRequestToProvider(TestData.Sender, TestData.Destination, TestData.Message); + smsAggregate.ReceiveResponseFromProvider(TestData.ProviderSMSReference); + + smsAggregate.ResendRequestToProvider(); + + smsAggregate.ResendCount.ShouldBe(1); + smsAggregate.GetDeliveryStatus(1).ShouldBe(MessageStatus.InProgress); + } + + + [Fact] + public void SMSAggregate_ResendRequestToProvider_IsDelivered_MessageIsResent() + { + SMSAggregate smsAggregate = SMSAggregate.Create(TestData.MessageId); + + smsAggregate.SendRequestToProvider(TestData.Sender, TestData.Destination, TestData.Message); + smsAggregate.ReceiveResponseFromProvider(TestData.ProviderSMSReference); + smsAggregate.MarkMessageAsDelivered(TestData.ProviderStatusDescription, TestData.DeliveredDateTime); + smsAggregate.ResendRequestToProvider(); + + smsAggregate.ResendCount.ShouldBe(1); + smsAggregate.GetDeliveryStatus(1).ShouldBe(MessageStatus.InProgress); + } + + [Fact] + public void SMSAggregate_ResendRequestToProvider_NotSet_ErrorThrown() + { + SMSAggregate smsAggregate = SMSAggregate.Create(TestData.MessageId); + + Should.Throw(() => smsAggregate.ResendRequestToProvider()); + } + + [Fact] + public void SMSAggregate_ResendRequestToProvider_InProgress_ErrorThrown() + { + SMSAggregate smsAggregate = SMSAggregate.Create(TestData.MessageId); + + smsAggregate.SendRequestToProvider(TestData.Sender, TestData.Destination, TestData.Message); + Should.Throw(() => smsAggregate.ResendRequestToProvider()); + } + + [Fact] + public void SMSAggregate_ResendRequestToProvider_Rejected_ErrorThrown() + { + SMSAggregate smsAggregate = SMSAggregate.Create(TestData.MessageId); + + smsAggregate.SendRequestToProvider(TestData.Sender, TestData.Destination, TestData.Message); + smsAggregate.ReceiveResponseFromProvider(TestData.ProviderSMSReference); + smsAggregate.MarkMessageAsRejected(TestData.ProviderStatusDescription, TestData.RejectedDateTime); + Should.Throw(() => smsAggregate.ResendRequestToProvider()); + } + + [Fact] + public void SMSAggregate_ResendRequestToProvider_Expired_ErrorThrown() + { + SMSAggregate smsAggregate = SMSAggregate.Create(TestData.MessageId); + + smsAggregate.SendRequestToProvider(TestData.Sender, TestData.Destination, TestData.Message); + smsAggregate.ReceiveResponseFromProvider(TestData.ProviderSMSReference); + smsAggregate.MarkMessageAsExpired(TestData.ProviderStatusDescription, TestData.ExpiredDateTime); + Should.Throw(() => smsAggregate.ResendRequestToProvider()); + } + + [Fact] + public void SMSAggregate_ResendRequestToProvider_Undelivered_ErrorThrown() + { + SMSAggregate smsAggregate = SMSAggregate.Create(TestData.MessageId); + + smsAggregate.SendRequestToProvider(TestData.Sender, TestData.Destination, TestData.Message); + smsAggregate.ReceiveResponseFromProvider(TestData.ProviderSMSReference); + smsAggregate.MarkMessageAsUndeliverable(TestData.ProviderStatusDescription, TestData.UndeliveredDateTime); + Should.Throw(() => smsAggregate.ResendRequestToProvider()); + } + + [Fact] + public void SMSAggregate_PlayEvent_UnsupportedEvent_ErrorThrown() { Logger.Initialise(NullLogger.Instance); SMSAggregate smsAggregate = new SMSAggregate(); diff --git a/MessagingService.SMSMessage.DomainEvents/DomainEvents.cs b/MessagingService.SMSMessage.DomainEvents/DomainEvents.cs index e5e775e..5145cf7 100644 --- a/MessagingService.SMSMessage.DomainEvents/DomainEvents.cs +++ b/MessagingService.SMSMessage.DomainEvents/DomainEvents.cs @@ -25,4 +25,6 @@ public record SMSMessageRejectedEvent(Guid MessageId, public record SMSMessageUndeliveredEvent(Guid MessageId, String ProviderStatus, - DateTime UndeliveredDateTime) : DomainEvent(MessageId, Guid.NewGuid()); \ No newline at end of file + DateTime UndeliveredDateTime) : DomainEvent(MessageId, Guid.NewGuid()); + +public record RequestResentToSMSProviderEvent(Guid MessageId) : DomainEvent(MessageId, Guid.NewGuid()); \ No newline at end of file diff --git a/MessagingService.SMSMessageAggregate/SMSAggregate.cs b/MessagingService.SMSMessageAggregate/SMSAggregate.cs index bd4f4df..d4e5eed 100644 --- a/MessagingService.SMSMessageAggregate/SMSAggregate.cs +++ b/MessagingService.SMSMessageAggregate/SMSAggregate.cs @@ -24,6 +24,9 @@ public class SMSAggregate : Aggregate [ExcludeFromCodeCoverage] public SMSAggregate() { + this.DeliveryStatusList = new List { + MessageStatus.NotSet + }; } /// @@ -36,6 +39,9 @@ private SMSAggregate(Guid aggregateId) this.AggregateId = aggregateId; this.MessageId = aggregateId; + this.DeliveryStatusList = new List { + MessageStatus.NotSet + }; } #endregion @@ -82,7 +88,7 @@ private SMSAggregate(Guid aggregateId) /// /// The message status. /// - public MessageStatus MessageStatus { get; private set; } + //public MessageStatus MessageStatus { get; private set; } /// /// Creates the specified aggregate identifier. @@ -117,7 +123,7 @@ public void SendRequestToProvider(String sender, String destination, String message) { - 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"); } @@ -194,9 +200,9 @@ public void MarkMessageAsUndeliverable(String providerStatus, /// Message at status {this.MessageStatus} cannot be set to expired private void CheckMessageCanBeSetToExpired() { - if (this.MessageStatus != MessageStatus.Sent) + if (this.DeliveryStatusList[this.ResendCount] != MessageStatus.Sent) { - throw new InvalidOperationException($"Message at status {this.MessageStatus} cannot be set to expired"); + throw new InvalidOperationException($"Message at status {this.DeliveryStatusList[this.ResendCount]} cannot be set to expired"); } } @@ -206,9 +212,9 @@ private void CheckMessageCanBeSetToExpired() /// 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"); } } /// @@ -217,9 +223,9 @@ private void CheckMessageCanBeSetToRejected() /// 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"); } } @@ -229,9 +235,9 @@ private void CheckMessageCanBeSetToDelivered() /// Message at status {this.MessageStatus} cannot be set to undeliverable private void CheckMessageCanBeSetToUndeliverable() { - if (this.MessageStatus != MessageStatus.Sent) + if (this.DeliveryStatusList[this.ResendCount] != MessageStatus.Sent) { - throw new InvalidOperationException($"Message at status {this.MessageStatus} cannot be set to undeliverable"); + throw new InvalidOperationException($"Message at status {this.DeliveryStatusList[this.ResendCount]} cannot be set to undeliverable"); } } @@ -268,10 +274,10 @@ private void PlayEvent(Object @event) /// The domain event. private void PlayEvent(RequestSentToSMSProviderEvent domainEvent) { - this.MessageStatus = MessageStatus.InProgress; this.Sender = domainEvent.Sender; this.Destination = domainEvent.Destination; this.Message = domainEvent.Message; + this.DeliveryStatusList[this.ResendCount] = MessageStatus.InProgress; } /// @@ -281,7 +287,7 @@ private void PlayEvent(RequestSentToSMSProviderEvent domainEvent) private void PlayEvent(ResponseReceivedFromSMSProviderEvent domainEvent) { this.ProviderReference = domainEvent.ProviderSMSReference; - this.MessageStatus = MessageStatus.Sent; + this.DeliveryStatusList[this.ResendCount] = MessageStatus.Sent; } /// @@ -290,7 +296,7 @@ private void PlayEvent(ResponseReceivedFromSMSProviderEvent domainEvent) /// The domain event. private void PlayEvent(SMSMessageExpiredEvent domainEvent) { - this.MessageStatus = MessageStatus.Expired; + this.DeliveryStatusList[this.ResendCount] = MessageStatus.Expired; } /// @@ -299,7 +305,7 @@ private void PlayEvent(SMSMessageExpiredEvent domainEvent) /// The domain event. private void PlayEvent(SMSMessageDeliveredEvent domainEvent) { - this.MessageStatus = MessageStatus.Delivered; + this.DeliveryStatusList[this.ResendCount] = MessageStatus.Delivered; } /// @@ -308,7 +314,7 @@ private void PlayEvent(SMSMessageDeliveredEvent domainEvent) /// The domain event. private void PlayEvent(SMSMessageRejectedEvent domainEvent) { - this.MessageStatus = MessageStatus.Rejected; + this.DeliveryStatusList[this.ResendCount] = MessageStatus.Rejected; } /// @@ -317,7 +323,38 @@ private void PlayEvent(SMSMessageRejectedEvent domainEvent) /// The domain event. private void PlayEvent(SMSMessageUndeliveredEvent domainEvent) { - this.MessageStatus = MessageStatus.Undeliverable; + this.DeliveryStatusList[this.ResendCount] = MessageStatus.Undeliverable; + } + + public Int32 ResendCount { get; private set; } + private List DeliveryStatusList; + + 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]}]"); + } + + RequestResentToSMSProviderEvent requestResentToSMSProviderEvent = new RequestResentToSMSProviderEvent(this.AggregateId); + + this.ApplyAndAppend(requestResentToSMSProviderEvent); + } + + public MessageStatus GetDeliveryStatus(Int32? resendAttempt = null) + { + if (resendAttempt.HasValue == false) + { + return this.DeliveryStatusList[this.ResendCount]; + } + return this.DeliveryStatusList[resendAttempt.Value]; + } + + private void PlayEvent(RequestResentToSMSProviderEvent domainEvent) + { + this.ResendCount++; + this.DeliveryStatusList.Add(MessageStatus.InProgress); } } } diff --git a/MessagingService.Testing/TestData.cs b/MessagingService.Testing/TestData.cs index 1f0ba2a..041dfeb 100644 --- a/MessagingService.Testing/TestData.cs +++ b/MessagingService.Testing/TestData.cs @@ -244,6 +244,9 @@ public class TestData public static ResendEmailRequest ResendEmailRequest => ResendEmailRequest.Create(TestData.ConnectionIdentifier, TestData.MessageId); + public static ResendSMSRequest ResendSMSRequest => ResendSMSRequest.Create(TestData.ConnectionIdentifier, + TestData.MessageId); + public static SendEmailRequest SendEmailRequestNoAttachments => SendEmailRequest.Create(TestData.ConnectionIdentifier, TestData.MessageId, TestData.FromAddress, @@ -288,7 +291,24 @@ public static EmailAggregate GetSentEmailAggregate() new SMSServiceProxyResponse { SMSIdentifier = TestData.SMSIdentifier, - ApiStatusCode = TestData.ApiStatusCodeSuccess + ApiCallSuccessful = true + }; + + + public static SMSServiceProxyResponse FailedAPICallSMSServiceProxyResponse => + new SMSServiceProxyResponse + { + Error = "NotFound", + ErrorCode = "404", + ApiCallSuccessful = false, + }; + + public static SMSServiceProxyResponse FailedSMSServiceProxyResponse => + new SMSServiceProxyResponse + { + Error = "NotFound", + ErrorCode = "404", + ApiCallSuccessful = true, }; public static SMSAggregate GetEmptySMSAggregate() diff --git a/MessagingService/Bootstrapper/MediatorRegistry.cs b/MessagingService/Bootstrapper/MediatorRegistry.cs index 59a8e7e..d7cf363 100644 --- a/MessagingService/Bootstrapper/MediatorRegistry.cs +++ b/MessagingService/Bootstrapper/MediatorRegistry.cs @@ -15,9 +15,10 @@ public class MediatorRegistry : ServiceRegistry public MediatorRegistry() { this.AddTransient(); - this.AddSingleton, MessagingRequestHandler>(); - this.AddSingleton, MessagingRequestHandler>(); - this.AddSingleton, MessagingRequestHandler>(); + this.AddSingleton, MessagingRequestHandler>(); + this.AddSingleton, MessagingRequestHandler>(); + this.AddSingleton, MessagingRequestHandler>(); + this.AddSingleton, MessagingRequestHandler>(); this.AddSingleton>(container => (serviceName) => { diff --git a/MessagingService/Controllers/SMSController.cs b/MessagingService/Controllers/SMSController.cs index 0014f8c..4ba3486 100644 --- a/MessagingService/Controllers/SMSController.cs +++ b/MessagingService/Controllers/SMSController.cs @@ -82,6 +82,30 @@ public async Task SendSMS([FromBody] SendSMSRequest sendSMSReques }); } + [HttpPost] + [Route("resend")] + [SwaggerResponse(202, "Accepted", typeof(SendSMSResponse))] + [SwaggerResponseExample(202, typeof(SendSMSResponseExample))] + public async Task ResendSMS([FromBody] ResendSMSRequest resendSMSRequest, + CancellationToken cancellationToken) + { + // Reject password tokens + if (ClaimsHelper.IsPasswordToken(this.User)) + { + return this.Forbid(); + } + + // Create the command + BusinessLogic.Requests.ResendSMSRequest request = BusinessLogic.Requests.ResendSMSRequest.Create(resendSMSRequest.ConnectionIdentifier, + resendSMSRequest.MessageId); + + // Route the command + await this.Mediator.Send(request, cancellationToken); + + // return the result + return this.Accepted($"{SMSController.ControllerRoute}/{resendSMSRequest.MessageId}"); + } + #endregion #region Others