using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using LCU.API.Internal.MemberPreferencesApi.Services; using LCU.API.Internal.MemberPreferencesApiLib.Repositories; using LCU.API.Internal.MemberPreferencesApi.Models.EmailPolicies; using LCU.Library.Internal.MemberPreferencesApiModels.Entities; using LCU.Library.Internal.MemberPreferencesApiModels.Enums; using Moq; using Newtonsoft.Json; using NUnit.Framework; namespace LCU.API.Internal.MemberPreferencesApi.UnitTests.Services { public class MemberEmailServiceTest { private readonly IMemberEmailService _service; private readonly FakeUnifiedOptInRepository _repository; private readonly Mock _mockIntegrationService; private readonly Mock _mockPreferenceService; private readonly Mock[] _policies = { new Mock(), new Mock() }; public MemberEmailServiceTest() { var unitOfWork = new FakeUnitOfWork(); _repository = new FakeUnifiedOptInRepository(unitOfWork); _mockIntegrationService = new Mock(); _mockPreferenceService = new Mock(); _service = new MemberEmailService(_repository, unitOfWork, _policies.Select(p => p.Object), _mockPreferenceService.Object, _mockIntegrationService.Object); } private static readonly (DateTime? firstSent, DateTime? lastSent, EmailStatus PriorStatus, int count)[] PreUpdateStateScenarios = { (null, null, EmailStatus.FailingDelivery, 1), (DateTime.Now.AddHours(-2), null, EmailStatus.FailingDelivery, 2), (null, DateTime.Now.AddHours(-3), EmailStatus.FailingDelivery, 3), (DateTime.Now.AddHours(-4), DateTime.Now.AddHours(-4), EmailStatus.FailingDelivery, 4), (null, null, EmailStatus.DeliveredSuccessfully, 5), (DateTime.Now.AddHours(-6), null, EmailStatus.DeliveredSuccessfully, 6), (null, DateTime.Now.AddHours(-7), EmailStatus.DeliveredSuccessfully, 7), (DateTime.Now.AddHours(-8), DateTime.Now.AddHours(-8), EmailStatus.DeliveredSuccessfully, 8), }; private static readonly (bool isUnifiedOptIn, bool matchesUnifiedOptin, bool matchesENotification, bool matchesEstatement)[] MatchScenarios = { (true, true, true, true), (true, true, true, false), (true, true, false, true), (true, true, false, false), (false, false, true, true), (false, false, true, false), (false, false, false, true), (false, false, true, true), (false, false, true, false), (false, false, false, true), }; private static IEnumerable CombinedScenarios() { foreach (var match in MatchScenarios) { foreach (var state in PreUpdateStateScenarios) { yield return new UpdateScenario(state, match); } } } private static readonly object[] UpdateScenarios = CombinedScenarios().ToArray(); private static readonly Random Random = new Random(DateTime.Now.Millisecond); private static string EmailAddressFactory() { static char RandomLetter() => (char)('a' + Random.Next(0, 26)); return string.Concat ( Enumerable.Repeat>(RandomLetter, Random.Next(5, 50)).Select(f => f()) ) .RandomizeCase(); } [Test] public async Task EmailSucceededToSend_UserOptInSettingsNotFound_ReturnsFalse() { var userId = Guid.NewGuid().ToString(); _repository.MemberOptInSettings.Remove(userId); var updated = await _service.EmailSucceededToSend(userId, Guid.NewGuid().ToString()); Assert.IsFalse(updated); } [Test] public async Task EmailSucceededToSend_NoEmailMatches_ReturnsFalse() { var userId = Guid.NewGuid().ToString(); var email = EmailAddressFactory(); _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UnifiedOptInEmail = EmailAddressFactory(), EStatementsEmail = EmailAddressFactory(), ENotificationsEmail = EmailAddressFactory() }; var updated = await _service.EmailSucceededToSend(userId, email); Assert.IsFalse(updated); } [Test] public async Task EmailSucceededToSend_NullEmailNoMatches_ReturnsFalse() { var userId = Guid.NewGuid().ToString(); string email = null; _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UnifiedOptInEmail = EmailAddressFactory(), EStatementsEmail = EmailAddressFactory(), ENotificationsEmail = EmailAddressFactory() }; var updated = await _service.EmailSucceededToSend(userId, email); Assert.IsFalse(updated); } [TestCaseSource(nameof(UpdateScenarios))] public async Task EmailSucceededToSend_EmailMatchesRegardlessOfCase_ReturnsTrue(UpdateScenario scenario) { var userId = Guid.NewGuid().ToString(); var email = EmailAddressFactory(); var unmatchedEmail = EmailAddressFactory(); _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UnifiedOptInEmail = scenario.MatchScenario.matchesUnifiedOptin ? email.RandomizeCase() : unmatchedEmail, EStatementsEmail = scenario.MatchScenario.matchesEstatement ? email.RandomizeCase() : unmatchedEmail, ENotificationsEmail = scenario.MatchScenario.matchesENotification ? email.RandomizeCase() : unmatchedEmail }; var updated = await _service.EmailSucceededToSend(userId, email); Assert.IsTrue(updated); } [TestCaseSource(nameof(UpdateScenarios))] public async Task EmailSucceededToSend_NullEmailMatched_ReturnsTrue(UpdateScenario scenario) { var userId = Guid.NewGuid().ToString(); string email = null; var unmatchedEmail = EmailAddressFactory(); _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UnifiedOptInEmail = scenario.MatchScenario.matchesUnifiedOptin ? null : unmatchedEmail, EStatementsEmail = scenario.MatchScenario.matchesEstatement ? null : unmatchedEmail, ENotificationsEmail = scenario.MatchScenario.matchesENotification ? null : unmatchedEmail }; var updated = await _service.EmailSucceededToSend(userId, email); Assert.IsTrue(updated); } [TestCaseSource(nameof(UpdateScenarios))] public async Task EmailSucceededToSend_EmailMatchesRegardlessOfCase_UpdatesTrackingOnMatched(UpdateScenario scenario) { var userId = Guid.NewGuid().ToString(); var email = EmailAddressFactory(); var unmatchedEmail = EmailAddressFactory(); var (isUniversalOptin, matchesUnifiedOptin, matchesEstatement, matchesENotification) = scenario.MatchScenario; var (firstSent, lastSent, status, count) = scenario.PriorState; _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UnifiedOptInEmail = matchesUnifiedOptin ? email.RandomizeCase() : unmatchedEmail, UnifiedOptInEmailFirstSent = firstSent, UnifiedOptInEmailLastSent = lastSent, UnifiedOptInEmailStatus = status, UnifiedOptInEmailStatusCount = count, EStatementsEmail = matchesEstatement ? email.RandomizeCase() : unmatchedEmail, EStatementsEmailFirstSent = firstSent, EStatementsEmailLastSent = lastSent, EStatementsEmailStatus = status, EStatementsEmailStatusCount = count, ENotificationsEmail = matchesENotification ? email.RandomizeCase() : unmatchedEmail, ENotificationsEmailFirstSent = firstSent, ENotificationsEmailLastSent = lastSent, ENotificationsEmailStatus = status, ENotificationsEmailStatusCount = count }; var actionStart = DateTime.Now; await _service.EmailSucceededToSend(userId, email); var elapsedActionTime = DateTime.Now - actionStart; var updatedSettings = _repository.MemberOptInSettings[userId]; if (matchesUnifiedOptin) { Assert.IsNull(updatedSettings.UnifiedOptInEmailFirstSent); Assert.That(updatedSettings.UnifiedOptInEmailLastSent, Is.EqualTo(actionStart).Within(elapsedActionTime)); Assert.AreEqual(EmailStatus.DeliveredSuccessfully, updatedSettings.UnifiedOptInEmailStatus); Assert.AreEqual(0, updatedSettings.UnifiedOptInEmailStatusCount); } if (matchesEstatement) { Assert.IsNull(updatedSettings.EStatementsEmailFirstSent); Assert.That(updatedSettings.EStatementsEmailLastSent, Is.EqualTo(actionStart).Within(elapsedActionTime)); Assert.AreEqual(EmailStatus.DeliveredSuccessfully, updatedSettings.EStatementsEmailStatus); Assert.AreEqual(0, updatedSettings.EStatementsEmailStatusCount); } if (matchesENotification) { Assert.IsNull(updatedSettings.ENotificationsEmailFirstSent); Assert.That(updatedSettings.ENotificationsEmailLastSent, Is.EqualTo(actionStart).Within(elapsedActionTime)); Assert.AreEqual(EmailStatus.DeliveredSuccessfully, updatedSettings.ENotificationsEmailStatus); Assert.AreEqual(0, updatedSettings.ENotificationsEmailStatusCount); } } [TestCaseSource(nameof(UpdateScenarios))] public async Task EmailSucceededToSend_NullEmailMatched_UpdatesTrackingOnMatched(UpdateScenario scenario) { var userId = Guid.NewGuid().ToString(); string email = null; var unmatchedEmail = EmailAddressFactory(); var (isUniversalOptin, matchesUnifiedOptin, matchesEstatement, matchesENotification) = scenario.MatchScenario; var (firstSent, lastSent, status, count) = scenario.PriorState; _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UnifiedOptInEmail = matchesUnifiedOptin ? null : unmatchedEmail, UnifiedOptInEmailFirstSent = firstSent, UnifiedOptInEmailLastSent = lastSent, UnifiedOptInEmailStatus = status, UnifiedOptInEmailStatusCount = count, EStatementsEmail = matchesEstatement ? null : unmatchedEmail, EStatementsEmailFirstSent = firstSent, EStatementsEmailLastSent = lastSent, EStatementsEmailStatus = status, EStatementsEmailStatusCount = count, ENotificationsEmail = matchesENotification ? null : unmatchedEmail, ENotificationsEmailFirstSent = firstSent, ENotificationsEmailLastSent = lastSent, ENotificationsEmailStatus = status, ENotificationsEmailStatusCount = count }; var actionStart = DateTime.Now; await _service.EmailSucceededToSend(userId, email); var elapsedActionTime = DateTime.Now - actionStart; var updatedSettings = _repository.MemberOptInSettings[userId]; if (matchesUnifiedOptin) { Assert.IsNull(updatedSettings.UnifiedOptInEmailFirstSent); Assert.That(updatedSettings.UnifiedOptInEmailLastSent, Is.EqualTo(actionStart).Within(elapsedActionTime)); Assert.AreEqual(EmailStatus.DeliveredSuccessfully, updatedSettings.UnifiedOptInEmailStatus); Assert.AreEqual(0, updatedSettings.UnifiedOptInEmailStatusCount); } if (matchesEstatement) { Assert.IsNull(updatedSettings.EStatementsEmailFirstSent); Assert.That(updatedSettings.EStatementsEmailLastSent, Is.EqualTo(actionStart).Within(elapsedActionTime)); Assert.AreEqual(EmailStatus.DeliveredSuccessfully, updatedSettings.EStatementsEmailStatus); Assert.AreEqual(0, updatedSettings.EStatementsEmailStatusCount); } if (matchesENotification) { Assert.IsNull(updatedSettings.ENotificationsEmailFirstSent); Assert.That(updatedSettings.ENotificationsEmailLastSent, Is.EqualTo(actionStart).Within(elapsedActionTime)); Assert.AreEqual(EmailStatus.DeliveredSuccessfully, updatedSettings.ENotificationsEmailStatus); Assert.AreEqual(0, updatedSettings.ENotificationsEmailStatusCount); } } [TestCaseSource(nameof(UpdateScenarios))] public async Task EmailSucceededToSend_EmailMatchesRegardlessOfCase_DoesNotUpdateTrackingOnUnmatched( UpdateScenario scenario) { var userId = Guid.NewGuid().ToString(); var email = EmailAddressFactory(); var unmatchedEmail = EmailAddressFactory(); var (isUniversalOptin, matchesUnifiedOptin, matchesEstatement, matchesENotification) = scenario.MatchScenario; var (firstSent, lastSent, status, count) = scenario.PriorState; _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UnifiedOptInEmail = matchesUnifiedOptin ? email.RandomizeCase() : unmatchedEmail, UnifiedOptInEmailFirstSent = firstSent, UnifiedOptInEmailLastSent = lastSent, UnifiedOptInEmailStatus = status, UnifiedOptInEmailStatusCount = count, EStatementsEmail = matchesEstatement ? email.RandomizeCase() : unmatchedEmail, EStatementsEmailFirstSent = firstSent, EStatementsEmailLastSent = lastSent, EStatementsEmailStatus = status, EStatementsEmailStatusCount = count, ENotificationsEmail = matchesENotification ? email.RandomizeCase() : unmatchedEmail, ENotificationsEmailFirstSent = firstSent, ENotificationsEmailLastSent = lastSent, ENotificationsEmailStatus = status, ENotificationsEmailStatusCount = count }; await _service.EmailSucceededToSend(userId, email); var updatedSettings = _repository.MemberOptInSettings[userId]; if (!matchesUnifiedOptin) { Assert.AreEqual(firstSent, updatedSettings.UnifiedOptInEmailFirstSent); Assert.AreEqual(lastSent, updatedSettings.UnifiedOptInEmailLastSent); Assert.AreEqual(status, updatedSettings.UnifiedOptInEmailStatus); Assert.AreEqual(count, updatedSettings.UnifiedOptInEmailStatusCount); } if (!matchesEstatement) { Assert.AreEqual(firstSent, updatedSettings.EStatementsEmailFirstSent); Assert.AreEqual(lastSent, updatedSettings.EStatementsEmailLastSent); Assert.AreEqual(status, updatedSettings.EStatementsEmailStatus); Assert.AreEqual(count, updatedSettings.EStatementsEmailStatusCount); } if (!matchesENotification) { Assert.AreEqual(firstSent, updatedSettings.ENotificationsEmailFirstSent); Assert.AreEqual(lastSent, updatedSettings.ENotificationsEmailLastSent); Assert.AreEqual(status, updatedSettings.ENotificationsEmailStatus); Assert.AreEqual(count, updatedSettings.ENotificationsEmailStatusCount); } } [Test] public async Task EmailFailedToSend_UserOptInSettingsNotFound_ReturnsFalse() { var userId = Guid.NewGuid().ToString(); _repository.MemberOptInSettings.Remove(userId); var updated = await _service.EmailFailedToSend(userId, EmailAddressFactory(), true); Assert.IsFalse(updated); } [Test] public async Task EmailFailedToSend_EmailMatchesNoOptinEmailPreference_ReturnsFalse() { var userId = Guid.NewGuid().ToString(); var email = EmailAddressFactory(); _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UnifiedOptInEmail = EmailAddressFactory(), EStatementsEmail = EmailAddressFactory(), ENotificationsEmail = EmailAddressFactory() }; var updated = await _service.EmailFailedToSend(userId, email, true); Assert.IsFalse(updated); } [Test] public async Task EmailFailedToSend_NullEmailMatchesNoOptinEmailPreference_ReturnsFalse() { var userId = Guid.NewGuid().ToString(); string email = null; _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UnifiedOptInEmail = EmailAddressFactory(), EStatementsEmail = EmailAddressFactory(), ENotificationsEmail = EmailAddressFactory() }; var updated = await _service.EmailFailedToSend(userId, email, true); Assert.IsFalse(updated); } [TestCaseSource(nameof(UpdateScenarios))] public async Task EmailFailedToSend_EmailMatchesRegardlessOfCase_ReturnsTrue(UpdateScenario scenario) { var userId = Guid.NewGuid().ToString(); var email = EmailAddressFactory(); var unmatchedEmail = EmailAddressFactory(); var (isUniversalOptin, matchesUnifiedOptin, matchesEstatement, matchesENotification) = scenario.MatchScenario; var (firstSent, lastSent, status, count) = scenario.PriorState; _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UniversalOptIn = isUniversalOptin ? "Electronic" : null, UnifiedOptInEmail = matchesUnifiedOptin && isUniversalOptin ? email.RandomizeCase() : unmatchedEmail, UnifiedOptInEmailFirstSent = isUniversalOptin ? DateTime.Now : firstSent, UnifiedOptInEmailLastSent = isUniversalOptin ? DateTime.Now : lastSent, UnifiedOptInEmailStatus = status, UnifiedOptInEmailStatusCount = count, EStatementsEmail = matchesEstatement && !isUniversalOptin ? email.RandomizeCase() : unmatchedEmail, EStatementsEmailFirstSent = firstSent, EStatementsEmailLastSent = lastSent, EStatementsEmailStatus = status, EStatementsEmailStatusCount = count, ENotificationsEmail = matchesENotification && !isUniversalOptin ? email.RandomizeCase() : unmatchedEmail, ENotificationsEmailFirstSent = firstSent, ENotificationsEmailLastSent = lastSent, ENotificationsEmailStatus = status, ENotificationsEmailStatusCount = count }; var updated = await _service.EmailFailedToSend(userId, email, true); Assert.IsTrue(updated); } [TestCaseSource(nameof(UpdateScenarios))] public async Task EmailFailedToSend_NullEmailMatched_ReturnsTrue(UpdateScenario scenario) { var userId = Guid.NewGuid().ToString(); string email = null; var unmatchedEmail = EmailAddressFactory(); var (isUniversalOptin, matchesUnifiedOptin, matchesEstatement, matchesENotification) = scenario.MatchScenario; var (firstSent, lastSent, status, count) = scenario.PriorState; _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UniversalOptIn = isUniversalOptin ? "Electronic" : null, UnifiedOptInEmail = matchesUnifiedOptin && isUniversalOptin ? null : unmatchedEmail, UnifiedOptInEmailFirstSent = firstSent, UnifiedOptInEmailLastSent = lastSent, UnifiedOptInEmailStatus = status, UnifiedOptInEmailStatusCount = count, EStatementsEmail = matchesEstatement && !isUniversalOptin ? null : unmatchedEmail, EStatementsEmailFirstSent = firstSent, EStatementsEmailLastSent = lastSent, EStatementsEmailStatus = status, EStatementsEmailStatusCount = count, ENotificationsEmail = matchesENotification && !isUniversalOptin ? null : unmatchedEmail, ENotificationsEmailFirstSent = firstSent, ENotificationsEmailLastSent = lastSent, ENotificationsEmailStatus = status, ENotificationsEmailStatusCount = count }; var updated = await _service.EmailFailedToSend(userId, email, true); var updatedSettings = _repository.MemberOptInSettings[userId]; Assert.IsTrue(updated); } [TestCaseSource(nameof(UpdateScenarios))] public async Task EmailFailedToSend_EmailMatchesRegardlessOfCase_UpdatesTrackingOnMatched(UpdateScenario scenario) { var userId = Guid.NewGuid().ToString(); var email = EmailAddressFactory(); var unmatchedEmail = EmailAddressFactory(); var (isUniversalOptin, matchesUnifiedOptin, matchesEstatement, matchesENotification) = scenario.MatchScenario; var (firstSent, lastSent, status, count) = scenario.PriorState; _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UniversalOptIn = isUniversalOptin ? "Electronic" : null, UnifiedOptInEmail = matchesUnifiedOptin && isUniversalOptin ? email.RandomizeCase() : unmatchedEmail, UnifiedOptInEmailFirstSent = firstSent, UnifiedOptInEmailLastSent = lastSent, UnifiedOptInEmailStatus = status, UnifiedOptInEmailStatusCount = count, EStatementsEmail = matchesEstatement && !isUniversalOptin ? email.RandomizeCase() : unmatchedEmail, EStatementsEmailFirstSent = firstSent, EStatementsEmailLastSent = lastSent, EStatementsEmailStatus = status, EStatementsEmailStatusCount = count, ENotificationsEmail = matchesENotification && !isUniversalOptin ? email.RandomizeCase() : unmatchedEmail, ENotificationsEmailFirstSent = firstSent, ENotificationsEmailLastSent = lastSent, ENotificationsEmailStatus = status, ENotificationsEmailStatusCount = count }; var actionStart = DateTime.Now; await _service.EmailFailedToSend(userId, email, true); var elapsedActionTime = DateTime.Now - actionStart; var updatedSettings = _repository.MemberOptInSettings[userId]; var updatedFirstSent = firstSent is null ? Is.EqualTo(actionStart).Within(elapsedActionTime) : Is.EqualTo(firstSent); if (matchesUnifiedOptin && isUniversalOptin) { Assert.That(updatedSettings.UnifiedOptInEmailFirstSent, updatedFirstSent); Assert.That(updatedSettings.UnifiedOptInEmailLastSent, Is.EqualTo(actionStart).Within(elapsedActionTime)); Assert.AreEqual(EmailStatus.FailingDelivery, updatedSettings.UnifiedOptInEmailStatus); Assert.AreEqual(count + 1, updatedSettings.UnifiedOptInEmailStatusCount); } if (matchesEstatement && !isUniversalOptin) { Assert.That(updatedSettings.EStatementsEmailFirstSent, updatedFirstSent); Assert.That(updatedSettings.EStatementsEmailLastSent, Is.EqualTo(actionStart).Within(elapsedActionTime)); Assert.AreEqual(EmailStatus.FailingDelivery, updatedSettings.EStatementsEmailStatus); Assert.AreEqual(count + 1, updatedSettings.EStatementsEmailStatusCount); } if (matchesENotification && !isUniversalOptin) { Assert.That(updatedSettings.ENotificationsEmailFirstSent, updatedFirstSent); Assert.That(updatedSettings.ENotificationsEmailLastSent, Is.EqualTo(actionStart).Within(elapsedActionTime)); Assert.AreEqual(EmailStatus.FailingDelivery, updatedSettings.ENotificationsEmailStatus); Assert.AreEqual(count + 1, updatedSettings.ENotificationsEmailStatusCount); } } [TestCaseSource(nameof(UpdateScenarios))] public async Task EmailFailedToSend_NullEmailMatched_UpdatesTrackingOnMatched(UpdateScenario scenario) { var userId = Guid.NewGuid().ToString(); string email = null; var unmatchedEmail = EmailAddressFactory(); var (isUniversalOptin, matchesUnifiedOptin, matchesEstatement, matchesENotification) = scenario.MatchScenario; var (firstSent, lastSent, status, count) = scenario.PriorState; _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UniversalOptIn = isUniversalOptin ? "Electronic" : null, UnifiedOptInEmail = matchesUnifiedOptin && isUniversalOptin ? null : unmatchedEmail, UnifiedOptInEmailFirstSent = firstSent, UnifiedOptInEmailLastSent = lastSent, UnifiedOptInEmailStatus = status, UnifiedOptInEmailStatusCount = count, EStatementsEmail = matchesEstatement && !isUniversalOptin ? null : unmatchedEmail, EStatementsEmailFirstSent = firstSent, EStatementsEmailLastSent = lastSent, EStatementsEmailStatus = status, EStatementsEmailStatusCount = count, ENotificationsEmail = matchesENotification && !isUniversalOptin ? null : unmatchedEmail, ENotificationsEmailFirstSent = firstSent, ENotificationsEmailLastSent = lastSent, ENotificationsEmailStatus = status, ENotificationsEmailStatusCount = count }; var actionStart = DateTime.Now; await _service.EmailFailedToSend(userId, email, true); var elapsedActionTime = DateTime.Now - actionStart; var updatedSettings = _repository.MemberOptInSettings[userId]; var updatedFirstSent = firstSent is null ? Is.EqualTo(actionStart).Within(elapsedActionTime) : Is.EqualTo(firstSent); if (matchesUnifiedOptin && isUniversalOptin) { Assert.That(updatedSettings.UnifiedOptInEmailFirstSent, updatedFirstSent); Assert.That(updatedSettings.UnifiedOptInEmailLastSent, Is.EqualTo(actionStart).Within(elapsedActionTime)); Assert.AreEqual(EmailStatus.FailingDelivery, updatedSettings.UnifiedOptInEmailStatus); Assert.AreEqual(count + 1, updatedSettings.UnifiedOptInEmailStatusCount); } if (matchesEstatement && !isUniversalOptin) { Assert.That(updatedSettings.EStatementsEmailFirstSent, updatedFirstSent); Assert.That(updatedSettings.EStatementsEmailLastSent, Is.EqualTo(actionStart).Within(elapsedActionTime)); Assert.AreEqual(EmailStatus.FailingDelivery, updatedSettings.EStatementsEmailStatus); Assert.AreEqual(count + 1, updatedSettings.EStatementsEmailStatusCount); } if (matchesENotification && !isUniversalOptin) { Assert.That(updatedSettings.ENotificationsEmailFirstSent, updatedFirstSent); Assert.That(updatedSettings.ENotificationsEmailLastSent, Is.EqualTo(actionStart).Within(elapsedActionTime)); Assert.AreEqual(EmailStatus.FailingDelivery, updatedSettings.ENotificationsEmailStatus); Assert.AreEqual(count + 1, updatedSettings.ENotificationsEmailStatusCount); } } [TestCaseSource(nameof(UpdateScenarios))] public async Task EmailFailedToSend_EmailMatchesRegardlessOfCase_DoesNotUpdateTrackingOnUnmatched(UpdateScenario scenario) { var userId = Guid.NewGuid().ToString(); var email = EmailAddressFactory(); var unmatchedEmail = EmailAddressFactory(); var (isUniversalOptin, matchesUnifiedOptin, matchesEstatement, matchesENotification) = scenario.MatchScenario; var (firstSent, lastSent, status, count) = scenario.PriorState; _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UnifiedOptInEmail = matchesUnifiedOptin && isUniversalOptin ? email.RandomizeCase() : unmatchedEmail, UnifiedOptInEmailFirstSent = firstSent, UnifiedOptInEmailLastSent = lastSent, UnifiedOptInEmailStatus = status, UnifiedOptInEmailStatusCount = count, EStatementsEmail = matchesEstatement && !isUniversalOptin ? email.RandomizeCase() : unmatchedEmail, EStatementsEmailFirstSent = firstSent, EStatementsEmailLastSent = lastSent, EStatementsEmailStatus = status, EStatementsEmailStatusCount = count, ENotificationsEmail = matchesENotification && !isUniversalOptin ? email.RandomizeCase() : unmatchedEmail, ENotificationsEmailFirstSent = firstSent, ENotificationsEmailLastSent = lastSent, ENotificationsEmailStatus = status, ENotificationsEmailStatusCount = count }; await _service.EmailFailedToSend(userId, email, true); var updatedSettings = _repository.MemberOptInSettings[userId]; if (!matchesUnifiedOptin) { Assert.AreEqual(firstSent, updatedSettings.UnifiedOptInEmailFirstSent); Assert.AreEqual(lastSent, updatedSettings.UnifiedOptInEmailLastSent); Assert.AreEqual(status, updatedSettings.UnifiedOptInEmailStatus); Assert.AreEqual(count, updatedSettings.UnifiedOptInEmailStatusCount); } if (!matchesEstatement) { Assert.AreEqual(firstSent, updatedSettings.EStatementsEmailFirstSent); Assert.AreEqual(lastSent, updatedSettings.EStatementsEmailLastSent); Assert.AreEqual(status, updatedSettings.EStatementsEmailStatus); Assert.AreEqual(count, updatedSettings.EStatementsEmailStatusCount); } if (!matchesENotification) { Assert.AreEqual(firstSent, updatedSettings.ENotificationsEmailFirstSent); Assert.AreEqual(lastSent, updatedSettings.ENotificationsEmailLastSent); Assert.AreEqual(status, updatedSettings.ENotificationsEmailStatus); Assert.AreEqual(count, updatedSettings.ENotificationsEmailStatusCount); } } [TestCaseSource(nameof(UpdateScenarios))] public async Task EmailFailedToSend_NullEmailMatched_DoesNotUpdateTrackingOnUnmatched(UpdateScenario scenario) { var userId = Guid.NewGuid().ToString(); string email = null; var unmatchedEmail = EmailAddressFactory(); var (isUniversalOptin, matchesUnifiedOptin, matchesEstatement, matchesENotification) = scenario.MatchScenario; var (firstSent, lastSent, status, count) = scenario.PriorState; _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UnifiedOptInEmail = matchesUnifiedOptin ? null : unmatchedEmail, UnifiedOptInEmailFirstSent = firstSent, UnifiedOptInEmailLastSent = lastSent, UnifiedOptInEmailStatus = status, UnifiedOptInEmailStatusCount = count, EStatementsEmail = matchesEstatement ? null : unmatchedEmail, EStatementsEmailFirstSent = firstSent, EStatementsEmailLastSent = lastSent, EStatementsEmailStatus = status, EStatementsEmailStatusCount = count, ENotificationsEmail = matchesENotification ? null : unmatchedEmail, ENotificationsEmailFirstSent = firstSent, ENotificationsEmailLastSent = lastSent, ENotificationsEmailStatus = status, ENotificationsEmailStatusCount = count }; await _service.EmailFailedToSend(userId, email, true); var updatedSettings = _repository.MemberOptInSettings[userId]; if (!matchesUnifiedOptin) { Assert.AreEqual(firstSent, updatedSettings.UnifiedOptInEmailFirstSent); Assert.AreEqual(lastSent, updatedSettings.UnifiedOptInEmailLastSent); Assert.AreEqual(status, updatedSettings.UnifiedOptInEmailStatus); Assert.AreEqual(count, updatedSettings.UnifiedOptInEmailStatusCount); } if (!matchesEstatement) { Assert.AreEqual(firstSent, updatedSettings.EStatementsEmailFirstSent); Assert.AreEqual(lastSent, updatedSettings.EStatementsEmailLastSent); Assert.AreEqual(status, updatedSettings.EStatementsEmailStatus); Assert.AreEqual(count, updatedSettings.EStatementsEmailStatusCount); } if (!matchesENotification) { Assert.AreEqual(firstSent, updatedSettings.ENotificationsEmailFirstSent); Assert.AreEqual(lastSent, updatedSettings.ENotificationsEmailLastSent); Assert.AreEqual(status, updatedSettings.ENotificationsEmailStatus); Assert.AreEqual(count, updatedSettings.ENotificationsEmailStatusCount); } } [Test] public async Task EmailFailedToSend_PolicyMakesUpdate_ReturnsTrue() { var userId = Guid.NewGuid().ToString(); _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UserId = userId, EStatementsEmail = "" }; _policies[1] .Setup(p => p.Apply(It.Is(s => s.UserId == userId), It.IsAny())) .ReturnsAsync(true); var updated = await _service.EmailFailedToSend(userId, string.Empty, true); Assert.IsTrue(updated); } [Test] public async Task EmailFailedToSend_PolicyMakesUpdate_FeatureFlagReturnsFalse() { var userId = Guid.NewGuid().ToString(); _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UserId = userId }; _policies[1] .Setup(p => p.Apply(It.Is(s => s.UserId == userId), It.IsAny())) .ReturnsAsync(false); var updated = await _service.EmailFailedToSend(userId, string.Empty, false); Assert.IsFalse(updated); } [Test] public async Task EmailFailedToSend_PolicyMakesUpdate_UpdatesTracking() { var userId = Guid.NewGuid().ToString(); _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UserId = userId }; _policies[1] .Setup(p => p.Apply(It.Is(s => s.UserId == userId), It.IsAny())) .Callback((s, b) => s.LastUpdatedBy = nameof(EmailFailedToSend_PolicyMakesUpdate_UpdatesTracking)) .ReturnsAsync(true); await _service.EmailFailedToSend(userId, string.Empty, true); var updatedSettings = _repository.MemberOptInSettings[userId]; Assert.AreEqual(nameof(EmailFailedToSend_PolicyMakesUpdate_UpdatesTracking), updatedSettings.LastUpdatedBy); } [Test] public async Task EmailFailedToSend_AllCases_EvaluatesAllFailurePolicies() { var userId = Guid.NewGuid().ToString(); _repository.MemberOptInSettings[userId] = new MemberOptInSettings { UserId = userId }; _policies[1] .Setup(p => p.Apply(It.Is(s => s.UserId == userId), It.IsAny())) .ReturnsAsync(true); await _service.EmailFailedToSend(userId, string.Empty, true); foreach (var policy in _policies) { policy.Verify(p => p.Apply(It.Is(s => s.UserId == userId), It.IsAny())); } } private interface IObservableRepository { void ApplyChanges(TKey key, TValue value); } private class FakeUnifiedOptInRepository : IUnifiedOptInRepository, IObservableRepository { public IDictionary MemberOptInSettings { get; } = new Dictionary(); private readonly IRepositoryObserver _observer; public FakeUnifiedOptInRepository(IRepositoryObserver observer) { _observer = observer; _observer.Observe(this); } public Task GetMemberOptInSettings(string userId) { if (!MemberOptInSettings.TryGetValue(userId, out var optInSettings)) { return Task.FromResult(null); } var clonedSettings = JsonConvert.DeserializeObject ( JsonConvert.SerializeObject(optInSettings) ); _observer.Track(userId, clonedSettings); return Task.FromResult(clonedSettings); } public Task GetMemberOptInSettingsNoTracking(string userId) { throw new NotImplementedException(); } public Task GetOrCreateMemberOptinSettings(string userId) { throw new NotImplementedException(); } public Task> GetAllMemberOptInSettings(List memberNumbers) { throw new NotImplementedException(); } public void ApplyChanges(string key, MemberOptInSettings value) { MemberOptInSettings[key] = value; } } private interface IRepositoryObserver { void Track(TKey key, TValue value); void Observe(IObservableRepository subject); } private class FakeUnitOfWork : IUnitOfWork, IRepositoryObserver { private readonly IDictionary _activeRecords = new Dictionary(); private IObservableRepository _subject; public Task CompleteAsync() { foreach (var record in _activeRecords) { _subject.ApplyChanges(record.Key, record.Value); } return Task.CompletedTask; } public Task CompleteAsync(string operatorId, DateTime dateAdded, UserType userType) { return CompleteAsync(); } public void Track(string key, MemberOptInSettings value) { _activeRecords[key] = value; } public void Observe(IObservableRepository subject) { _subject = subject; } } public struct UpdateScenario { public (DateTime? firstSent, DateTime? lastSent, EmailStatus status, int count) PriorState { get; } public (bool isUniversalOptIn, bool matchesUnifiedOptin, bool matchesENotification, bool matchesEstatement) MatchScenario { get; } public UpdateScenario((DateTime? firstSent, DateTime? lastSent, EmailStatus status, int count) priorState, (bool isUniveralsOptin, bool matchesUnifiedOptin, bool matchesENotification, bool matchesEstatement) matchScenario) { PriorState = priorState; MatchScenario = matchScenario; } } } } public static class RandomCaseExtension { private static readonly Random Random = new Random(DateTime.Now.Millisecond); public static string RandomizeCase(this string s) { static char RandomCase(char c) => Random.Next() % 2 == 0 ? char.ToUpper(c) : char.ToLower(c); return string.Concat(s.Select(RandomCase)); } }