From d111fd317b93111bf0bfcce80f6a5331e558d4ee Mon Sep 17 00:00:00 2001 From: "n.bitounis" Date: Mon, 1 Jun 2020 15:08:20 +0300 Subject: [PATCH] * Added delivery events and UI. * Various renames and fixes. --- .../Factories/DbSesDeliveryEventFactory.cs | 28 +++++++ .../Factories/DbSesOpenEventFactory.cs | 30 ++++++++ .../Factories/DbSesOpenFactory.cs | 29 -------- ...endFactory.cs => DbSesSendEventFactory.cs} | 5 +- .../Models/SesDeliveryEvent.cs | 10 +++ .../Models/SesDeliveryEventModel.cs | 8 ++ .../Models/{SesOpen.cs => SesOpenEvent.cs} | 2 +- .../{SesSendModel.cs => SesOpenEventModel.cs} | 4 +- .../Models/{SesSend.cs => SesSendEvent.cs} | 2 +- .../{SesOpenModel.cs => SesSendEventModel.cs} | 4 +- .../Pages/FindDeliveryEvents.cshtml | 74 +++++++++++++++++++ .../Pages/FindDeliveryEvents.cshtml.cs | 44 +++++++++++ ...FindOpens.cshtml => FindOpenEvents.cshtml} | 8 +- ...ens.cshtml.cs => FindOpenEvents.cshtml.cs} | 8 +- ...FindSends.cshtml => FindSendEvents.cshtml} | 8 +- ...nds.cshtml.cs => FindSendEvents.cshtml.cs} | 8 +- .../Pages/Shared/_Layout.cshtml | 7 +- .../Services/Interfaces/ISearchService.cs | 5 +- .../Services/NotificationService.cs | 28 +++++-- .../Services/SearchService.cs | 14 +++- .../Entities/SesDeliveryEvent.cs | 12 +++ .../Mappings/SesDeliveryEventMap.cs | 17 +++++ .../Mappings/SesOpenEventMap.cs | 2 +- .../Mappings/SesSendEventMap.cs | 2 +- .../ISesDeliveryEventsRepository.cs | 16 ++++ .../SesDeliveryEventsRepository.cs | 64 ++++++++++++++++ .../StartupExtensions.cs | 5 +- Sql/ses_notifications_init.sql | 25 +++++++ .../DbSesDeliveryEventFactoryTests.cs | 32 ++++++++ .../Factories/DbSesDeliveryFactoryTests.cs | 1 - ...Tests.cs => DbSesOpenEventFactoryTests.cs} | 12 ++- .../Factories/DbSesSendEventFactoryTests.cs | 29 ++++++++ .../Helpers/TestHelpers.cs | 49 +++++++++++- .../Services/NotificationServiceTests.cs | 46 ++++++++---- .../Services/SearchServiceTests.cs | 15 ++-- 35 files changed, 558 insertions(+), 95 deletions(-) create mode 100644 Projects/SesNotifications.App/Factories/DbSesDeliveryEventFactory.cs create mode 100644 Projects/SesNotifications.App/Factories/DbSesOpenEventFactory.cs delete mode 100644 Projects/SesNotifications.App/Factories/DbSesOpenFactory.cs rename Projects/SesNotifications.App/Factories/{DbSesSendFactory.cs => DbSesSendEventFactory.cs} (78%) create mode 100644 Projects/SesNotifications.App/Models/SesDeliveryEvent.cs create mode 100644 Projects/SesNotifications.App/Models/SesDeliveryEventModel.cs rename Projects/SesNotifications.App/Models/{SesOpen.cs => SesOpenEvent.cs} (87%) rename Projects/SesNotifications.App/Models/{SesSendModel.cs => SesOpenEventModel.cs} (52%) rename Projects/SesNotifications.App/Models/{SesSend.cs => SesSendEvent.cs} (65%) rename Projects/SesNotifications.App/Models/{SesOpenModel.cs => SesSendEventModel.cs} (52%) create mode 100644 Projects/SesNotifications.App/Pages/FindDeliveryEvents.cshtml create mode 100644 Projects/SesNotifications.App/Pages/FindDeliveryEvents.cshtml.cs rename Projects/SesNotifications.App/Pages/{FindOpens.cshtml => FindOpenEvents.cshtml} (92%) rename Projects/SesNotifications.App/Pages/{FindOpens.cshtml.cs => FindOpenEvents.cshtml.cs} (75%) rename Projects/SesNotifications.App/Pages/{FindSends.cshtml => FindSendEvents.cshtml} (91%) rename Projects/SesNotifications.App/Pages/{FindSends.cshtml.cs => FindSendEvents.cshtml.cs} (75%) create mode 100644 Projects/SesNotifications.DataAccess/Entities/SesDeliveryEvent.cs create mode 100644 Projects/SesNotifications.DataAccess/Mappings/SesDeliveryEventMap.cs create mode 100644 Projects/SesNotifications.DataAccess/Repositories/Interfaces/ISesDeliveryEventsRepository.cs create mode 100644 Projects/SesNotifications.DataAccess/Repositories/SesDeliveryEventsRepository.cs create mode 100644 Tests/SesNotifications.App.Tests/Factories/DbSesDeliveryEventFactoryTests.cs rename Tests/SesNotifications.App.Tests/Factories/{DbSesOpenFactoryTests.cs => DbSesOpenEventFactoryTests.cs} (58%) create mode 100644 Tests/SesNotifications.App.Tests/Factories/DbSesSendEventFactoryTests.cs diff --git a/Projects/SesNotifications.App/Factories/DbSesDeliveryEventFactory.cs b/Projects/SesNotifications.App/Factories/DbSesDeliveryEventFactory.cs new file mode 100644 index 0000000..c9021f1 --- /dev/null +++ b/Projects/SesNotifications.App/Factories/DbSesDeliveryEventFactory.cs @@ -0,0 +1,28 @@ +using System; +using SesNotifications.App.Models; +using SesDeliveryEvent = SesNotifications.DataAccess.Entities.SesDeliveryEvent; + +namespace SesNotifications.App.Factories +{ + public static class DbSesDeliveryEventFactory + { + public static SesDeliveryEvent Create(this SesDeliveryEventModel delivery, long notificationId) + { + return new SesDeliveryEvent + { + NotificationId = notificationId, + NotificationType = "Delivery", + SentAt = Convert.ToDateTime(delivery.Mail.Timestamp), + MessageId = delivery.Mail.MessageId, + Source = delivery.Mail.Source, + SourceArn = delivery.Mail.SourceArn, + SourceIp = delivery.Mail.SourceIp, + SendingAccountId = delivery.Mail.SendingAccountId, + DeliveredAt = Convert.ToDateTime(delivery.Delivery.Timestamp), + SmtpResponse = delivery.Delivery.SmtpResponse, + ReportingMta = delivery.Delivery.ReportingMta, + Recipients = string.Join(',', delivery.Delivery.Recipients) + }; + } + } +} \ No newline at end of file diff --git a/Projects/SesNotifications.App/Factories/DbSesOpenEventFactory.cs b/Projects/SesNotifications.App/Factories/DbSesOpenEventFactory.cs new file mode 100644 index 0000000..c973526 --- /dev/null +++ b/Projects/SesNotifications.App/Factories/DbSesOpenEventFactory.cs @@ -0,0 +1,30 @@ +using System; +using SesNotifications.App.Models; +using SesNotifications.DataAccess.Entities; +using SesOpenEvent = SesNotifications.DataAccess.Entities.SesOpenEvent; + +namespace SesNotifications.App.Factories +{ + public static class DbSesOpenEventFactory + { + public static SesOpenEvent Create(this SesOpenEventModel openEvent, long notificationId) + { + return new SesOpenEvent + { + Id = notificationId, + NotificationId = notificationId, + NotificationType = "Open", + SentAt = Convert.ToDateTime(openEvent.Mail.Timestamp), + MessageId = openEvent.Mail.MessageId, + Source = openEvent.Mail.Source, + SourceArn = openEvent.Mail.SourceArn, + SourceIp = openEvent.Mail.SourceIp, + SendingAccountId = openEvent.Mail.SendingAccountId, + Recipients = string.Join(',', openEvent.Mail.Destination), + OpenedAt = Convert.ToDateTime(openEvent.Open.Timestamp), + UserAgent = openEvent.Open.UserAgent, + IpAddress = openEvent.Open.IpAddress + }; + } + } +} \ No newline at end of file diff --git a/Projects/SesNotifications.App/Factories/DbSesOpenFactory.cs b/Projects/SesNotifications.App/Factories/DbSesOpenFactory.cs deleted file mode 100644 index 1f64381..0000000 --- a/Projects/SesNotifications.App/Factories/DbSesOpenFactory.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using SesNotifications.App.Models; -using SesNotifications.DataAccess.Entities; - -namespace SesNotifications.App.Factories -{ - public static class DbSesOpenFactory - { - public static SesOpenEvent Create(this SesOpenModel open, long notificationId) - { - return new SesOpenEvent - { - Id = notificationId, - NotificationId = notificationId, - NotificationType = "Open", - SentAt = Convert.ToDateTime(open.Mail.Timestamp), - MessageId = open.Mail.MessageId, - Source = open.Mail.Source, - SourceArn = open.Mail.SourceArn, - SourceIp = open.Mail.SourceIp, - SendingAccountId = open.Mail.SendingAccountId, - Recipients = string.Join(',', open.Mail.Destination), - OpenedAt = Convert.ToDateTime(open.Open.Timestamp), - UserAgent = open.Open.UserAgent, - IpAddress = open.Open.IpAddress - }; - } - } -} \ No newline at end of file diff --git a/Projects/SesNotifications.App/Factories/DbSesSendFactory.cs b/Projects/SesNotifications.App/Factories/DbSesSendEventFactory.cs similarity index 78% rename from Projects/SesNotifications.App/Factories/DbSesSendFactory.cs rename to Projects/SesNotifications.App/Factories/DbSesSendEventFactory.cs index 698c909..48ed6a6 100644 --- a/Projects/SesNotifications.App/Factories/DbSesSendFactory.cs +++ b/Projects/SesNotifications.App/Factories/DbSesSendEventFactory.cs @@ -1,12 +1,13 @@ using System; using SesNotifications.App.Models; using SesNotifications.DataAccess.Entities; +using SesSendEvent = SesNotifications.DataAccess.Entities.SesSendEvent; namespace SesNotifications.App.Factories { - public static class DbSesSendFactory + public static class DbSesSendEventFactory { - public static SesSendEvent Create(this SesSendModel open, long notificationId) + public static SesSendEvent Create(this SesSendEventModel open, long notificationId) { return new SesSendEvent { diff --git a/Projects/SesNotifications.App/Models/SesDeliveryEvent.cs b/Projects/SesNotifications.App/Models/SesDeliveryEvent.cs new file mode 100644 index 0000000..90b437b --- /dev/null +++ b/Projects/SesNotifications.App/Models/SesDeliveryEvent.cs @@ -0,0 +1,10 @@ +namespace SesNotifications.App.Models +{ + public class SesDeliveryEvent + { + public virtual string Timestamp { get; set; } + public virtual string[] Recipients { get; set; } + public virtual string SmtpResponse { get; set; } + public virtual string ReportingMta { get; set; } + } +} \ No newline at end of file diff --git a/Projects/SesNotifications.App/Models/SesDeliveryEventModel.cs b/Projects/SesNotifications.App/Models/SesDeliveryEventModel.cs new file mode 100644 index 0000000..fd77502 --- /dev/null +++ b/Projects/SesNotifications.App/Models/SesDeliveryEventModel.cs @@ -0,0 +1,8 @@ +namespace SesNotifications.App.Models +{ + public class SesDeliveryEventModel : Ses + { + public virtual SesMail Mail { get; set; } + public virtual SesDeliveryEvent Delivery { get; set; } + } +} diff --git a/Projects/SesNotifications.App/Models/SesOpen.cs b/Projects/SesNotifications.App/Models/SesOpenEvent.cs similarity index 87% rename from Projects/SesNotifications.App/Models/SesOpen.cs rename to Projects/SesNotifications.App/Models/SesOpenEvent.cs index 94e290a..fabcbe7 100644 --- a/Projects/SesNotifications.App/Models/SesOpen.cs +++ b/Projects/SesNotifications.App/Models/SesOpenEvent.cs @@ -1,6 +1,6 @@ namespace SesNotifications.App.Models { - public class SesOpen + public class SesOpenEvent { public virtual string Timestamp { get; set; } public virtual string UserAgent { get; set; } diff --git a/Projects/SesNotifications.App/Models/SesSendModel.cs b/Projects/SesNotifications.App/Models/SesOpenEventModel.cs similarity index 52% rename from Projects/SesNotifications.App/Models/SesSendModel.cs rename to Projects/SesNotifications.App/Models/SesOpenEventModel.cs index e813b87..2587d6e 100644 --- a/Projects/SesNotifications.App/Models/SesSendModel.cs +++ b/Projects/SesNotifications.App/Models/SesOpenEventModel.cs @@ -1,8 +1,8 @@ namespace SesNotifications.App.Models { - public class SesSendModel : Ses + public class SesOpenEventModel : Ses { public virtual SesMail Mail { get; set; } - public virtual SesSend Send { get; set; } + public virtual SesOpenEvent Open { get; set; } } } \ No newline at end of file diff --git a/Projects/SesNotifications.App/Models/SesSend.cs b/Projects/SesNotifications.App/Models/SesSendEvent.cs similarity index 65% rename from Projects/SesNotifications.App/Models/SesSend.cs rename to Projects/SesNotifications.App/Models/SesSendEvent.cs index 77818b3..0287fdb 100644 --- a/Projects/SesNotifications.App/Models/SesSend.cs +++ b/Projects/SesNotifications.App/Models/SesSendEvent.cs @@ -1,6 +1,6 @@ namespace SesNotifications.App.Models { - public class SesSend + public class SesSendEvent { } } \ No newline at end of file diff --git a/Projects/SesNotifications.App/Models/SesOpenModel.cs b/Projects/SesNotifications.App/Models/SesSendEventModel.cs similarity index 52% rename from Projects/SesNotifications.App/Models/SesOpenModel.cs rename to Projects/SesNotifications.App/Models/SesSendEventModel.cs index 12efc55..f846955 100644 --- a/Projects/SesNotifications.App/Models/SesOpenModel.cs +++ b/Projects/SesNotifications.App/Models/SesSendEventModel.cs @@ -1,8 +1,8 @@ namespace SesNotifications.App.Models { - public class SesOpenModel : Ses + public class SesSendEventModel : Ses { public virtual SesMail Mail { get; set; } - public virtual SesOpen Open { get; set; } + public virtual SesSendEvent Send { get; set; } } } \ No newline at end of file diff --git a/Projects/SesNotifications.App/Pages/FindDeliveryEvents.cshtml b/Projects/SesNotifications.App/Pages/FindDeliveryEvents.cshtml new file mode 100644 index 0000000..e7883ce --- /dev/null +++ b/Projects/SesNotifications.App/Pages/FindDeliveryEvents.cshtml @@ -0,0 +1,74 @@ +@page +@model SesNotifications.App.Pages.FindDeliveryEventsModel +@{ + ViewData["Title"] = "Find delivery events"; +} + + + +

@ViewData["Title"]

+

Search for delivery events by date range and optional recipient.

+
+
+
+
+
+
+ + + + + + + + + + + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + @foreach (var record in Model.DeliveryEvents) + { + + + + + + + + + + + + + + + + + + } +
IDNotification IDNotification TypeSent atMessage IDSourceRecipientsSource ARNSource IPSending Account IDDelivered AtSMTP ResponseReporting MTARaw modelRaw message
@record.Id@record.NotificationId@record.NotificationType@record.SentAt.ToString("yyyy-MM-dd HH:mm:ssZ")@record.MessageId@record.Source@record.Recipients@record.SourceArn@record.SourceIp@record.SendingAccountId@record.DeliveredAt.ToString("yyyy-MM-dd HH:mm:ssZ")@record.SmtpResponse@record.ReportingMtaRAW (MODEL)RAW (MESSAGE)
+
+
\ No newline at end of file diff --git a/Projects/SesNotifications.App/Pages/FindDeliveryEvents.cshtml.cs b/Projects/SesNotifications.App/Pages/FindDeliveryEvents.cshtml.cs new file mode 100644 index 0000000..22aabc4 --- /dev/null +++ b/Projects/SesNotifications.App/Pages/FindDeliveryEvents.cshtml.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using SesNotifications.App.Services.Interfaces; +using SesNotifications.DataAccess.Entities; + +namespace SesNotifications.App.Pages +{ + public class FindDeliveryEventsModel : PageModel + { + [BindProperty] + public InputModel Input { get; set; } + + public IList DeliveryEvents { get; set; } = new List(); + + public class InputModel + { + [Required] + [DataType(DataType.Date)] + public DateTime Start { get; set; } + + [Required] + [DataType(DataType.Date)] + public DateTime End { get; set; } + + public string Email { get; set; } + } + + private readonly ISearchService _searchService; + + public FindDeliveryEventsModel(ISearchService searchService) + { + _searchService = searchService; + } + + public IActionResult OnPost() + { + DeliveryEvents = _searchService.FindDeliveryEvents(Input.Email, Input.Start, Input.End); + return Page(); + } + } +} \ No newline at end of file diff --git a/Projects/SesNotifications.App/Pages/FindOpens.cshtml b/Projects/SesNotifications.App/Pages/FindOpenEvents.cshtml similarity index 92% rename from Projects/SesNotifications.App/Pages/FindOpens.cshtml rename to Projects/SesNotifications.App/Pages/FindOpenEvents.cshtml index b6031c2..82482cd 100644 --- a/Projects/SesNotifications.App/Pages/FindOpens.cshtml +++ b/Projects/SesNotifications.App/Pages/FindOpenEvents.cshtml @@ -1,13 +1,13 @@ @page -@model SesNotifications.App.Pages.FindOpensModel +@model SesNotifications.App.Pages.FindOpenEventsModel @{ - ViewData["Title"] = "Find opens"; + ViewData["Title"] = "Find open events"; }

@ViewData["Title"]

-

Search for opens by date range and optional recipient.

+

Search for open events by date range and optional recipient.


@@ -49,7 +49,7 @@ User Agent IP Address - @foreach (var record in Model.Opens) + @foreach (var record in Model.OpenEvents) { @record.Id diff --git a/Projects/SesNotifications.App/Pages/FindOpens.cshtml.cs b/Projects/SesNotifications.App/Pages/FindOpenEvents.cshtml.cs similarity index 75% rename from Projects/SesNotifications.App/Pages/FindOpens.cshtml.cs rename to Projects/SesNotifications.App/Pages/FindOpenEvents.cshtml.cs index 82e337c..8535d34 100644 --- a/Projects/SesNotifications.App/Pages/FindOpens.cshtml.cs +++ b/Projects/SesNotifications.App/Pages/FindOpenEvents.cshtml.cs @@ -8,12 +8,12 @@ namespace SesNotifications.App.Pages { - public class FindOpensModel : PageModel + public class FindOpenEventsModel : PageModel { [BindProperty] public InputModel Input { get; set; } - public IList Opens { get; set; } = new List(); + public IList OpenEvents { get; set; } = new List(); public class InputModel { @@ -30,14 +30,14 @@ public class InputModel private readonly ISearchService _searchService; - public FindOpensModel(ISearchService searchService) + public FindOpenEventsModel(ISearchService searchService) { _searchService = searchService; } public IActionResult OnPost() { - Opens = _searchService.FindOpens(Input.Email, Input.Start, Input.End); + OpenEvents = _searchService.FindOpenEvents(Input.Email, Input.Start, Input.End); return Page(); } } diff --git a/Projects/SesNotifications.App/Pages/FindSends.cshtml b/Projects/SesNotifications.App/Pages/FindSendEvents.cshtml similarity index 91% rename from Projects/SesNotifications.App/Pages/FindSends.cshtml rename to Projects/SesNotifications.App/Pages/FindSendEvents.cshtml index 45e23d1..1145e33 100644 --- a/Projects/SesNotifications.App/Pages/FindSends.cshtml +++ b/Projects/SesNotifications.App/Pages/FindSendEvents.cshtml @@ -1,13 +1,13 @@ @page -@model SesNotifications.App.Pages.FindSendsModel +@model SesNotifications.App.Pages.FindSendEventsModel @{ - ViewData["Title"] = "Find sends"; + ViewData["Title"] = "Find send events"; }

@ViewData["Title"]

-

Search for sends by date range and optional recipient.

+

Search for send events by date range and optional recipient.


@@ -46,7 +46,7 @@ FeedbackId Reporting MTA - @foreach (var record in Model.Sends) + @foreach (var record in Model.SendEvents) { @record.Id diff --git a/Projects/SesNotifications.App/Pages/FindSends.cshtml.cs b/Projects/SesNotifications.App/Pages/FindSendEvents.cshtml.cs similarity index 75% rename from Projects/SesNotifications.App/Pages/FindSends.cshtml.cs rename to Projects/SesNotifications.App/Pages/FindSendEvents.cshtml.cs index 95b367e..9395073 100644 --- a/Projects/SesNotifications.App/Pages/FindSends.cshtml.cs +++ b/Projects/SesNotifications.App/Pages/FindSendEvents.cshtml.cs @@ -8,12 +8,12 @@ namespace SesNotifications.App.Pages { - public class FindSendsModel : PageModel + public class FindSendEventsModel : PageModel { [BindProperty] public InputModel Input { get; set; } - public IList Sends { get; set; } = new List(); + public IList SendEvents { get; set; } = new List(); public class InputModel { @@ -30,14 +30,14 @@ public class InputModel private readonly ISearchService _searchService; - public FindSendsModel(ISearchService searchService) + public FindSendEventsModel(ISearchService searchService) { _searchService = searchService; } public IActionResult OnPost() { - Sends = _searchService.FindSends(Input.Email, Input.Start, Input.End); + SendEvents = _searchService.FindSendEvents(Input.Email, Input.Start, Input.End); return Page(); } } diff --git a/Projects/SesNotifications.App/Pages/Shared/_Layout.cshtml b/Projects/SesNotifications.App/Pages/Shared/_Layout.cshtml index eb72579..7925c53 100644 --- a/Projects/SesNotifications.App/Pages/Shared/_Layout.cshtml +++ b/Projects/SesNotifications.App/Pages/Shared/_Layout.cshtml @@ -28,9 +28,10 @@ diff --git a/Projects/SesNotifications.App/Services/Interfaces/ISearchService.cs b/Projects/SesNotifications.App/Services/Interfaces/ISearchService.cs index 72ca377..a910578 100644 --- a/Projects/SesNotifications.App/Services/Interfaces/ISearchService.cs +++ b/Projects/SesNotifications.App/Services/Interfaces/ISearchService.cs @@ -11,8 +11,9 @@ public interface ISearchService IList FindDeliveries(string email, DateTime start, DateTime end); IList FindComplaints(string email, DateTime start, DateTime end); IList FindBounces(string email, DateTime start, DateTime end); - IList FindOpens(string email, DateTime start, DateTime end); - IList FindSends(string email, DateTime start, DateTime end); + IList FindOpenEvents(string email, DateTime start, DateTime end); + IList FindSendEvents(string email, DateTime start, DateTime end); + IList FindDeliveryEvents(string email, DateTime start, DateTime end); IList FindRaw(DateTime start, DateTime end); SesNotification FindRaw(long id); } diff --git a/Projects/SesNotifications.App/Services/NotificationService.cs b/Projects/SesNotifications.App/Services/NotificationService.cs index cdf5b76..3ee49e4 100644 --- a/Projects/SesNotifications.App/Services/NotificationService.cs +++ b/Projects/SesNotifications.App/Services/NotificationService.cs @@ -23,6 +23,7 @@ public class NotificationService : INotificationService private readonly ISesDeliveriesRepository _sesDeliveriesRepository; private readonly ISesOpensEventsRepository _sesOpensEventsRepository; private readonly ISesSendEventsRepository _sesSendEventsRepository; + private readonly ISesDeliveryEventsRepository _sesDeliveryEventsRepository; private readonly ILogger _logger; public NotificationService(INotificationsRepository notificationsRepository, @@ -31,6 +32,7 @@ public NotificationService(INotificationsRepository notificationsRepository, ISesDeliveriesRepository sesDeliveriesRepository, ISesOpensEventsRepository sesOpensEventsRepository, ISesSendEventsRepository sesSendEventsRepository, + ISesDeliveryEventsRepository sesDeliveryEventsRepository, ILogger logger) { _notificationsRepository = notificationsRepository; @@ -39,6 +41,8 @@ public NotificationService(INotificationsRepository notificationsRepository, _sesDeliveriesRepository = sesDeliveriesRepository; _sesOpensEventsRepository = sesOpensEventsRepository; _sesSendEventsRepository = sesSendEventsRepository; + _sesDeliveriesRepository = sesDeliveriesRepository; + _sesDeliveryEventsRepository = sesDeliveryEventsRepository; _logger = logger; } @@ -89,10 +93,13 @@ private void HandleNotificationInternal(string content) switch (ses.EventType.ToLower()) { case Open: - HandleOpen(content); + HandleOpenEvent(content); break; case Send: - HandleSend(content); + HandleSendEvent(content); + break; + case Delivery: + HandleDeliveryEvent(content); break; default: throw new NotSupportedException($"Unsupported message {content.Substring(0, 50)}..."); @@ -100,6 +107,15 @@ private void HandleNotificationInternal(string content) } } + private void HandleDeliveryEvent(string content) + { + var delivery = JsonConvert.DeserializeObject(content); + + var notification = SaveNotification(delivery.Mail, content); + + _sesDeliveryEventsRepository.Save(delivery.Create(notification.Id)); + } + private void HandleDelivery(string content) { var delivery = JsonConvert.DeserializeObject(content); @@ -127,18 +143,18 @@ private void HandleBounce(string content) _sesBouncesRepository.Save(bounce.Create(notification.Id)); } - private void HandleOpen(string content) + private void HandleOpenEvent(string content) { - var open = JsonConvert.DeserializeObject(content); + var open = JsonConvert.DeserializeObject(content); var notification = SaveNotification(open.Mail, content); _sesOpensEventsRepository.Save(open.Create(notification.Id)); } - private void HandleSend(string content) + private void HandleSendEvent(string content) { - var send = JsonConvert.DeserializeObject(content); + var send = JsonConvert.DeserializeObject(content); var notification = SaveNotification(send.Mail, content); diff --git a/Projects/SesNotifications.App/Services/SearchService.cs b/Projects/SesNotifications.App/Services/SearchService.cs index 407f030..27e509e 100644 --- a/Projects/SesNotifications.App/Services/SearchService.cs +++ b/Projects/SesNotifications.App/Services/SearchService.cs @@ -17,6 +17,7 @@ public class SearchService : ISearchService private readonly ISesDeliveriesRepository _sesDeliveriesRepository; private readonly ISesOpensEventsRepository _sesOpensEventsRepository; private readonly ISesSendEventsRepository _sesSendEventsRepository; + private readonly ISesDeliveryEventsRepository _sesDeliveryEventsRepository; private readonly ILogger _logger; public SearchService(INotificationsRepository notificationsRepository, @@ -25,6 +26,7 @@ public SearchService(INotificationsRepository notificationsRepository, ISesDeliveriesRepository sesDeliveriesRepository, ISesOpensEventsRepository sesOpensEventsRepository, ISesSendEventsRepository sesSendEventsRepository, + ISesDeliveryEventsRepository sesDeliveryEventsRepository, ILogger logger) { _notificationsRepository = notificationsRepository; @@ -33,6 +35,7 @@ public SearchService(INotificationsRepository notificationsRepository, _sesDeliveriesRepository = sesDeliveriesRepository; _sesOpensEventsRepository = sesOpensEventsRepository; _sesSendEventsRepository = sesSendEventsRepository; + _sesDeliveryEventsRepository = sesDeliveryEventsRepository; _logger = logger; } @@ -57,20 +60,27 @@ public IList FindBounces(string email, DateTime start, DateTime end) : _sesBouncesRepository.FindByRecipientAndSentDateRange($"%{email}%", start, end); } - public IList FindOpens(string email, DateTime start, DateTime end) + public IList FindOpenEvents(string email, DateTime start, DateTime end) { return string.IsNullOrEmpty(email) ? _sesOpensEventsRepository.FindBySentDateRange(start, end) : _sesOpensEventsRepository.FindByRecipientAndSentDateRange($"%{email}%", start, end); } - public IList FindSends(string email, DateTime start, DateTime end) + public IList FindSendEvents(string email, DateTime start, DateTime end) { return string.IsNullOrEmpty(email) ? _sesSendEventsRepository.FindBySentDateRange(start, end) : _sesSendEventsRepository.FindByRecipientAndSentDateRange($"%{email}%", start, end); } + public IList FindDeliveryEvents(string email, DateTime start, DateTime end) + { + return string.IsNullOrEmpty(email) + ? _sesDeliveryEventsRepository.FindBySentDateRange(start, end) + : _sesDeliveryEventsRepository.FindByRecipientAndSentDateRange($"%{email}%", start, end); + } + public IList FindRaw(DateTime start, DateTime end) { return _notificationsRepository.FindBySentDateRange(start, end); diff --git a/Projects/SesNotifications.DataAccess/Entities/SesDeliveryEvent.cs b/Projects/SesNotifications.DataAccess/Entities/SesDeliveryEvent.cs new file mode 100644 index 0000000..879bae8 --- /dev/null +++ b/Projects/SesNotifications.DataAccess/Entities/SesDeliveryEvent.cs @@ -0,0 +1,12 @@ +using System; + +namespace SesNotifications.DataAccess.Entities +{ + public class SesDeliveryEvent : SesCommon + { + public virtual DateTime DeliveredAt { get; set; } + public virtual string SmtpResponse { get; set; } + public virtual string ReportingMta { get; set; } + public virtual string Recipients { get; set; } + } +} diff --git a/Projects/SesNotifications.DataAccess/Mappings/SesDeliveryEventMap.cs b/Projects/SesNotifications.DataAccess/Mappings/SesDeliveryEventMap.cs new file mode 100644 index 0000000..1fa48e7 --- /dev/null +++ b/Projects/SesNotifications.DataAccess/Mappings/SesDeliveryEventMap.cs @@ -0,0 +1,17 @@ +using SesNotifications.DataAccess.Entities; + +namespace SesNotifications.DataAccess.Mappings +{ + public class SesDeliveryEventMap : SesCommonMap + { + public SesDeliveryEventMap() + { + Table("ses_notifications.deliveryevents"); + MapCommon(); + Map(x => x.DeliveredAt).Column("delivered_at"); + Map(x => x.SmtpResponse).Column("smtp_response"); + Map(x => x.ReportingMta).Column("reporting_mta"); + Map(x => x.Recipients).Column("recipients"); + } + } +} \ No newline at end of file diff --git a/Projects/SesNotifications.DataAccess/Mappings/SesOpenEventMap.cs b/Projects/SesNotifications.DataAccess/Mappings/SesOpenEventMap.cs index 9562623..c0b9caa 100644 --- a/Projects/SesNotifications.DataAccess/Mappings/SesOpenEventMap.cs +++ b/Projects/SesNotifications.DataAccess/Mappings/SesOpenEventMap.cs @@ -6,7 +6,7 @@ public class SesOpenEventMap : SesCommonMap { public SesOpenEventMap() { - Table("ses_notifications.opens"); + Table("ses_notifications.openevents"); MapCommon(); Map(x => x.Recipients).Column("recipients"); Map(x => x.OpenedAt).Column("opened_at"); diff --git a/Projects/SesNotifications.DataAccess/Mappings/SesSendEventMap.cs b/Projects/SesNotifications.DataAccess/Mappings/SesSendEventMap.cs index 2e14bb0..c9007d0 100644 --- a/Projects/SesNotifications.DataAccess/Mappings/SesSendEventMap.cs +++ b/Projects/SesNotifications.DataAccess/Mappings/SesSendEventMap.cs @@ -6,7 +6,7 @@ public class SesSendEventMap : SesCommonMap { public SesSendEventMap() { - Table("ses_notifications.sends"); + Table("ses_notifications.sendevents"); MapCommon(); Map(x => x.Recipients).Column("recipients"); } diff --git a/Projects/SesNotifications.DataAccess/Repositories/Interfaces/ISesDeliveryEventsRepository.cs b/Projects/SesNotifications.DataAccess/Repositories/Interfaces/ISesDeliveryEventsRepository.cs new file mode 100644 index 0000000..8dbbb17 --- /dev/null +++ b/Projects/SesNotifications.DataAccess/Repositories/Interfaces/ISesDeliveryEventsRepository.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using SesNotifications.DataAccess.Entities; + +namespace SesNotifications.DataAccess.Repositories.Interfaces +{ + public interface ISesDeliveryEventsRepository + { + void Save(SesDeliveryEvent sesDeliveryEvent); + SesDeliveryEvent FindById(long id); + IList FindByMessageId(string messageId); + IList FindBySentDateRange(DateTime start, DateTime end); + IList FindByRecipient(string email); + IList FindByRecipientAndSentDateRange(string email, DateTime start, DateTime end); + } +} diff --git a/Projects/SesNotifications.DataAccess/Repositories/SesDeliveryEventsRepository.cs b/Projects/SesNotifications.DataAccess/Repositories/SesDeliveryEventsRepository.cs new file mode 100644 index 0000000..3a49039 --- /dev/null +++ b/Projects/SesNotifications.DataAccess/Repositories/SesDeliveryEventsRepository.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using NHibernate; +using NHibernate.Criterion; +using SesNotifications.DataAccess.Entities; +using SesNotifications.DataAccess.Repositories.Interfaces; + +namespace SesNotifications.DataAccess.Repositories +{ + public class SesDeliveryEventsRepository : Repository, ISesDeliveryEventsRepository + { + public SesDeliveryEventsRepository() + { + } + + public SesDeliveryEventsRepository(ISession session) : base(session) + { + } + + public void Save(SesDeliveryEvent sesDeliveryEvent) + { + Session.Save(sesDeliveryEvent); + } + + public SesDeliveryEvent FindById(long id) + { + return Session.Get(id); + } + + public IList FindByMessageId(string messageId) + { + return Session.CreateCriteria() + .Add(Restrictions.Eq(nameof(SesDeliveryEvent.MessageId), messageId)) + .List(); + } + + public IList FindBySentDateRange(DateTime start, DateTime end) + { + return Session.CreateCriteria() + .Add(Restrictions.Ge(nameof(SesDeliveryEvent.SentAt), start)) + .Add(Restrictions.Le(nameof(SesDeliveryEvent.SentAt), end)) + .AddOrder(Order.Desc(nameof(SesDeliveryEvent.SentAt))) + .List(); + } + + public IList FindByRecipient(string email) + { + return Session.CreateCriteria() + .Add(Restrictions.InsensitiveLike(nameof(SesDeliveryEvent.Recipients), email)) + .AddOrder(Order.Desc(nameof(SesDeliveryEvent.SentAt))) + .List(); + } + + public IList FindByRecipientAndSentDateRange(string email, DateTime start, DateTime end) + { + return Session.CreateCriteria() + .Add(Restrictions.InsensitiveLike(nameof(SesDeliveryEvent.Recipients), email)) + .Add(Restrictions.Ge(nameof(SesDeliveryEvent.SentAt), start)) + .Add(Restrictions.Le(nameof(SesDeliveryEvent.SentAt), end)) + .AddOrder(Order.Desc(nameof(SesDeliveryEvent.SentAt))) + .List(); + } + } +} diff --git a/Projects/SesNotifications.DataAccess/StartupExtensions.cs b/Projects/SesNotifications.DataAccess/StartupExtensions.cs index c67a686..763d704 100644 --- a/Projects/SesNotifications.DataAccess/StartupExtensions.cs +++ b/Projects/SesNotifications.DataAccess/StartupExtensions.cs @@ -22,7 +22,9 @@ public static IServiceCollection AddNHibernate(this IServiceCollection services, .Add() .Add() .Add() - .Add()) + .Add() + .Add() + ) .BuildSessionFactory(); SessionManager.Build(sessionFactory); @@ -46,6 +48,7 @@ public static IServiceCollection AddScopedRepositories(this IServiceCollection s services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); return services; } diff --git a/Sql/ses_notifications_init.sql b/Sql/ses_notifications_init.sql index 0a6d36d..0391fe2 100644 --- a/Sql/ses_notifications_init.sql +++ b/Sql/ses_notifications_init.sql @@ -1,4 +1,29 @@ CREATE SCHEMA ses_notifications AUTHORIZATION postgres; + +CREATE TABLE ses_notifications.deliveryevents ( + id int8 NOT NULL GENERATED ALWAYS AS IDENTITY, + notification_id int8 NOT NULL, + notification_type varchar(32) NOT NULL, + sent_at timestamptz NOT NULL, + message_id varchar(128) NOT NULL, + source varchar(256) NOT NULL COLLATE "ucs_basic", + source_arn varchar(256) NULL, + source_ip varchar(32) NULL, + sending_account_id varchar(128) NULL, + delivered_at timestamptz NULL, + smtp_response varchar(256) NULL COLLATE "ucs_basic", + reporting_mta varchar(256) NULL, + recipients varchar(64000) NULL COLLATE "ucs_basic" +); +CREATE INDEX deliveryevents_delivered_at_idx ON ses_notifications.deliveryevents (delivered_at); +CREATE INDEX deliveryevents_from_idx ON ses_notifications.deliveryevents (source); +CREATE UNIQUE INDEX deliveryevents_id_idx ON ses_notifications.deliveryevents (id); +CREATE INDEX deliveryevents_message_id_idx ON ses_notifications.deliveryevents (message_id); +CREATE INDEX deliveryevents_notification_id_idx ON ses_notifications.deliveryevents (notification_id); +CREATE INDEX deliveryevents_recipients_idx ON ses_notifications.deliveryevents (recipients); +CREATE INDEX deliveryevents_sent_at_idx ON ses_notifications.deliveryevents (sent_at); +CREATE INDEX deliveryevents_smtp_response_idx ON ses_notifications.deliveryevents (smtp_response); + CREATE TABLE ses_notifications.openevents ( id int8 NOT NULL GENERATED ALWAYS AS IDENTITY, notification_id int8 NOT NULL, diff --git a/Tests/SesNotifications.App.Tests/Factories/DbSesDeliveryEventFactoryTests.cs b/Tests/SesNotifications.App.Tests/Factories/DbSesDeliveryEventFactoryTests.cs new file mode 100644 index 0000000..c3e630c --- /dev/null +++ b/Tests/SesNotifications.App.Tests/Factories/DbSesDeliveryEventFactoryTests.cs @@ -0,0 +1,32 @@ +using System; +using SesNotifications.App.Factories; +using SesNotifications.App.Tests.Helpers; +using Xunit; + +namespace SesNotifications.App.Tests.Factories +{ + public class DbSesDeliveryEventFactoryTests + { + [Fact] + public void Verify() + { + var dt = DateTime.UtcNow; + var deliveryEvent = TestHelpers.GetSesDeliveryEventModel(dt); + + var sesDeliveryEvent = deliveryEvent.Create(1); + + Assert.Equal(sesDeliveryEvent.Recipients, string.Join(',', deliveryEvent.Delivery.Recipients)); + Assert.Equal(sesDeliveryEvent.SmtpResponse, deliveryEvent.Delivery.SmtpResponse); + Assert.Equal(sesDeliveryEvent.DeliveredAt.Iso8601(), dt.Iso8601()); + Assert.Equal(sesDeliveryEvent.SentAt.Iso8601(), dt.Iso8601()); + Assert.Equal(sesDeliveryEvent.SendingAccountId, deliveryEvent.Mail.SendingAccountId); + Assert.Equal(sesDeliveryEvent.ReportingMta, deliveryEvent.Delivery.ReportingMta); + Assert.Equal(sesDeliveryEvent.SourceArn, deliveryEvent.Mail.SourceArn); + Assert.Equal(sesDeliveryEvent.Source, deliveryEvent.Mail.Source); + Assert.Equal(sesDeliveryEvent.SourceIp, deliveryEvent.Mail.SourceIp); + Assert.Equal(sesDeliveryEvent.NotificationType, deliveryEvent.EventType); + Assert.Equal(1, sesDeliveryEvent.NotificationId); + Assert.Equal(sesDeliveryEvent.MessageId, deliveryEvent.Mail.MessageId); + } + } +} diff --git a/Tests/SesNotifications.App.Tests/Factories/DbSesDeliveryFactoryTests.cs b/Tests/SesNotifications.App.Tests/Factories/DbSesDeliveryFactoryTests.cs index 50def38..152d090 100644 --- a/Tests/SesNotifications.App.Tests/Factories/DbSesDeliveryFactoryTests.cs +++ b/Tests/SesNotifications.App.Tests/Factories/DbSesDeliveryFactoryTests.cs @@ -20,7 +20,6 @@ public void Verify() Assert.Equal(sesDelivery.DeliveredAt.Iso8601(), dt.Iso8601()); Assert.Equal(sesDelivery.SentAt.Iso8601(), dt.Iso8601()); Assert.Equal(sesDelivery.SendingAccountId, delivery.Mail.SendingAccountId); - Assert.Equal(sesDelivery.RemoteMtaIp, delivery.Delivery.RemoteMtaIp); Assert.Equal(sesDelivery.ReportingMta, delivery.Delivery.ReportingMta); Assert.Equal(sesDelivery.SourceArn, delivery.Mail.SourceArn); Assert.Equal(sesDelivery.Source, delivery.Mail.Source); diff --git a/Tests/SesNotifications.App.Tests/Factories/DbSesOpenFactoryTests.cs b/Tests/SesNotifications.App.Tests/Factories/DbSesOpenEventFactoryTests.cs similarity index 58% rename from Tests/SesNotifications.App.Tests/Factories/DbSesOpenFactoryTests.cs rename to Tests/SesNotifications.App.Tests/Factories/DbSesOpenEventFactoryTests.cs index ecbb765..9a9a587 100644 --- a/Tests/SesNotifications.App.Tests/Factories/DbSesOpenFactoryTests.cs +++ b/Tests/SesNotifications.App.Tests/Factories/DbSesOpenEventFactoryTests.cs @@ -5,7 +5,7 @@ namespace SesNotifications.App.Tests.Factories { - public class DbSesOpenFactoryTests + public class DbSesOpenEventFactoryTests { [Fact] public void Verify() @@ -16,11 +16,17 @@ public void Verify() var sesOpen = open.Create(1); Assert.Equal(sesOpen.Recipients, string.Join(',', open.Mail.Destination)); - Assert.Equal(sesOpen.OpenedAt.Iso8601(), open.Open.Timestamp); + Assert.Equal(sesOpen.SentAt.Iso8601(), dt.Iso8601()); + Assert.Equal(sesOpen.SendingAccountId, open.Mail.SendingAccountId); Assert.Equal(sesOpen.SourceArn, open.Mail.SourceArn); - Assert.Equal(sesOpen.NotificationType, open.NotificationType); + Assert.Equal(sesOpen.Source, open.Mail.Source); + Assert.Equal(sesOpen.SourceIp, open.Mail.SourceIp); + Assert.Equal(sesOpen.NotificationType, open.EventType); Assert.Equal(1, sesOpen.NotificationId); Assert.Equal(sesOpen.MessageId, open.Mail.MessageId); + Assert.Equal(sesOpen.OpenedAt.Iso8601(), open.Open.Timestamp); + Assert.Equal(sesOpen.UserAgent, open.Open.UserAgent); + Assert.Equal(sesOpen.IpAddress, open.Open.IpAddress); } } } \ No newline at end of file diff --git a/Tests/SesNotifications.App.Tests/Factories/DbSesSendEventFactoryTests.cs b/Tests/SesNotifications.App.Tests/Factories/DbSesSendEventFactoryTests.cs new file mode 100644 index 0000000..4e80bb4 --- /dev/null +++ b/Tests/SesNotifications.App.Tests/Factories/DbSesSendEventFactoryTests.cs @@ -0,0 +1,29 @@ +using System; +using SesNotifications.App.Factories; +using SesNotifications.App.Tests.Helpers; +using Xunit; + +namespace SesNotifications.App.Tests.Factories +{ + public class DbSesSendEventFactoryTests + { + [Fact] + public void Verify() + { + var dt = DateTime.UtcNow; + var open = TestHelpers.GetSesSendModel(dt); + + var sesOpen = open.Create(1); + + Assert.Equal(sesOpen.Recipients, string.Join(',', open.Mail.Destination)); + Assert.Equal(sesOpen.SentAt.Iso8601(), dt.Iso8601()); + Assert.Equal(sesOpen.SendingAccountId, open.Mail.SendingAccountId); + Assert.Equal(sesOpen.SourceArn, open.Mail.SourceArn); + Assert.Equal(sesOpen.Source, open.Mail.Source); + Assert.Equal(sesOpen.SourceIp, open.Mail.SourceIp); + Assert.Equal(sesOpen.NotificationType, open.EventType); + Assert.Equal(1, sesOpen.NotificationId); + Assert.Equal(sesOpen.MessageId, open.Mail.MessageId); + } + } +} diff --git a/Tests/SesNotifications.App.Tests/Helpers/TestHelpers.cs b/Tests/SesNotifications.App.Tests/Helpers/TestHelpers.cs index 33af645..fa74150 100644 --- a/Tests/SesNotifications.App.Tests/Helpers/TestHelpers.cs +++ b/Tests/SesNotifications.App.Tests/Helpers/TestHelpers.cs @@ -5,6 +5,21 @@ namespace SesNotifications.App.Tests.Helpers { public class TestHelpers { + public static SesOpenEventModel GetOpenEventModel(DateTime dt) + { + return new SesOpenEventModel + { + EventType = "Open", + Mail = GetSesMail(dt), + Open = new SesOpenEvent + { + UserAgent = "user agent", + IpAddress = "ip_address", + Timestamp = dt.Iso8601() + } + }; + } + public static SesDeliveryModel GetSesDeliveryModel(DateTime dt) { return new SesDeliveryModel @@ -22,6 +37,22 @@ public static SesDeliveryModel GetSesDeliveryModel(DateTime dt) }; } + public static SesDeliveryEventModel GetSesDeliveryEventModel(DateTime dt) + { + return new SesDeliveryEventModel + { + EventType = "Delivery", + Mail = GetSesMail(dt), + Delivery = new SesDeliveryEvent + { + Recipients = new[] { "recipient_1" }, + ReportingMta = "mta", + SmtpResponse = "response", + Timestamp = dt.Iso8601() + } + }; + } + public static SesComplaintModel GetSesComplaintModel(DateTime dt) { return new SesComplaintModel @@ -59,13 +90,13 @@ public static SesBounceModel GetSesBounceModel(DateTime dt) }; } - public static SesOpenModel GetSesOpenModel(DateTime dt) + public static SesOpenEventModel GetSesOpenModel(DateTime dt) { - return new SesOpenModel + return new SesOpenEventModel { - NotificationType = "Open", + EventType = "Open", Mail = GetSesMail(dt), - Open = new SesOpen + Open = new SesOpenEvent { UserAgent = "user_agent", IpAddress = "ip_address", @@ -74,6 +105,16 @@ public static SesOpenModel GetSesOpenModel(DateTime dt) }; } + public static SesSendEventModel GetSesSendModel(DateTime dt) + { + return new SesSendEventModel + { + EventType = "Send", + Mail = GetSesMail(dt), + Send = new SesSendEvent() + }; + } + public static SesMail GetSesMail(DateTime dt) { return new SesMail diff --git a/Tests/SesNotifications.App.Tests/Services/NotificationServiceTests.cs b/Tests/SesNotifications.App.Tests/Services/NotificationServiceTests.cs index fc1633f..7817c53 100644 --- a/Tests/SesNotifications.App.Tests/Services/NotificationServiceTests.cs +++ b/Tests/SesNotifications.App.Tests/Services/NotificationServiceTests.cs @@ -22,7 +22,7 @@ public void VerifyDelivery() mockSesDeliveries.Setup(x => x.Save(It.IsAny())); var service = new NotificationService(mockNotifications.Object, null, null, mockSesDeliveries.Object, - null, null, mockLogger.Object); + null, null, null, mockLogger.Object); service.HandleNotification(Delivery); @@ -41,7 +41,7 @@ public void VerifyBounce() mockSesBounces.Setup(x => x.Save(It.IsAny())); var service = new NotificationService(mockNotifications.Object, mockSesBounces.Object, null, null, - null, null, mockLogger.Object); + null, null, null, mockLogger.Object); service.HandleNotification(Bounce); @@ -60,7 +60,7 @@ public void VerifyComplaints() mockSesComplaints.Setup(x => x.Save(It.IsAny())); var service = new NotificationService(mockNotifications.Object, null, mockSesComplaints.Object, null, - null, null, mockLogger.Object); + null, null, null, mockLogger.Object); service.HandleNotification(Complaint); @@ -69,7 +69,7 @@ public void VerifyComplaints() } [Fact] - public void VerifyOpens() + public void VerifyOpenEvents() { var mockNotifications = new Mock(MockBehavior.Strict); var mockSesOpens = new Mock(MockBehavior.Strict); @@ -79,16 +79,16 @@ public void VerifyOpens() mockSesOpens.Setup(x => x.Save(It.IsAny())); var service = new NotificationService(mockNotifications.Object, null, null, null, - mockSesOpens.Object, null, mockLogger.Object); + mockSesOpens.Object, null, null, mockLogger.Object); - service.HandleNotification(Open); + service.HandleNotification(OpenEvent); mockNotifications.Verify(x => x.Save(It.IsAny()), Times.Exactly(1)); mockSesOpens.Verify(x => x.Save(It.IsAny()), Times.Exactly(1)); } [Fact] - public void VerifySends() + public void VerifySendEvents() { var mockNotifications = new Mock(MockBehavior.Strict); var mockSesSends = new Mock(MockBehavior.Strict); @@ -98,20 +98,39 @@ public void VerifySends() mockSesSends.Setup(x => x.Save(It.IsAny())); var service = new NotificationService(mockNotifications.Object, null, null, null, - null, mockSesSends.Object,mockLogger.Object); + null, mockSesSends.Object, null, mockLogger.Object); - service.HandleNotification(Send); + service.HandleNotification(SendEvent); mockNotifications.Verify(x => x.Save(It.IsAny()), Times.Exactly(1)); mockSesSends.Verify(x => x.Save(It.IsAny()), Times.Exactly(1)); } + [Fact] + public void VerifyDeliveryEvents() + { + var mockNotifications = new Mock(MockBehavior.Strict); + var mockSesDeliveries = new Mock(MockBehavior.Strict); + var mockLogger = new Mock>(MockBehavior.Loose); + + mockNotifications.Setup(x => x.Save(It.IsAny())); + mockSesDeliveries.Setup(x => x.Save(It.IsAny())); + + var service = new NotificationService(mockNotifications.Object, null, null, null, + null, null, mockSesDeliveries.Object, mockLogger.Object); + + service.HandleNotification(DeliveryEvent); + + mockNotifications.Verify(x => x.Save(It.IsAny()), Times.Exactly(1)); + mockSesDeliveries.Verify(x => x.Save(It.IsAny()), Times.Exactly(1)); + } + [Fact] public void VerifyInvalidException() { var mockLogger = new Mock>(MockBehavior.Loose); - var service = new NotificationService(null, null, null, null, null, null, mockLogger.Object); + var service = new NotificationService(null, null, null, null, null, null, null, mockLogger.Object); Assert.Throws(() => service.HandleNotification(NotJson)); } @@ -121,7 +140,7 @@ public void VerifyUnsupportedException() { var mockLogger = new Mock>(MockBehavior.Loose); - var service = new NotificationService(null, null, null, null, null, null, mockLogger.Object); + var service = new NotificationService(null, null, null, null, null, null, null, mockLogger.Object); Assert.Throws(() => service.HandleNotification(Invalid)); } @@ -130,8 +149,9 @@ public void VerifyUnsupportedException() private const string Delivery = "{ \"notificationType\":\"Delivery\", \"mail\":{ \"timestamp\":\"2016-01-27T14:59:38.237Z\", \"messageId\":\"0000014644fe5ef6-9a483358-9170-4cb4-a269-f5dcdf415321-000000\", \"source\":\"john@example.com\", \"sourceArn\": \"arn:aws:ses:us-west-2:888888888888:identity/example.com\", \"sourceIp\": \"127.0.3.0\", \"sendingAccountId\":\"123456789012\", \"destination\":[ \"jane@example.com\" ], \"headersTruncated\":false, \"headers\":[ { \"name\":\"From\", \"value\":\"\\\"John Doe\\\" \" }, { \"name\":\"To\", \"value\":\"\\\"Jane Doe\\\" \" }, { \"name\":\"Message-ID\", \"value\":\"custom-message-ID\" }, { \"name\":\"Subject\", \"value\":\"Hello\" }, { \"name\":\"Content-Type\", \"value\":\"text/plain; charset=\\\"UTF-8\\\"\" }, { \"name\":\"Content-Transfer-Encoding\", \"value\":\"base64\" }, { \"name\":\"Date\", \"value\":\"Wed, 27 Jan 2016 14:58:45 +0000\" } ], \"commonHeaders\":{ \"from\":[ \"John Doe \" ], \"date\":\"Wed, 27 Jan 2016 14:58:45 +0000\", \"to\":[ \"Jane Doe \" ], \"messageId\":\"custom-message-ID\", \"subject\":\"Hello\" } }, \"delivery\":{ \"timestamp\":\"2016-01-27T14:59:38.237Z\", \"recipients\":[\"jane@example.com\"], \"processingTimeMillis\":546, \"reportingMTA\":\"a8-70.smtp-out.amazonses.com\", \"smtpResponse\":\"250 ok: Message 64111812 accepted\", \"remoteMtaIp\":\"127.0.2.0\" } }"; private const string Complaint ="{ \"notificationType\":\"Complaint\", \"complaint\":{ \"userAgent\":\"AnyCompany Feedback Loop (V0.01)\", \"complainedRecipients\":[ { \"emailAddress\":\"richard@example.com\" } ], \"complaintFeedbackType\":\"abuse\", \"arrivalDate\":\"2016-01-27T14:59:38.237Z\", \"timestamp\":\"2016-01-27T14:59:38.237Z\", \"feedbackId\":\"000001378603177f-18c07c78-fa81-4a58-9dd1-fedc3cb8f49a-000000\" }, \"mail\":{ \"timestamp\":\"2016-01-27T14:59:38.237Z\", \"messageId\":\"000001378603177f-7a5433e7-8edb-42ae-af10-f0181f34d6ee-000000\", \"source\":\"john@example.com\", \"sourceArn\": \"arn:aws:ses:us-west-2:888888888888:identity/example.com\", \"sourceIp\": \"127.0.3.0\", \"sendingAccountId\":\"123456789012\", \"destination\":[ \"jane@example.com\", \"mary@example.com\", \"richard@example.com\" ], \"headersTruncated\":false, \"headers\":[ { \"name\":\"From\", \"value\":\"\\\"John Doe\\\" \" }, { \"name\":\"To\", \"value\":\"\\\"Jane Doe\\\" , \\\"Mary Doe\\\" , \\\"Richard Doe\\\" \" }, { \"name\":\"Message-ID\", \"value\":\"custom-message-ID\" }, { \"name\":\"Subject\", \"value\":\"Hello\" }, { \"name\":\"Content-Type\", \"value\":\"text/plain; charset=\\\"UTF-8\\\"\" }, { \"name\":\"Content-Transfer-Encoding\", \"value\":\"base64\" }, { \"name\":\"Date\", \"value\":\"Wed, 27 Jan 2016 14:05:45 +0000\" } ], \"commonHeaders\":{ \"from\":[ \"John Doe \" ], \"date\":\"Wed, 27 Jan 2016 14:05:45 +0000\", \"to\":[ \"Jane Doe , Mary Doe , Richard Doe \" ], \"messageId\":\"custom-message-ID\", \"subject\":\"Hello\" } } }"; private const string Bounce = "{ \"notificationType\":\"Bounce\", \"bounce\":{ \"bounceType\":\"Permanent\", \"bounceSubType\": \"General\", \"bouncedRecipients\":[ { \"emailAddress\":\"jane@example.com\" }, { \"emailAddress\":\"richard@example.com\" } ], \"timestamp\":\"2016-01-27T14:59:38.237Z\", \"feedbackId\":\"00000137860315fd-869464a4-8680-4114-98d3-716fe35851f9-000000\", \"remoteMtaIp\":\"127.0.2.0\" }, \"mail\":{ \"timestamp\":\"2016-01-27T14:59:38.237Z\", \"messageId\":\"00000137860315fd-34208509-5b74-41f3-95c5-22c1edc3c924-000000\", \"source\":\"john@example.com\", \"sourceArn\": \"arn:aws:ses:us-west-2:888888888888:identity/example.com\", \"sourceIp\": \"127.0.3.0\", \"sendingAccountId\":\"123456789012\", \"destination\":[ \"jane@example.com\", \"mary@example.com\", \"richard@example.com\" ], \"headersTruncated\":false, \"headers\":[ { \"name\":\"From\", \"value\":\"\\\"John Doe\\\" \" }, { \"name\":\"To\", \"value\":\"\\\"Jane Doe\\\" , \\\"Mary Doe\\\" , \\\"Richard Doe\\\" \" }, { \"name\":\"Message-ID\", \"value\":\"custom-message-ID\" }, { \"name\":\"Subject\", \"value\":\"Hello\" }, { \"name\":\"Content-Type\", \"value\":\"text/plain; charset=\\\"UTF-8\\\"\" }, { \"name\":\"Content-Transfer-Encoding\", \"value\":\"base64\" }, { \"name\":\"Date\", \"value\":\"Wed, 27 Jan 2016 14:05:45 +0000\" } ], \"commonHeaders\":{ \"from\":[ \"John Doe \" ], \"date\":\"Wed, 27 Jan 2016 14:05:45 +0000\", \"to\":[ \"Jane Doe , Mary Doe , Richard Doe \" ], \"messageId\":\"custom-message-ID\", \"subject\":\"Hello\" } } }"; - private const string Open = "{\"eventType\": \"Open\",\"mail\": {\"timestamp\": \"2020-05-08T08:42:19.146Z\",\"source\": \"d.test+python@test.gr\",\"sendingAccountId\": \"257120724488\",\"messageId\": \"01070171f3731a8a-8ab938e9-787d-4165-b967-fdb2abec1fc7-000000\",\"destination\": [\"mr.test@gmail.com\"],\"headersTruncated\": false,\"headers\": [{\"name\": \"Received\",\"value\": \"from [127.0.1.1] ([11.111.111.68]) by email-smtp.amazonaws.com with SMTP (SimpleEmailService-d-GG6TMY8U3) id 4E25Kp3rgG6djvRHUmZH for mr.test@gmail.com; Fri, 08 May 2020 08:42:19 +0000 (UTC)\"},{\"name\": \"Content-Type\",\"value\": \"multipart/alternative\"},{\"name\": \"MIME-Version\",\"value\": \"1.0\"},{\"name\": \"Subject\",\"value\": \"Amazon SES Test (Python smtplib) staging\"},{\"name\": \"From\",\"value\": \"Dimos Python script \"},{\"name\": \"To\",\"value\": \"mr.test@gmail.com\"},{\"name\": \"X-SES-CONFIGURATION-SET\",\"value\": \"ses-to-sqs\"},{\"name\": \"Message-ID\",\"value\": \"null\"}],\"commonHeaders\": {\"from\": [\"Dimos Python script \"],\"to\": [\"mr.test@gmail.com\"],\"messageId\": \"01070171f3731a8a-8ab938e9-787d-4165-b967-fdb2abec1fc7-000000\",\"subject\": \"Amazon SES Test (Python smtplib) staging\"},\"tags\": {\"ses:operation\": [\"SendSmtpEmail\"],\"ses:configuration-set\": [\"ses-to-sqs\"],\"ses:source-ip\": [\"11.111.112.61\"],\"ses:from-domain\": [\"test.gr\"],\"ses:caller-identity\": [\"ses-smtp-notificator\"]}},\"open\": {\"timestamp\": \"2020-05-08T08:56:46.917Z\",\"userAgent\": \"Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)\",\"ipAddress\": \"11.111.11.111\"}}"; - private const string Send = "{\"eventType\":\"Send\",\"mail\":{\"timestamp\":\"2020-05-29T15:26:26.221Z\",\"source\":\"tester+python@xe.gr\",\"sourceArn\":\"arn:aws:ses:eu-central-1:234567890:identity/test.com\",\"sendingAccountId\":\"234567890\",\"messageId\":\"01070172610aa1ad-ffae92b5-dd4d-42e5-aa72-e75d9cd4a995-000000\",\"destination\":[\"tester@gmail.com\"],\"headersTruncated\":false,\"headers\":[{\"name\":\"Received\",\"value\":\"from [127.0.1.1] (ppp-12-34-56-78.home.test.com [12.34.567.222]) by email-smtp.amazonaws.com with SMTP (SimpleEmailService-d-32P1K87U3) id f4qxpujfRBhaohG8QqoE for test@gmail.com; Fri, 29 May 2020 15:26:26 +0000 (UTC)\"},{\"name\":\"Content-Type\",\"value\":\"multipart/alternative; boundary=1497643119139888747\"},{\"name\":\"MIME-Version\",\"value\":\"1.0\"},{\"name\":\"Subject\",\"value\":\"Amazon SES Test (Python smtplib) prod with configset\"},{\"name\":\"From\",\"value\":\"Python script \"},{\"name\":\"To\",\"value\":\"tester@gmail.com\"},{\"name\":\"X-SES-CONFIGURATION-SET\",\"value\":\"ses-events\"}],\"commonHeaders\":{\"from\":[\"Python script \"],\"to\":[\"tester@gmail.com\"],\"messageId\":\"01070172610aa1ad-ffae92b5-dd4d-42e5-aa72-e75d9cd4a995-000000\",\"subject\":\"Amazon SES Test (Python smtplib) prod with configset\"},\"tags\":{\"ses:operation\":[\"SendSmtpEmail\"],\"ses:configuration-set\":[\"ses-events\"],\"ses:source-ip\":[\"12.34.567.156\"],\"ses:from-domain\":[\"test.com\"],\"ses:caller-identity\":[\"ses-smtp-notificator\"]}},\"send\":{}}"; + private const string OpenEvent = "{\"eventType\": \"open\",\"mail\": {\"timestamp\": \"2020-05-08T08:42:19.146Z\",\"source\": \"d.test+python@test.gr\",\"sendingAccountId\": \"257120724488\",\"messageId\": \"01070171f3731a8a-8ab938e9-787d-4165-b967-fdb2abec1fc7-000000\",\"destination\": [\"mr.test@gmail.com\"],\"headersTruncated\": false,\"headers\": [{\"name\": \"Received\",\"value\": \"from [127.0.1.1] ([11.111.111.68]) by email-smtp.amazonaws.com with SMTP (SimpleEmailService-d-GG6TMY8U3) id 4E25Kp3rgG6djvRHUmZH for mr.test@gmail.com; Fri, 08 May 2020 08:42:19 +0000 (UTC)\"},{\"name\": \"Content-Type\",\"value\": \"multipart/alternative\"},{\"name\": \"MIME-Version\",\"value\": \"1.0\"},{\"name\": \"Subject\",\"value\": \"Amazon SES Test (Python smtplib) staging\"},{\"name\": \"From\",\"value\": \"Dimos Python script \"},{\"name\": \"To\",\"value\": \"mr.test@gmail.com\"},{\"name\": \"X-SES-CONFIGURATION-SET\",\"value\": \"ses-to-sqs\"},{\"name\": \"Message-ID\",\"value\": \"null\"}],\"commonHeaders\": {\"from\": [\"Dimos Python script \"],\"to\": [\"mr.test@gmail.com\"],\"messageId\": \"01070171f3731a8a-8ab938e9-787d-4165-b967-fdb2abec1fc7-000000\",\"subject\": \"Amazon SES Test (Python smtplib) staging\"},\"tags\": {\"ses:operation\": [\"SendSmtpEmail\"],\"ses:configuration-set\": [\"ses-to-sqs\"],\"ses:source-ip\": [\"11.111.112.61\"],\"ses:from-domain\": [\"test.gr\"],\"ses:caller-identity\": [\"ses-smtp-notificator\"]}},\"open\": {\"timestamp\": \"2020-05-08T08:56:46.917Z\",\"userAgent\": \"Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)\",\"ipAddress\": \"11.111.11.111\"}}"; + private const string SendEvent = "{\"eventType\":\"send\",\"mail\":{\"timestamp\":\"2020-05-29T15:26:26.221Z\",\"source\":\"tester+python@xe.gr\",\"sourceArn\":\"arn:aws:ses:eu-central-1:234567890:identity/test.com\",\"sendingAccountId\":\"234567890\",\"messageId\":\"01070172610aa1ad-ffae92b5-dd4d-42e5-aa72-e75d9cd4a995-000000\",\"destination\":[\"tester@gmail.com\"],\"headersTruncated\":false,\"headers\":[{\"name\":\"Received\",\"value\":\"from [127.0.1.1] (ppp-12-34-56-78.home.test.com [12.34.567.222]) by email-smtp.amazonaws.com with SMTP (SimpleEmailService-d-32P1K87U3) id f4qxpujfRBhaohG8QqoE for test@gmail.com; Fri, 29 May 2020 15:26:26 +0000 (UTC)\"},{\"name\":\"Content-Type\",\"value\":\"multipart/alternative; boundary=1497643119139888747\"},{\"name\":\"MIME-Version\",\"value\":\"1.0\"},{\"name\":\"Subject\",\"value\":\"Amazon SES Test (Python smtplib) prod with configset\"},{\"name\":\"From\",\"value\":\"Python script \"},{\"name\":\"To\",\"value\":\"tester@gmail.com\"},{\"name\":\"X-SES-CONFIGURATION-SET\",\"value\":\"ses-events\"}],\"commonHeaders\":{\"from\":[\"Python script \"],\"to\":[\"tester@gmail.com\"],\"messageId\":\"01070172610aa1ad-ffae92b5-dd4d-42e5-aa72-e75d9cd4a995-000000\",\"subject\":\"Amazon SES Test (Python smtplib) prod with configset\"},\"tags\":{\"ses:operation\":[\"SendSmtpEmail\"],\"ses:configuration-set\":[\"ses-events\"],\"ses:source-ip\":[\"12.34.567.156\"],\"ses:from-domain\":[\"test.com\"],\"ses:caller-identity\":[\"ses-smtp-notificator\"]}},\"send\":{}}"; + private const string DeliveryEvent = "{\"eventType\": \"Delivery\",\"mail\": {\"timestamp\": \"2020-05-29T15:26:26.221Z\",\"source\": \"test+python@test.com\",\"sourceArn\": \"arn:aws:ses:eu-central-1:1234567:identity/test.com\",\"sendingAccountId\": \"1234567\",\"messageId\": \"01070172610aa1ad-ffae92b5-dd4d-42e5-aa72-e75d9cd4a995-000000\",\"destination\": [\"tester@gmail.com\"],\"headersTruncated\": false,\"headers\": [{\"name\": \"Received\",\"value\": \"from [127.0.1.1] (ppp-12-34-56-156.home.test.gr [12.34.56.156]) by email-smtp.amazonaws.com with SMTP (SimpleEmailService-d-32P1K87U3) id f4qxpujfRBhaohG8QqoE for tester@gmail.com; Fri, 29 May 2020 15:26:26 +0000 (UTC)\"},{\"name\": \"Content-Type\",\"value\": \"multipart/alternative; boundary=\"},{\"name\": \"MIME-Version\",\"value\": \"1.0\"},{\"name\": \"Subject\",\"value\": \"Amazon SES Test (Python smtplib) prod with configset\"},{\"name\": \"From\",\"value\": \"Python script \"},{\"name\": \"To\",\"value\": \"tester@gmail.com\"},{\"name\": \"X-SES-CONFIGURATION-SET\",\"value\": \"ses-events\"}],\"commonHeaders\": {\"from\": [\"Python script \"],\"to\": [\"tester@gmail.com\"],\"messageId\": \"01070172610aa1ad-ffae92b5-dd4d-42e5-aa72-e75d9cd4a995-000000\",\"subject\": \"Amazon SES Test (Python smtplib) prod with configset\"},\"tags\": {\"ses:operation\": [\"SendSmtpEmail\"],\"ses:configuration-set\": [\"ses-events\"],\"ses:source-ip\": [\"12.34.56.156\"],\"ses:from-domain\": [\"test.com\"],\"ses:caller-identity\": [\"ses-smtp-notificator\"],\"ses:outgoing-ip\": [\"12.34.56.14\"]}},\"delivery\": {\"timestamp\": \"2020-05-29T15:26:27.213Z\",\"processingTimeMillis\": 992,\"recipients\": [\"tester@gmail.com\"],\"smtpResponse\": \"250 2.0.0 OK 1590765987 s15si9015272wru.411 - gsmtp\",\"reportingMTA\": \"b224-14.smtp-out.eu-central-1.amazonses.com\"}}"; private const string Invalid = "{ \"notificationType\":\"SomethingElse\", \"mail\":{ \"timestamp\":\"2016-01-27T14:59:38.237Z\", \"messageId\":\"0000014644fe5ef6-9a483358-9170-4cb4-a269-f5dcdf415321-000000\", \"source\":\"john@example.com\", \"sourceArn\": \"arn:aws:ses:us-west-2:888888888888:identity/example.com\", \"sourceIp\": \"127.0.3.0\", \"sendingAccountId\":\"123456789012\", \"destination\":[ \"jane@example.com\" ], \"headersTruncated\":false, \"headers\":[ { \"name\":\"From\", \"value\":\"\\\"John Doe\\\" \" }, { \"name\":\"To\", \"value\":\"\\\"Jane Doe\\\" \" }, { \"name\":\"Message-ID\", \"value\":\"custom-message-ID\" }, { \"name\":\"Subject\", \"value\":\"Hello\" }, { \"name\":\"Content-Type\", \"value\":\"text/plain; charset=\\\"UTF-8\\\"\" }, { \"name\":\"Content-Transfer-Encoding\", \"value\":\"base64\" }, { \"name\":\"Date\", \"value\":\"Wed, 27 Jan 2016 14:58:45 +0000\" } ], \"commonHeaders\":{ \"from\":[ \"John Doe \" ], \"date\":\"Wed, 27 Jan 2016 14:58:45 +0000\", \"to\":[ \"Jane Doe \" ], \"messageId\":\"custom-message-ID\", \"subject\":\"Hello\" } }, \"delivery\":{ \"timestamp\":\"2016-01-27T14:59:38.237Z\", \"recipients\":[\"jane@example.com\"], \"processingTimeMillis\":546, \"reportingMTA\":\"a8-70.smtp-out.amazonses.com\", \"smtpResponse\":\"250 ok: Message 64111812 accepted\", \"remoteMtaIp\":\"127.0.2.0\" } }"; private const string NotJson = "some string"; } diff --git a/Tests/SesNotifications.App.Tests/Services/SearchServiceTests.cs b/Tests/SesNotifications.App.Tests/Services/SearchServiceTests.cs index 1340b5e..0c0ebf6 100644 --- a/Tests/SesNotifications.App.Tests/Services/SearchServiceTests.cs +++ b/Tests/SesNotifications.App.Tests/Services/SearchServiceTests.cs @@ -24,7 +24,8 @@ public void VerifyFindDeliveries(string email, bool expectedMailQuery) .Returns(new List()); var mockLogger = new Mock>(MockBehavior.Loose); - var service = new SearchService(null, null, null, mockSesDeliveries.Object, null, null, mockLogger.Object); + var service = new SearchService(null, null, null, mockSesDeliveries.Object, null, null, null, + mockLogger.Object); service.FindDeliveries(email, DateTime.UtcNow.AddDays(-30), DateTime.UtcNow.AddDays(1)); @@ -48,7 +49,8 @@ public void VerifyFindBounces(string email, bool expectedMailQuery) .Returns(new List()); var mockLogger = new Mock>(MockBehavior.Loose); - var service = new SearchService(null, mockSesBounces.Object, null, null, null, null, mockLogger.Object); + var service = new SearchService(null, mockSesBounces.Object, null, null, null, null, null, + mockLogger.Object); service.FindBounces(email, DateTime.UtcNow.AddDays(-30), DateTime.UtcNow.AddDays(1)); @@ -72,7 +74,8 @@ public void VerifyFindComplaints(string email, bool expectedMailQuery) .Returns(new List()); var mockLogger = new Mock>(MockBehavior.Loose); - var service = new SearchService(null, null, mockSesComplaints.Object, null, null, null, mockLogger.Object); + var service = new SearchService(null, null, mockSesComplaints.Object, null, null, null, null, + mockLogger.Object); service.FindComplaints(email, DateTime.UtcNow.AddDays(-30), DateTime.UtcNow.AddDays(1)); @@ -91,7 +94,8 @@ public void VerifyFindRaw() .Returns(new List()); var mockLogger = new Mock>(MockBehavior.Loose); - var service = new SearchService(mockNotifications.Object, null, null, null, null, null, mockLogger.Object); + var service = new SearchService(mockNotifications.Object, null, null, null, null, null, null, + mockLogger.Object); service.FindRaw(DateTime.UtcNow.AddDays(-30), DateTime.UtcNow.AddDays(1)); @@ -105,7 +109,8 @@ public void VerifyFindOneRaw() mockNotifications.Setup(x => x.FindById(1)).Returns(new SesNotification()); var mockLogger = new Mock>(MockBehavior.Loose); - var service = new SearchService(mockNotifications.Object, null, null, null, null, null, mockLogger.Object); + var service = new SearchService(mockNotifications.Object, null, null, null, null, null, null, + mockLogger.Object); service.FindRaw(1);