diff --git a/AllReady.Processing/AllReady.Processing.Tests/AllReady.Processing.Tests.csproj b/AllReady.Processing/AllReady.Processing.Tests/AllReady.Processing.Tests.csproj new file mode 100644 index 0000000..b9525ef --- /dev/null +++ b/AllReady.Processing/AllReady.Processing.Tests/AllReady.Processing.Tests.csproj @@ -0,0 +1,43 @@ + + + + net461 + + false + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AllReady.Processing/AllReady.Processing.Tests/ProcessEmailQueueMessageTests.cs b/AllReady.Processing/AllReady.Processing.Tests/ProcessEmailQueueMessageTests.cs new file mode 100644 index 0000000..8b17f24 --- /dev/null +++ b/AllReady.Processing/AllReady.Processing.Tests/ProcessEmailQueueMessageTests.cs @@ -0,0 +1,120 @@ +using Moq; +using Xunit; +using Shouldly; +using Microsoft.Azure.WebJobs.Host; +using SendGrid.Helpers.Mail; +using System.Collections.Generic; +using System.Diagnostics; +using System; + +namespace AllReady.Processing.Tests +{ + public class ProcessEmailQueueMessageTests + { + // comes from Environment Variable + const string EmailSentFrom = "from@tests.com"; + const string FromEnvironmentVariable = "Authentication:SendGrid:FromEmail"; + + [Fact] + public void ShouldOutputMailWithPlainTextOnly() + { + // Arrange + var loggerMock = new MockTrace(); + var messageInQueue = MessageInQueue("Subject test", "test@testing.com", "Message text plain"); + Environment.SetEnvironmentVariable(FromEnvironmentVariable, EmailSentFrom); + // Act + ProcessEmailQueueMessage.Run(messageInQueue, out Mail message, loggerMock); + // Assert + message.From.Name.ShouldBe("AllReady"); + message.From.Address.ShouldBe(EmailSentFrom); + message.Subject.ShouldBe("Subject test"); + message.Contents[0].Type.ShouldBe("text/plain"); + message.Contents[0].Value.ShouldBe("Message text plain"); + message.Personalization[0].Tos[0].Address.ShouldBe("test@testing.com"); + } + + [Fact] + public void ShouldOutputMailWithHtmlTextOnly() + { + // Arrange + var loggerMock = new MockTrace(); + var messageInQueue = MessageInQueue("Subject test", "test@testing.com", null, "text"); + //Environment.SetEnvironmentVariable(FromEnvironmentVariable, EmailSentFrom); + // Act + ProcessEmailQueueMessage.Run(messageInQueue, out Mail message, loggerMock); + // Assert + message.From.Name.ShouldBe("AllReady"); + message.From.Address.ShouldBe(EmailSentFrom); + message.Subject.ShouldBe("Subject test"); + message.Contents[0].Type.ShouldBe("text/html"); + message.Contents[0].Value.ShouldBe("text"); + message.Personalization[0].Tos[0].Address.ShouldBe("test@testing.com"); + } + + [Fact] + public void ShouldTraceTheSubjectAndRecepient() + { + // Arrange + var loggerMock = new MockTrace(); + var messageInQueue = MessageInQueue("Subject test", "test@testing.com", null, "text"); + Environment.SetEnvironmentVariable(FromEnvironmentVariable, EmailSentFrom); + // Act + ProcessEmailQueueMessage.Run(messageInQueue, out Mail message, loggerMock); + // Assert + loggerMock.Events.Count.ShouldBe(1); + loggerMock.Events[0].Level.ShouldBe(TraceLevel.Info); + loggerMock.Events[0].Message.ShouldBe("Sending email with subject `Subject test` to `test@testing.com`"); + } + + [Fact] + public void ShouldThrowOnDeserialization() + { + // Arrange + var loggerMock = new MockTrace(); + Environment.SetEnvironmentVariable(FromEnvironmentVariable, EmailSentFrom); + // Act + // Assert + Should.Throw(() => ProcessEmailQueueMessage.Run("invalid message", out Mail message, loggerMock)); + } + + [Fact] + public void ShouldThrowForMissing_From() + { + // Arrange + var loggerMock = new MockTrace(); + Environment.SetEnvironmentVariable(FromEnvironmentVariable, null); + var messageInQueue = MessageInQueue("Subject test", "test@testing.com", null, "text"); + + // Act + // Assert + Should.Throw(() => ProcessEmailQueueMessage.Run(messageInQueue, out Mail message, loggerMock)); + } + + public class MockTrace : TraceWriter + { + public List Events = new List(); + + public MockTrace() : base(TraceLevel.Verbose) + { + } + + public override void Trace(TraceEvent traceEvent) + { + this.Events.Add(traceEvent); + } + } + + private string MessageInQueue(string subject, string recipient, string message = null, string htmlMessage = null) + { + return $"{{" + + $"\"Recipient\":\"{recipient}\"," + + $"\"Subject\":\"{subject}\"," + + (message != null ? "\"Message\":\"" + message + "\"" : "") + + (htmlMessage!= null ? "\"HtmlMessage\":\"" + htmlMessage + "\"" : "") + + $"}}"; + } + + } +} + + diff --git a/AllReady.Processing/AllReady.Processing.sln b/AllReady.Processing/AllReady.Processing.sln index b664a56..63b94f6 100644 --- a/AllReady.Processing/AllReady.Processing.sln +++ b/AllReady.Processing/AllReady.Processing.sln @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27130.2027 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AllReady.Processing", "AllReady.Processing\AllReady.Processing.csproj", "{C619B518-08A3-4CFC-BC22-CB0B4A63CB31}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AllReady.Processing", "AllReady.Processing\AllReady.Processing.csproj", "{C619B518-08A3-4CFC-BC22-CB0B4A63CB31}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AllReady.Processing.Tests", "AllReady.Processing.Tests\AllReady.Processing.Tests.csproj", "{1FDEA4EB-E405-44B3-81B1-05F2B5AEB034}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +17,10 @@ Global {C619B518-08A3-4CFC-BC22-CB0B4A63CB31}.Debug|Any CPU.Build.0 = Debug|Any CPU {C619B518-08A3-4CFC-BC22-CB0B4A63CB31}.Release|Any CPU.ActiveCfg = Release|Any CPU {C619B518-08A3-4CFC-BC22-CB0B4A63CB31}.Release|Any CPU.Build.0 = Release|Any CPU + {1FDEA4EB-E405-44B3-81B1-05F2B5AEB034}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FDEA4EB-E405-44B3-81B1-05F2B5AEB034}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FDEA4EB-E405-44B3-81B1-05F2B5AEB034}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FDEA4EB-E405-44B3-81B1-05F2B5AEB034}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AllReady.Processing/AllReady.Processing/AllReady.Processing.csproj b/AllReady.Processing/AllReady.Processing/AllReady.Processing.csproj index 0715787..0cc3591 100644 --- a/AllReady.Processing/AllReady.Processing/AllReady.Processing.csproj +++ b/AllReady.Processing/AllReady.Processing/AllReady.Processing.csproj @@ -3,7 +3,9 @@ net461 + + diff --git a/AllReady.Processing/AllReady.Processing/ProcessEmailQueueMessage.cs b/AllReady.Processing/AllReady.Processing/ProcessEmailQueueMessage.cs new file mode 100644 index 0000000..7748dc4 --- /dev/null +++ b/AllReady.Processing/AllReady.Processing/ProcessEmailQueueMessage.cs @@ -0,0 +1,64 @@ +using System; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Host; +using Newtonsoft.Json; +using SendGrid.Helpers.Mail; + +namespace AllReady.Processing +{ + public static class ProcessEmailQueueMessage + { + [FunctionName("ProcessEmailQueueMessage")] + [StorageAccount("AzureWebJobsStorage")] + public static void Run([QueueTrigger("email-pending-deliveries")] string queueItem, + [SendGrid(ApiKey = "AzureWebJobsSendGridApiKey")] out Mail message, TraceWriter log) + { + var queuedEmailMessage = JsonConvert.DeserializeObject(queueItem); + + var from = GuardAgainstInvalidEmailAddress( + Environment.GetEnvironmentVariable("Authentication:SendGrid:FromEmail")); + + log.Info($"Sending email with subject `{queuedEmailMessage.Subject}` to `{queuedEmailMessage.Recipient}`"); + + message = new Mail + { + From = new Email(from, "AllReady"), + Subject = queuedEmailMessage.Subject + }; + + if (queuedEmailMessage.Message != null) + { + message.AddContent(new Content + { + Type = "text/plain", + Value = queuedEmailMessage.Message + }); + } + + if (queuedEmailMessage.HtmlMessage != null) + { + message.AddContent(new Content + { + Type = "text/html", + Value = queuedEmailMessage.HtmlMessage + }); + } + + var personalization = new Personalization(); + personalization.AddTo(new Email(queuedEmailMessage.Recipient)); + message.AddPersonalization(personalization); + } + + private static string GuardAgainstInvalidEmailAddress(string @from) + { + if (string.IsNullOrWhiteSpace(@from)) + { + throw new InvalidOperationException( + "Environment variable `Authentication:SendGrid:FromEmail` is missing or contains invalid entry."); + } + + return @from; + } + } +} + diff --git a/AllReady.Processing/AllReady.Processing/QueueTest.cs b/AllReady.Processing/AllReady.Processing/QueueTest.cs index dac8cde..f47a142 100644 --- a/AllReady.Processing/AllReady.Processing/QueueTest.cs +++ b/AllReady.Processing/AllReady.Processing/QueueTest.cs @@ -9,10 +9,10 @@ public static class QueueTest // This function can be used locally to test interaction with your // local storage emulator. - [FunctionName("QueueTest")] - public static void Run([QueueTrigger("queue-test", Connection = "")]string item, TraceWriter log) - { - log.Info($"A message was dequeued from the queue-test queue: {item}"); - } + //[FunctionName("QueueTest")] + //public static void Run([QueueTrigger("queue-test")]string item, TraceWriter log) + //{ + // log.Info($"A message was dequeued from the queue-test queue: {item}"); + //} } } diff --git a/AllReady.Processing/AllReady.Processing/QueuedEmailMessage.cs b/AllReady.Processing/AllReady.Processing/QueuedEmailMessage.cs new file mode 100644 index 0000000..6ff09f7 --- /dev/null +++ b/AllReady.Processing/AllReady.Processing/QueuedEmailMessage.cs @@ -0,0 +1,15 @@ +namespace AllReady.Processing +{ + + public class QueuedEmailMessage + { + + public string Recipient { get; set; } + + public string Message { get; set; } + + public string HtmlMessage { get; set; } + + public string Subject { get; set; } + } +}