diff --git a/src/Altinn.Notifications.Core/Enums/NotificationChannel.cs b/src/Altinn.Notifications.Core/Enums/NotificationChannel.cs
index 49e30aa8..c49014bb 100644
--- a/src/Altinn.Notifications.Core/Enums/NotificationChannel.cs
+++ b/src/Altinn.Notifications.Core/Enums/NotificationChannel.cs
@@ -13,5 +13,15 @@ public enum NotificationChannel
///
/// The selected channel for the notification is SMS.
///
- Sms
+ Sms,
+
+ ///
+ /// The selected channel for the notification is email and to use SMS if email is not available.
+ ///
+ EmailPreferred,
+
+ ///
+ /// The selected channel for the notification is SMS and to use email if SMS is not available.
+ ///
+ SmsPreferred
}
diff --git a/src/Altinn.Notifications.Core/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.Notifications.Core/Extensions/ServiceCollectionExtensions.cs
index c3004c28..47bf5f31 100644
--- a/src/Altinn.Notifications.Core/Extensions/ServiceCollectionExtensions.cs
+++ b/src/Altinn.Notifications.Core/Extensions/ServiceCollectionExtensions.cs
@@ -33,6 +33,7 @@ public static void AddCoreServices(this IServiceCollection services, IConfigurat
.AddSingleton()
.AddSingleton()
.AddSingleton()
+ .AddSingleton()
.AddSingleton()
.AddSingleton()
.AddSingleton()
diff --git a/src/Altinn.Notifications.Core/Services/ContactPointService.cs b/src/Altinn.Notifications.Core/Services/ContactPointService.cs
index 8170235e..ff506db5 100644
--- a/src/Altinn.Notifications.Core/Services/ContactPointService.cs
+++ b/src/Altinn.Notifications.Core/Services/ContactPointService.cs
@@ -1,4 +1,5 @@
-using Altinn.Notifications.Core.Helpers;
+using Altinn.Notifications.Core.Enums;
+using Altinn.Notifications.Core.Helpers;
using Altinn.Notifications.Core.Integrations;
using Altinn.Notifications.Core.Models;
using Altinn.Notifications.Core.Models.Address;
@@ -84,6 +85,114 @@ await AugmentRecipients(
});
}
+ ///
+ public async Task AddPreferredContactPoints(NotificationChannel channel, List recipients, string? resourceId)
+ {
+ await AugmentRecipients(
+ recipients,
+ resourceId,
+ (recipient, userContactPoints) =>
+ {
+ if (channel == NotificationChannel.EmailPreferred)
+ {
+ AddPreferredOrFallbackContactPoint(
+ recipient,
+ userContactPoints.Email,
+ userContactPoints.MobileNumber,
+ email => new EmailAddressPoint(email),
+ mobile => new SmsAddressPoint(mobile));
+ }
+ else if (channel == NotificationChannel.SmsPreferred)
+ {
+ AddPreferredOrFallbackContactPoint(
+ recipient,
+ userContactPoints.MobileNumber,
+ userContactPoints.Email,
+ mobile => new SmsAddressPoint(mobile),
+ email => new EmailAddressPoint(email));
+ }
+
+ return recipient;
+ },
+ (recipient, orgContactPoints) =>
+ {
+ if (channel == NotificationChannel.EmailPreferred)
+ {
+ AddPreferredOrFallbackContactPointList(
+ recipient,
+ orgContactPoints.EmailList,
+ orgContactPoints.MobileNumberList,
+ e => new EmailAddressPoint(e),
+ m => new SmsAddressPoint(m));
+
+ foreach (var userContact in orgContactPoints.UserContactPoints)
+ {
+ AddPreferredOrFallbackContactPoint(
+ recipient,
+ userContact.Email,
+ userContact.MobileNumber,
+ email => new EmailAddressPoint(email),
+ mobile => new SmsAddressPoint(mobile));
+ }
+ }
+ else if (channel == NotificationChannel.SmsPreferred)
+ {
+ AddPreferredOrFallbackContactPointList(
+ recipient,
+ orgContactPoints.MobileNumberList,
+ orgContactPoints.EmailList,
+ m => new SmsAddressPoint(m),
+ e => new EmailAddressPoint(e));
+
+ foreach (var userContact in orgContactPoints.UserContactPoints)
+ {
+ AddPreferredOrFallbackContactPoint(
+ recipient,
+ userContact.MobileNumber,
+ userContact.Email,
+ mobile => new SmsAddressPoint(mobile),
+ email => new EmailAddressPoint(email));
+ }
+ }
+
+ return recipient;
+ });
+ }
+
+ private static void AddPreferredOrFallbackContactPointList(
+ Recipient recipient,
+ List preferredList,
+ List fallbackList,
+ Func preferredSelector,
+ Func fallbackSelector)
+ {
+ if (preferredList.Count > 0)
+ {
+ recipient.AddressInfo.AddRange(preferredList.Select(preferredSelector).ToList());
+ }
+ else
+ {
+ recipient.AddressInfo.AddRange(fallbackList.Select(fallbackSelector).ToList());
+ }
+ }
+
+ private static void AddPreferredOrFallbackContactPoint(
+ Recipient recipient,
+ TPreferred preferredContact,
+ TFallback fallbackContact,
+ Func preferredSelector,
+ Func fallbackSelector)
+ {
+ if (!string.IsNullOrEmpty(preferredContact?.ToString()))
+ {
+ recipient.AddressInfo.Add(preferredSelector(preferredContact));
+ }
+ else if (!string.IsNullOrEmpty(fallbackContact?.ToString()))
+ {
+ recipient.AddressInfo.Add(fallbackSelector(fallbackContact));
+ }
+ }
+
private async Task> AugmentRecipients(
List recipients,
string? resourceId,
diff --git a/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs b/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs
index f131e67e..e271bd11 100644
--- a/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs
+++ b/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs
@@ -38,6 +38,12 @@ public async Task ProcessOrder(NotificationOrder order)
await _contactPointService.AddEmailContactPoints(recipientsWithoutEmail, order.ResourceId);
+ await ProcessOrderWithoutAddressLookup(order, recipients);
+ }
+
+ ///
+ public async Task ProcessOrderWithoutAddressLookup(NotificationOrder order, List recipients)
+ {
foreach (Recipient recipient in recipients)
{
await _emailService.CreateNotification(order.Id, order.RequestedSendTime, recipient, order.IgnoreReservation ?? false);
@@ -52,9 +58,15 @@ public async Task ProcessOrderRetry(NotificationOrder order)
await _contactPointService.AddEmailContactPoints(recipientsWithoutEmail, order.ResourceId);
+ await ProcessOrderRetryWithoutAddressLookup(order, recipients);
+ }
+
+ ///
+ public async Task ProcessOrderRetryWithoutAddressLookup(NotificationOrder order, List recipients)
+ {
List emailRecipients = await _emailNotificationRepository.GetRecipients(order.Id);
- foreach (Recipient recipient in order.Recipients)
+ foreach (Recipient recipient in recipients)
{
EmailAddressPoint? addressPoint = recipient.AddressInfo.Find(a => a.AddressType == AddressType.Email) as EmailAddressPoint;
diff --git a/src/Altinn.Notifications.Core/Services/Interfaces/IContactPointService.cs b/src/Altinn.Notifications.Core/Services/Interfaces/IContactPointService.cs
index 48f21384..8a0e1eb3 100644
--- a/src/Altinn.Notifications.Core/Services/Interfaces/IContactPointService.cs
+++ b/src/Altinn.Notifications.Core/Services/Interfaces/IContactPointService.cs
@@ -1,4 +1,5 @@
-using Altinn.Notifications.Core.Models;
+using Altinn.Notifications.Core.Enums;
+using Altinn.Notifications.Core.Models;
namespace Altinn.Notifications.Core.Services.Interfaces
{
@@ -24,5 +25,15 @@ public interface IContactPointService
/// The list of recipients augumented with SMS address points where available
/// Implementation alters the recipient reference object directly
public Task AddSmsContactPoints(List recipients, string? resourceId);
+
+ ///
+ /// Looks up and adds the SMS contact points for recipients based on their national identity number or organization number
+ ///
+ /// The notification channel specifying which channel is preferred
+ /// List of recipients to retrieve contact points for
+ /// The resource to find contact points in relation to
+ /// The list of recipients augumented with SMS address points where available
+ /// Implementation alters the recipient reference object directly
+ public Task AddPreferredContactPoints(NotificationChannel channel, List recipients, string? resourceId);
}
}
diff --git a/src/Altinn.Notifications.Core/Services/Interfaces/IEmailOrderProcessingService.cs b/src/Altinn.Notifications.Core/Services/Interfaces/IEmailOrderProcessingService.cs
index e011af04..b073ef60 100644
--- a/src/Altinn.Notifications.Core/Services/Interfaces/IEmailOrderProcessingService.cs
+++ b/src/Altinn.Notifications.Core/Services/Interfaces/IEmailOrderProcessingService.cs
@@ -1,4 +1,5 @@
-using Altinn.Notifications.Core.Models.Orders;
+using Altinn.Notifications.Core.Models;
+using Altinn.Notifications.Core.Models.Orders;
namespace Altinn.Notifications.Core.Services.Interfaces;
@@ -13,7 +14,19 @@ public interface IEmailOrderProcessingService
public Task ProcessOrder(NotificationOrder order);
///
- /// Retry processing of an order
+ /// Processes a notification order for the provided list of recipients
+ /// without looking up additional recipient data
+ ///
+ public Task ProcessOrderWithoutAddressLookup(NotificationOrder order, List recipients);
+
+ ///
+ /// Retry processing of a notification order
///
public Task ProcessOrderRetry(NotificationOrder order);
+
+ ///
+ /// Retryprocessing of a notification order for the provided list of recipients
+ /// without looking up additional recipient data
+ ///
+ public Task ProcessOrderRetryWithoutAddressLookup(NotificationOrder order, List recipients);
}
diff --git a/src/Altinn.Notifications.Core/Services/Interfaces/IPreferredChannelProcessingService.cs b/src/Altinn.Notifications.Core/Services/Interfaces/IPreferredChannelProcessingService.cs
new file mode 100644
index 00000000..bda36e6e
--- /dev/null
+++ b/src/Altinn.Notifications.Core/Services/Interfaces/IPreferredChannelProcessingService.cs
@@ -0,0 +1,19 @@
+using Altinn.Notifications.Core.Models.Orders;
+
+namespace Altinn.Notifications.Core.Services.Interfaces;
+
+///
+/// Interface for the order processing service speficic to email or sms preferred orders
+///
+public interface IPreferredChannelProcessingService
+{
+ ///
+ /// Processes a notification order
+ ///
+ public Task ProcessOrder(NotificationOrder order);
+
+ ///
+ /// Retry processing of an order
+ ///
+ public Task ProcessOrderRetry(NotificationOrder order);
+}
diff --git a/src/Altinn.Notifications.Core/Services/Interfaces/ISmsOrderProcessingService.cs b/src/Altinn.Notifications.Core/Services/Interfaces/ISmsOrderProcessingService.cs
index 535368dc..06ab12f9 100644
--- a/src/Altinn.Notifications.Core/Services/Interfaces/ISmsOrderProcessingService.cs
+++ b/src/Altinn.Notifications.Core/Services/Interfaces/ISmsOrderProcessingService.cs
@@ -1,4 +1,5 @@
-using Altinn.Notifications.Core.Models.Orders;
+using Altinn.Notifications.Core.Models;
+using Altinn.Notifications.Core.Models.Orders;
namespace Altinn.Notifications.Core.Services.Interfaces;
@@ -12,8 +13,20 @@ public interface ISmsOrderProcessingService
///
public Task ProcessOrder(NotificationOrder order);
+ ///
+ /// Processes a notification order for the provided list of recipients
+ /// without looking up additional recipient data
+ ///
+ public Task ProcessOrderWithoutAddressLookup(NotificationOrder order, List recipients);
+
///
/// Retry processing of an order
///
public Task ProcessOrderRetry(NotificationOrder order);
+
+ ///
+ /// Retryprocessing of a notification order for the provided list of recipients
+ /// without looking up additional recipient data
+ ///
+ public Task ProcessOrderRetryWithoutAddressLookup(NotificationOrder order, List recipients);
}
diff --git a/src/Altinn.Notifications.Core/Services/OrderProcessingService.cs b/src/Altinn.Notifications.Core/Services/OrderProcessingService.cs
index 8df75812..22ba1cde 100644
--- a/src/Altinn.Notifications.Core/Services/OrderProcessingService.cs
+++ b/src/Altinn.Notifications.Core/Services/OrderProcessingService.cs
@@ -21,6 +21,7 @@ public class OrderProcessingService : IOrderProcessingService
private readonly IOrderRepository _orderRepository;
private readonly IEmailOrderProcessingService _emailProcessingService;
private readonly ISmsOrderProcessingService _smsProcessingService;
+ private readonly IPreferredChannelProcessingService _preferredChannelProcessingService;
private readonly IConditionClient _conditionClient;
private readonly IKafkaProducer _producer;
private readonly string _pastDueOrdersTopic;
@@ -33,6 +34,7 @@ public OrderProcessingService(
IOrderRepository orderRepository,
IEmailOrderProcessingService emailProcessingService,
ISmsOrderProcessingService smsProcessingService,
+ IPreferredChannelProcessingService preferredChannelProcessingService,
IConditionClient conditionClient,
IKafkaProducer producer,
IOptions kafkaSettings,
@@ -41,6 +43,7 @@ public OrderProcessingService(
_orderRepository = orderRepository;
_emailProcessingService = emailProcessingService;
_smsProcessingService = smsProcessingService;
+ _preferredChannelProcessingService = preferredChannelProcessingService;
_conditionClient = conditionClient;
_producer = producer;
_pastDueOrdersTopic = kafkaSettings.Value.PastDueOrdersTopicName;
@@ -89,6 +92,10 @@ public async Task ProcessOrder(NotificationOrder order)
case NotificationChannel.Sms:
await _smsProcessingService.ProcessOrder(order);
break;
+ case NotificationChannel.EmailPreferred:
+ case NotificationChannel.SmsPreferred:
+ await _preferredChannelProcessingService.ProcessOrder(order);
+ break;
}
await _orderRepository.SetProcessingStatus(order.Id, OrderProcessingStatus.Completed);
@@ -113,6 +120,10 @@ public async Task ProcessOrderRetry(NotificationOrder order)
case NotificationChannel.Sms:
await _smsProcessingService.ProcessOrderRetry(order);
break;
+ case NotificationChannel.EmailPreferred:
+ case NotificationChannel.SmsPreferred:
+ await _preferredChannelProcessingService.ProcessOrderRetry(order);
+ break;
}
await _orderRepository.SetProcessingStatus(order.Id, OrderProcessingStatus.Completed);
diff --git a/src/Altinn.Notifications.Core/Services/OrderRequestService.cs b/src/Altinn.Notifications.Core/Services/OrderRequestService.cs
index 3676a5cb..e45cd877 100644
--- a/src/Altinn.Notifications.Core/Services/OrderRequestService.cs
+++ b/src/Altinn.Notifications.Core/Services/OrderRequestService.cs
@@ -75,32 +75,25 @@ public async Task RegisterNotificationOrder(No
private async Task GetRecipientLookupResult(List originalRecipients, NotificationChannel channel, string? resourceId)
{
- List recipientsWithoutContactPoint = [];
-
- foreach (var recipient in originalRecipients)
- {
- if (channel == NotificationChannel.Email && !recipient.AddressInfo.Exists(ap => ap.AddressType == AddressType.Email))
- {
- recipientsWithoutContactPoint.Add(recipient.DeepCopy());
- }
- else if (channel == NotificationChannel.Sms && !recipient.AddressInfo.Exists(ap => ap.AddressType == AddressType.Sms))
- {
- recipientsWithoutContactPoint.Add(recipient.DeepCopy());
- }
- }
+ List recipientsWithoutContactPoint = GetMissingContactRecipientList(channel, originalRecipients);
if (recipientsWithoutContactPoint.Count == 0)
{
return null;
}
- if (channel == NotificationChannel.Email)
- {
- await _contactPointService.AddEmailContactPoints(recipientsWithoutContactPoint, resourceId);
- }
- else if (channel == NotificationChannel.Sms)
+ switch (channel)
{
- await _contactPointService.AddSmsContactPoints(recipientsWithoutContactPoint, resourceId);
+ case NotificationChannel.Email:
+ await _contactPointService.AddEmailContactPoints(recipientsWithoutContactPoint, resourceId);
+ break;
+ case NotificationChannel.Sms:
+ await _contactPointService.AddSmsContactPoints(recipientsWithoutContactPoint, resourceId);
+ break;
+ case NotificationChannel.EmailPreferred:
+ case NotificationChannel.SmsPreferred:
+ await _contactPointService.AddPreferredContactPoints(channel, recipientsWithoutContactPoint, resourceId);
+ break;
}
var isReserved = recipientsWithoutContactPoint.Where(r => r.IsReserved.HasValue && r.IsReserved.Value).Select(r => r.NationalIdentityNumber!).ToList();
@@ -108,13 +101,7 @@ public async Task RegisterNotificationOrder(No
RecipientLookupResult lookupResult = new()
{
IsReserved = isReserved,
- MissingContact = recipientsWithoutContactPoint
- .Where(r => channel == NotificationChannel.Email ?
- !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Email) :
- !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Sms))
- .Select(r => r.OrganizationNumber ?? r.NationalIdentityNumber!)
- .Except(isReserved)
- .ToList()
+ MissingContact = GetMissingContactListIds(channel, recipientsWithoutContactPoint).Except(isReserved).ToList()
};
int recipientsWeCannotReach = lookupResult.MissingContact.Union(lookupResult.IsReserved).ToList().Count;
@@ -131,6 +118,46 @@ public async Task RegisterNotificationOrder(No
return lookupResult;
}
+ private static List GetMissingContactListIds(NotificationChannel channel, List recipients)
+ {
+ return channel switch
+ {
+ NotificationChannel.Email => recipients
+ .Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Email))
+ .Select(r => r.OrganizationNumber ?? r.NationalIdentityNumber!)
+ .ToList(),
+ NotificationChannel.Sms => recipients
+ .Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Sms))
+ .Select(r => r.OrganizationNumber ?? r.NationalIdentityNumber!)
+ .ToList(),
+ NotificationChannel.EmailPreferred or NotificationChannel.SmsPreferred => recipients
+ .Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Email || ap.AddressType == AddressType.Sms))
+ .Select(r => r.OrganizationNumber ?? r.NationalIdentityNumber!)
+ .ToList(),
+ _ => [],
+ };
+ }
+
+ private static List GetMissingContactRecipientList(NotificationChannel channel, List recipients)
+ {
+ return channel switch
+ {
+ NotificationChannel.Email => recipients
+ .Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Email))
+ .Select(r => r.DeepCopy())
+ .ToList(),
+ NotificationChannel.Sms => recipients
+ .Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Sms))
+ .Select(r => r.DeepCopy())
+ .ToList(),
+ NotificationChannel.EmailPreferred or NotificationChannel.SmsPreferred => recipients
+ .Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Email || ap.AddressType == AddressType.Sms))
+ .Select(r => r.DeepCopy())
+ .ToList(),
+ _ => [],
+ };
+ }
+
private List SetSenderIfNotDefined(List templates)
{
foreach (var template in templates.OfType().Where(template => string.IsNullOrEmpty(template.FromAddress)))
diff --git a/src/Altinn.Notifications.Core/Services/PreferredChannelProcessingService.cs b/src/Altinn.Notifications.Core/Services/PreferredChannelProcessingService.cs
new file mode 100644
index 00000000..65401d86
--- /dev/null
+++ b/src/Altinn.Notifications.Core/Services/PreferredChannelProcessingService.cs
@@ -0,0 +1,116 @@
+using Altinn.Notifications.Core.Enums;
+using Altinn.Notifications.Core.Models;
+using Altinn.Notifications.Core.Models.Orders;
+using Altinn.Notifications.Core.Services.Interfaces;
+
+namespace Altinn.Notifications.Core.Services;
+
+///
+/// Implementation of the
+///
+public class PreferredChannelProcessingService : IPreferredChannelProcessingService
+{
+ private readonly IEmailOrderProcessingService _emailProcessingService;
+ private readonly ISmsOrderProcessingService _smsProcessingService;
+ private readonly IContactPointService _contactPointService;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PreferredChannelProcessingService(
+ IEmailOrderProcessingService emailProcessingService,
+ ISmsOrderProcessingService smsProcessingService,
+ IContactPointService contactPointService)
+ {
+ _emailProcessingService = emailProcessingService;
+ _smsProcessingService = smsProcessingService;
+ _contactPointService = contactPointService;
+ }
+
+ ///
+ public async Task ProcessOrder(NotificationOrder order)
+ {
+ await ProcessOrderInternal(order, false);
+ }
+
+ ///
+ public async Task ProcessOrderRetry(NotificationOrder order)
+ {
+ await ProcessOrderInternal(order, true);
+ }
+
+ private async Task ProcessOrderInternal(NotificationOrder order, bool isRetry)
+ {
+ List recipients = order.Recipients;
+ List recipientsWithoutContactPoint =
+ recipients
+ .Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Email || ap.AddressType == AddressType.Sms))
+ .ToList();
+
+ await _contactPointService.AddPreferredContactPoints(order.NotificationChannel, recipientsWithoutContactPoint, order.ResourceId);
+
+ List preferredChannelRecipients;
+ List fallBackChannelRecipients;
+
+ switch (order.NotificationChannel)
+ {
+ case NotificationChannel.EmailPreferred:
+ (preferredChannelRecipients, fallBackChannelRecipients) =
+ GenerateRecipientLists(recipients, AddressType.Email, AddressType.Sms);
+
+ if (isRetry)
+ {
+ await _emailProcessingService.ProcessOrderRetryWithoutAddressLookup(order, preferredChannelRecipients);
+ await _smsProcessingService.ProcessOrderRetryWithoutAddressLookup(order, fallBackChannelRecipients);
+ }
+ else
+ {
+ await _emailProcessingService.ProcessOrderWithoutAddressLookup(order, preferredChannelRecipients);
+ await _smsProcessingService.ProcessOrderWithoutAddressLookup(order, fallBackChannelRecipients);
+ }
+
+ break;
+
+ case NotificationChannel.SmsPreferred:
+ (preferredChannelRecipients, fallBackChannelRecipients) =
+ GenerateRecipientLists(recipients, AddressType.Sms, AddressType.Email);
+
+ if (isRetry)
+ {
+ await _smsProcessingService.ProcessOrderRetryWithoutAddressLookup(order, preferredChannelRecipients);
+ await _emailProcessingService.ProcessOrderRetryWithoutAddressLookup(order, fallBackChannelRecipients);
+ }
+ else
+ {
+ await _smsProcessingService.ProcessOrderWithoutAddressLookup(order, preferredChannelRecipients);
+ await _emailProcessingService.ProcessOrderWithoutAddressLookup(order, fallBackChannelRecipients);
+ }
+
+ break;
+ }
+ }
+
+ private static (List PreferredChannelRecipients, List FallBackChannelRecipients) GenerateRecipientLists(
+ List recipients,
+ AddressType preferredAddressType,
+ AddressType fallbackAddressType)
+ {
+ List preferredChannelRecipients = recipients
+ .Where(r => r.AddressInfo.Exists(ap => ap.AddressType == preferredAddressType))
+ .ToList();
+
+ List fallBackChannelRecipients = recipients
+ .Where(r => r.AddressInfo.Exists(ap => ap.AddressType == fallbackAddressType))
+ .Except(preferredChannelRecipients)
+ .ToList();
+
+ List missingContactRecipients = recipients
+ .Except(preferredChannelRecipients)
+ .Except(fallBackChannelRecipients)
+ .ToList();
+
+ preferredChannelRecipients.AddRange(missingContactRecipients);
+
+ return (preferredChannelRecipients, fallBackChannelRecipients);
+ }
+}
diff --git a/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs b/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs
index 4169843e..688da616 100644
--- a/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs
+++ b/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs
@@ -1,5 +1,4 @@
-using System.Runtime.CompilerServices;
-using System.Web;
+using System.Web;
using Altinn.Notifications.Core.Enums;
using Altinn.Notifications.Core.Models;
@@ -38,6 +37,12 @@ public async Task ProcessOrder(NotificationOrder order)
var recipientsWithoutMobileNumber = recipients.Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Sms)).ToList();
await _contactPointService.AddSmsContactPoints(recipientsWithoutMobileNumber, order.ResourceId);
+ await ProcessOrderWithoutAddressLookup(order, recipients);
+ }
+
+ ///
+ public async Task ProcessOrderWithoutAddressLookup(NotificationOrder order, List recipients)
+ {
int smsCount = GetSmsCountForOrder(order);
foreach (Recipient recipient in recipients)
@@ -54,10 +59,16 @@ public async Task ProcessOrderRetry(NotificationOrder order)
await _contactPointService.AddSmsContactPoints(recipientsWithoutMobileNumber, order.ResourceId);
+ await ProcessOrderRetryWithoutAddressLookup(order, recipients);
+ }
+
+ ///
+ public async Task ProcessOrderRetryWithoutAddressLookup(NotificationOrder order, List recipients)
+ {
int smsCount = GetSmsCountForOrder(order);
List smsRecipients = await _smsNotificationRepository.GetRecipients(order.Id);
- foreach (Recipient recipient in order.Recipients)
+ foreach (Recipient recipient in recipients)
{
SmsAddressPoint? addressPoint = recipient.AddressInfo.Find(a => a.AddressType == AddressType.Sms) as SmsAddressPoint;
diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs
index 4945270d..f9d13a46 100644
--- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs
+++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs
@@ -1,7 +1,9 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using Altinn.Notifications.Core.Enums;
using Altinn.Notifications.Core.Integrations;
using Altinn.Notifications.Core.Models;
using Altinn.Notifications.Core.Models.Address;
@@ -261,12 +263,12 @@ public async Task AddEmailContactPoints_OrganizationNumberAndResourceAvailable_N
MobileNumber = "+4748123456",
Email = "user-1@domain.com"
},
- new UserContactPoints()
- {
- UserId = 200009,
- MobileNumber = "004699999999",
- Email = "user-9@domain.com"
- }
+ new UserContactPoints()
+ {
+ UserId = 200009,
+ MobileNumber = "004699999999",
+ Email = "user-9@domain.com"
+ }
]
}
]);
@@ -362,6 +364,312 @@ public async Task AddEmailContactPoints_OrganizationNumberAndResourceAvailable_A
Assert.Equivalent(expectedOutput, input);
}
+ [Fact]
+ public async Task AddPreferredContactPoints_EmailPreferred_PersonRecipient()
+ {
+ // Arrange
+ List recipients = [
+ new Recipient() { NationalIdentityNumber = "0" },
+ new Recipient() { NationalIdentityNumber = "1" },
+ new Recipient() { NationalIdentityNumber = "2" },
+ new Recipient() { NationalIdentityNumber = "3" }
+ ];
+
+ var profileClientMock = new Mock();
+
+ profileClientMock
+ .Setup(p => p.GetUserContactPoints(It.IsAny>()))
+ .ReturnsAsync([
+ new UserContactPoints() { NationalIdentityNumber = "0", Email = "user0@email.com" },
+ new UserContactPoints() { NationalIdentityNumber = "1", MobileNumber = "+4799999991" },
+ new UserContactPoints() { NationalIdentityNumber = "2", Email = "user2@email.com", MobileNumber = "+4799999992" },
+ new UserContactPoints() { NationalIdentityNumber = "3" }]);
+
+ var service = GetTestService(profileClientMock.Object, null, null);
+
+ // Act
+ await service.AddPreferredContactPoints(NotificationChannel.EmailPreferred, recipients, null);
+
+ // Assert
+ Recipient user0 = recipients[0];
+ Assert.Single(user0.AddressInfo);
+ Assert.IsType(user0.AddressInfo[0]);
+ Assert.Equal("user0@email.com", ((EmailAddressPoint)user0.AddressInfo[0]).EmailAddress);
+
+ Recipient user1 = recipients[1];
+ Assert.Single(user1.AddressInfo);
+ Assert.IsType(user1.AddressInfo[0]);
+ Assert.Equal("+4799999991", ((SmsAddressPoint)user1.AddressInfo[0]).MobileNumber);
+
+ Recipient user2 = recipients[2];
+ Assert.Single(user2.AddressInfo);
+ Assert.IsType(user2.AddressInfo[0]);
+ Assert.Equal("user2@email.com", ((EmailAddressPoint)user2.AddressInfo[0]).EmailAddress);
+
+ Recipient user3 = recipients[3];
+ Assert.Empty(user3.AddressInfo);
+ }
+
+ [Fact]
+ public async Task AddPreferredContactPoints_SmsPreferred_PersonRecipient()
+ {
+ // Arrange
+ List recipients = [
+ new Recipient() { NationalIdentityNumber = "0" },
+ new Recipient() { NationalIdentityNumber = "1" },
+ new Recipient() { NationalIdentityNumber = "2" },
+ new Recipient() { NationalIdentityNumber = "3" },
+ ];
+
+ var profileClientMock = new Mock();
+
+ profileClientMock
+ .Setup(p => p.GetUserContactPoints(It.IsAny>()))
+ .ReturnsAsync([
+ new UserContactPoints() { NationalIdentityNumber = "0", Email = "user0@email.com" },
+ new UserContactPoints() { NationalIdentityNumber = "1", MobileNumber = "+4799999991" },
+ new UserContactPoints() { NationalIdentityNumber = "2", Email = "user2@email.com", MobileNumber = "+4799999992" },
+ new UserContactPoints() { NationalIdentityNumber = "3" }]);
+
+ var service = GetTestService(profileClientMock.Object, null, null);
+
+ // Act
+ await service.AddPreferredContactPoints(NotificationChannel.SmsPreferred, recipients, null);
+
+ // Assert
+ Recipient user0 = recipients[0];
+ Assert.Single(user0.AddressInfo);
+ Assert.IsType(user0.AddressInfo[0]);
+ Assert.Equal("user0@email.com", ((EmailAddressPoint)user0.AddressInfo[0]).EmailAddress);
+
+ Recipient user1 = recipients[1];
+ Assert.Single(user1.AddressInfo);
+ Assert.IsType(user1.AddressInfo[0]);
+ Assert.Equal("+4799999991", ((SmsAddressPoint)user1.AddressInfo[0]).MobileNumber);
+
+ Recipient user2 = recipients[2];
+ Assert.Single(user2.AddressInfo);
+ Assert.IsType(user2.AddressInfo[0]);
+ Assert.Equal("+4799999992", ((SmsAddressPoint)user2.AddressInfo[0]).MobileNumber);
+
+ Recipient user3 = recipients[3];
+ Assert.Empty(user3.AddressInfo);
+ }
+
+ [Fact]
+ public async Task AddPreferredContactPoints_EmailPreferred_OrgRecipient()
+ {
+ // Arrange
+ List recipients = [
+ new Recipient() { OrganizationNumber = "0" },
+ new Recipient() { OrganizationNumber = "1" },
+ new Recipient() { OrganizationNumber = "2" },
+ new Recipient() { OrganizationNumber = "3" },
+ ];
+
+ var registerClientMock = new Mock();
+ registerClientMock
+ .Setup(r => r.GetOrganizationContactPoints(It.IsAny>()))
+ .ReturnsAsync([
+ new OrganizationContactPoints() { OrganizationNumber = "0", EmailList = ["0@org.com"] },
+ new OrganizationContactPoints() { OrganizationNumber = "1", MobileNumberList = ["+4799999991"] },
+ new OrganizationContactPoints() { OrganizationNumber = "2", EmailList = ["2@org.com"], MobileNumberList = ["+4799999992"] },
+ new OrganizationContactPoints() { OrganizationNumber = "3" }]);
+
+ var service = GetTestService(null, registerClientMock.Object, null);
+
+ // Act
+ await service.AddPreferredContactPoints(NotificationChannel.EmailPreferred, recipients, null);
+
+ // Assert
+ Recipient org0 = recipients[0];
+ Assert.Single(org0.AddressInfo);
+ Assert.IsType(org0.AddressInfo[0]);
+ Assert.Equal("0@org.com", ((EmailAddressPoint)org0.AddressInfo[0]).EmailAddress);
+
+ Recipient org1 = recipients[1];
+ Assert.Single(org1.AddressInfo);
+ Assert.IsType(org1.AddressInfo[0]);
+ Assert.Equal("+4799999991", ((SmsAddressPoint)org1.AddressInfo[0]).MobileNumber);
+
+ Recipient org2 = recipients[2];
+ Assert.Single(org2.AddressInfo);
+ Assert.IsType(org2.AddressInfo[0]);
+ Assert.Equal("2@org.com", ((EmailAddressPoint)org2.AddressInfo[0]).EmailAddress);
+
+ Recipient org3 = recipients[3];
+ Assert.Empty(org3.AddressInfo);
+ }
+
+ [Fact]
+ public async Task AddPreferredContactPoints_SmsPreferred_OrgOfficialRecipient()
+ {
+ // Arrange
+ List recipients = [
+ new Recipient() { OrganizationNumber = "0" },
+ new Recipient() { OrganizationNumber = "1" },
+ new Recipient() { OrganizationNumber = "2" },
+ new Recipient() { OrganizationNumber = "3" },
+ ];
+
+ var registerClientMock = new Mock();
+ registerClientMock
+ .Setup(r => r.GetOrganizationContactPoints(It.IsAny>()))
+ .ReturnsAsync([
+ new OrganizationContactPoints() { OrganizationNumber = "0", EmailList = ["0@org.com"] },
+ new OrganizationContactPoints() { OrganizationNumber = "1", MobileNumberList = ["+4799999991"] },
+ new OrganizationContactPoints() { OrganizationNumber = "2", EmailList = ["2@org.com"], MobileNumberList = ["+4799999992"] },
+ new OrganizationContactPoints() { OrganizationNumber = "3" }]);
+
+ var service = GetTestService(null, registerClientMock.Object, null);
+
+ // Act
+ await service.AddPreferredContactPoints(NotificationChannel.SmsPreferred, recipients, null);
+
+ // Assert
+ Recipient org0 = recipients[0];
+ Assert.Single(org0.AddressInfo);
+ Assert.IsType(org0.AddressInfo[0]);
+ Assert.Equal("0@org.com", ((EmailAddressPoint)org0.AddressInfo[0]).EmailAddress);
+
+ Recipient org1 = recipients[1];
+ Assert.Single(org1.AddressInfo);
+ Assert.IsType(org1.AddressInfo[0]);
+ Assert.Equal("+4799999991", ((SmsAddressPoint)org1.AddressInfo[0]).MobileNumber);
+
+ Recipient org2 = recipients[2];
+ Assert.Single(org2.AddressInfo);
+ Assert.IsType(org2.AddressInfo[0]);
+ Assert.Equal("+4799999992", ((SmsAddressPoint)org2.AddressInfo[0]).MobileNumber);
+
+ Recipient org3 = recipients[3];
+ Assert.Empty(org3.AddressInfo);
+ }
+
+ [Fact]
+ public async Task AddPreferredContactPoints_EmailPreferred_OrgUserRegisteredRecipient()
+ {
+ // Arrange
+ List recipients = [
+ new Recipient() { OrganizationNumber = "0" }
+ ];
+
+ var registerClientMock = new Mock();
+ registerClientMock
+ .Setup(r => r.GetOrganizationContactPoints(It.IsAny>()))
+ .ReturnsAsync([]);
+
+ var profileClientMock = new Mock();
+
+ profileClientMock
+ .Setup(p => p.GetUserRegisteredContactPoints(It.IsAny>(), It.IsAny()))
+ .ReturnsAsync([
+ new OrganizationContactPoints()
+ {
+ OrganizationNumber = "0",
+ UserContactPoints =
+ [
+ new UserContactPoints() { NationalIdentityNumber = "0", Email = "user0@email.com" },
+ new UserContactPoints() { NationalIdentityNumber = "1", MobileNumber = "+4799999991" },
+ new UserContactPoints() { NationalIdentityNumber = "2", Email = "user2@email.com", MobileNumber = "+4799999992" },
+ new UserContactPoints() { NationalIdentityNumber = "3" }
+ ]
+ }
+ ]);
+
+ var authorizationServiceMock = new Mock();
+
+ authorizationServiceMock
+ .Setup(a => a.AuthorizeUserContactPointsForResource(It.IsAny>(), It.IsAny()))
+ .ReturnsAsync([
+ new OrganizationContactPoints()
+ {
+ OrganizationNumber = "0",
+ UserContactPoints =
+ [
+ new UserContactPoints() { NationalIdentityNumber = "0", Email = "user0@email.com" },
+ new UserContactPoints() { NationalIdentityNumber = "1", MobileNumber = "+4799999991" },
+ new UserContactPoints() { NationalIdentityNumber = "2", Email = "user2@email.com", MobileNumber = "+4799999992" },
+ new UserContactPoints() { NationalIdentityNumber = "3" }
+ ]
+ }
+ ]);
+
+ var service = GetTestService(profileClientMock.Object, registerClientMock.Object, authorizationServiceMock.Object);
+
+ // Act
+ await service.AddPreferredContactPoints(NotificationChannel.EmailPreferred, recipients, "resource");
+
+ // Assert
+ Recipient org0 = recipients[0];
+ Assert.Equal(3, org0.AddressInfo.Count);
+ Assert.Equal("user0@email.com", ((EmailAddressPoint)org0.AddressInfo[0]).EmailAddress);
+ Assert.Equal("+4799999991", ((SmsAddressPoint)org0.AddressInfo[1]).MobileNumber);
+ Assert.Equal("user2@email.com", ((EmailAddressPoint)org0.AddressInfo[2]).EmailAddress);
+ }
+
+ [Fact]
+ public async Task AddPreferredContactPoints_SmsPreferred_OrgUserRegisteredRecipient()
+ {
+ // Arrange
+ List recipients = [
+ new Recipient() { OrganizationNumber = "0" }
+ ];
+
+ var registerClientMock = new Mock();
+ registerClientMock
+ .Setup(r => r.GetOrganizationContactPoints(It.IsAny>()))
+ .ReturnsAsync([]);
+
+ var profileClientMock = new Mock();
+
+ profileClientMock
+ .Setup(p => p.GetUserRegisteredContactPoints(It.IsAny>(), It.IsAny()))
+ .ReturnsAsync([
+ new OrganizationContactPoints()
+ {
+ OrganizationNumber = "0",
+ UserContactPoints =
+ [
+ new UserContactPoints() { NationalIdentityNumber = "0", Email = "user0@email.com" },
+ new UserContactPoints() { NationalIdentityNumber = "1", MobileNumber = "+4799999991" },
+ new UserContactPoints() { NationalIdentityNumber = "2", Email = "user2@email.com", MobileNumber = "+4799999992" },
+ new UserContactPoints() { NationalIdentityNumber = "3" }
+ ]
+ }
+ ]);
+
+ var authorizationServiceMock = new Mock();
+
+ authorizationServiceMock
+ .Setup(a => a.AuthorizeUserContactPointsForResource(It.IsAny>(), It.IsAny()))
+ .ReturnsAsync([
+ new OrganizationContactPoints()
+ {
+ OrganizationNumber = "0",
+ UserContactPoints =
+ [
+ new UserContactPoints() { NationalIdentityNumber = "0", Email = "user0@email.com" },
+ new UserContactPoints() { NationalIdentityNumber = "1", MobileNumber = "+4799999991" },
+ new UserContactPoints() { NationalIdentityNumber = "2", Email = "user2@email.com", MobileNumber = "+4799999992" },
+ new UserContactPoints() { NationalIdentityNumber = "3" }
+ ]
+ }
+ ]);
+
+ var service = GetTestService(profileClientMock.Object, registerClientMock.Object, authorizationServiceMock.Object);
+
+ // Act
+ await service.AddPreferredContactPoints(NotificationChannel.SmsPreferred, recipients, "resource");
+
+ // Assert
+ Recipient org0 = recipients[0];
+ Assert.Equal(3, org0.AddressInfo.Count);
+ Assert.Equal("user0@email.com", ((EmailAddressPoint)org0.AddressInfo[0]).EmailAddress);
+ Assert.Equal("+4799999991", ((SmsAddressPoint)org0.AddressInfo[1]).MobileNumber);
+ Assert.Equal("+4799999992", ((SmsAddressPoint)org0.AddressInfo[2]).MobileNumber);
+ }
+
private static ContactPointService GetTestService(
IProfileClient? profileClient = null,
IRegisterClient? registerClient = null,
diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/OrderProcessingServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/OrderProcessingServiceTests.cs
index 458c5167..ceb67f0f 100644
--- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/OrderProcessingServiceTests.cs
+++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/OrderProcessingServiceTests.cs
@@ -97,6 +97,37 @@ public async Task ProcessOrder_SmsOrder_SmsServiceCalled()
emailMockService.Verify(e => e.ProcessOrder(It.IsAny()), Times.Never);
}
+ [Theory]
+ [InlineData(NotificationChannel.EmailPreferred)]
+ [InlineData(NotificationChannel.SmsPreferred)]
+ public async Task ProcessOrder_EmailOrSmsPreferredOrder_PreferredServiceCalled(NotificationChannel notificationChannel)
+ {
+ // Arrange
+ NotificationOrder order = new()
+ {
+ NotificationChannel = notificationChannel,
+ };
+
+ var emailMockService = new Mock();
+ emailMockService.Setup(e => e.ProcessOrder(It.IsAny()));
+
+ var smsMockService = new Mock();
+ smsMockService.Setup(s => s.ProcessOrder(It.IsAny()));
+
+ var preferredMockService = new Mock();
+ preferredMockService.Setup(e => e.ProcessOrder(It.IsAny()));
+
+ var orderProcessingService = GetTestService(emailMock: emailMockService.Object, smsMock: smsMockService.Object, preferredMock: preferredMockService.Object);
+
+ // Act
+ await orderProcessingService.ProcessOrder(order);
+
+ // Assert
+ emailMockService.Verify(e => e.ProcessOrder(It.IsAny()), Times.Never);
+ smsMockService.Verify(s => s.ProcessOrder(It.IsAny()), Times.Never);
+ preferredMockService.Verify(s => s.ProcessOrder(It.IsAny()), Times.Once);
+ }
+
[Fact]
public async Task ProcessOrder_SendConditionNotMet_ProcessingStops()
{
@@ -182,6 +213,36 @@ public async Task ProcessOrderRetry_SmsOrder_SmsServiceCalled()
emailMockService.Verify(e => e.ProcessOrderRetry(It.IsAny()), Times.Never);
}
+ [Theory]
+ [InlineData(NotificationChannel.EmailPreferred)]
+ [InlineData(NotificationChannel.SmsPreferred)]
+ public async Task ProcessOrderRetry_EmailOrSmsPreferredOrder_PreferredServiceCalled(NotificationChannel notificationChannel)
+ {
+ // Arrange
+ NotificationOrder order = new()
+ {
+ NotificationChannel = notificationChannel,
+ };
+ var emailMockService = new Mock();
+ emailMockService.Setup(e => e.ProcessOrderRetry(It.IsAny()));
+
+ var smsMockService = new Mock();
+ smsMockService.Setup(s => s.ProcessOrderRetry(It.IsAny()));
+
+ var preferredMockService = new Mock();
+ preferredMockService.Setup(e => e.ProcessOrderRetry(It.IsAny()));
+
+ var orderProcessingService = GetTestService(emailMock: emailMockService.Object, smsMock: smsMockService.Object, preferredMock: preferredMockService.Object);
+
+ // Act
+ await orderProcessingService.ProcessOrderRetry(order);
+
+ // Assert
+ emailMockService.Verify(e => e.ProcessOrderRetry(It.IsAny()), Times.Never);
+ smsMockService.Verify(s => s.ProcessOrderRetry(It.IsAny()), Times.Never);
+ preferredMockService.Verify(s => s.ProcessOrderRetry(It.IsAny()), Times.Once);
+ }
+
[Fact]
public async Task ProcessOrderRetry_SendConditionNotMet_ProcessingStops()
{
@@ -312,6 +373,7 @@ private static OrderProcessingService GetTestService(
IOrderRepository? repo = null,
IEmailOrderProcessingService? emailMock = null,
ISmsOrderProcessingService? smsMock = null,
+ IPreferredChannelProcessingService? preferredMock = null,
IKafkaProducer? producer = null,
IConditionClient? conditionClient = null)
{
@@ -333,6 +395,12 @@ private static OrderProcessingService GetTestService(
smsMock = smsMockService.Object;
}
+ if (preferredMock == null)
+ {
+ var preferredMockService = new Mock();
+ preferredMock = preferredMockService.Object;
+ }
+
if (producer == null)
{
var producerMock = new Mock();
@@ -347,6 +415,6 @@ private static OrderProcessingService GetTestService(
var kafkaSettings = new Altinn.Notifications.Core.Configuration.KafkaSettings() { PastDueOrdersTopicName = _pastDueTopicName };
- return new OrderProcessingService(repo, emailMock, smsMock, conditionClient, producer, Options.Create(kafkaSettings), new LoggerFactory().CreateLogger());
+ return new OrderProcessingService(repo, emailMock, smsMock, preferredMock, conditionClient, producer, Options.Create(kafkaSettings), new LoggerFactory().CreateLogger());
}
}
diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/OrderRequestServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/OrderRequestServiceTests.cs
index cc070e27..9e368b02 100644
--- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/OrderRequestServiceTests.cs
+++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/OrderRequestServiceTests.cs
@@ -213,7 +213,7 @@ public async Task RegisterNotificationOrder_ForSms_NoSenderNumberDefaultInserted
}
[Fact]
- public async Task RegisterNotificationOrder_LookupFails_OrderCreated()
+ public async Task RegisterNotificationOrder_ForSms_LookupFails_OrderCreated()
{
// Arrange
DateTime sendTime = DateTime.UtcNow;
@@ -317,7 +317,7 @@ public async Task RegisterNotificationOrder_ForSms_LookupPartialSuccess_OrderCre
repoMock.VerifyAll();
contactPointMock.VerifyAll();
}
-
+
[Fact]
public async Task RegisterNotificationOrder_ForSms_LookupSuccess_OrderCreated()
{
@@ -335,7 +335,7 @@ public async Task RegisterNotificationOrder_ForSms_LookupSuccess_OrderCreated()
RequestedSendTime = sendTime,
Recipients = [
new Recipient() { NationalIdentityNumber = "16069412345" },
- ],
+ ],
Templates = { new SmsTemplate { Body = "sms-body", SenderNumber = "TestDefaultSmsSenderNumberNumber" } }
};
@@ -346,7 +346,7 @@ public async Task RegisterNotificationOrder_ForSms_LookupSuccess_OrderCreated()
NotificationChannel = NotificationChannel.Sms,
Recipients = [
new Recipient() { NationalIdentityNumber = "16069412345" },
- ],
+ ],
RequestedSendTime = sendTime,
Templates = { new SmsTemplate { Body = "sms-body" } }
};
@@ -385,6 +385,175 @@ public async Task RegisterNotificationOrder_ForSms_LookupSuccess_OrderCreated()
contactPointMock.VerifyAll();
}
+ [Fact]
+ public async Task RegisterNotificationOrder_ForSmsPreferred_LookupFails_OrderCreated()
+ {
+ // Arrange
+ DateTime sendTime = DateTime.UtcNow;
+ DateTime createdTime = DateTime.UtcNow.AddMinutes(-2);
+ Guid id = Guid.NewGuid();
+
+ NotificationOrderRequest input = new()
+ {
+ Creator = new Creator("ttd"),
+
+ NotificationChannel = NotificationChannel.SmsPreferred,
+ Recipients = [new Recipient() { NationalIdentityNumber = "1" }],
+ SendersReference = "senders-reference",
+ RequestedSendTime = sendTime,
+ Templates = [
+ new SmsTemplate { Body = "sms-body" },
+ new EmailTemplate { Body = "email-body", FromAddress = "noreply@altinn.no" }]
+ };
+
+ Mock repoMock = new();
+ repoMock
+ .Setup(r => r.Create(It.IsAny()))
+ .ReturnsAsync((NotificationOrder order) => order);
+
+ var service = GetTestService(repoMock.Object, null, id, createdTime);
+
+ // Act
+ NotificationOrderRequestResponse actual = await service.RegisterNotificationOrder(input);
+
+ // Assert
+ Assert.Equal(RecipientLookupStatus.Failed, actual.RecipientLookup?.Status);
+ repoMock.Verify(r => r.Create(It.IsAny()), Times.Once);
+ }
+
+ [Fact]
+ public async Task RegisterNotificationOrder_ForSmsPreferred_LookupPartialSuccess_OrderCreated()
+ {
+ // Arrange
+ DateTime sendTime = DateTime.UtcNow;
+ DateTime createdTime = DateTime.UtcNow.AddMinutes(-2);
+ Guid id = Guid.NewGuid();
+
+ NotificationOrderRequest input = new()
+ {
+ Creator = new Creator("ttd"),
+
+ NotificationChannel = NotificationChannel.SmsPreferred,
+ Recipients = [
+ new Recipient() { NationalIdentityNumber = "1" },
+ new Recipient() { NationalIdentityNumber = "2" },
+ new Recipient() { NationalIdentityNumber = "3" },
+ new Recipient() { NationalIdentityNumber = "4" }
+
+ ],
+ RequestedSendTime = sendTime,
+ Templates = [
+ new SmsTemplate { Body = "sms-body" },
+ new EmailTemplate { Body = "email-body", FromAddress = "noreply@altinn.no" }]
+ };
+
+ Mock repoMock = new();
+ repoMock
+ .Setup(r => r.Create(It.IsAny()))
+ .ReturnsAsync((NotificationOrder order) => order);
+
+ Mock contactPointMock = new();
+ contactPointMock
+ .Setup(cp => cp.AddPreferredContactPoints(input.NotificationChannel, It.IsAny>(), It.IsAny()))
+ .Callback, string?>((_, recipients, _) =>
+ {
+ foreach (var recipient in recipients)
+ {
+ if (recipient.NationalIdentityNumber == "1")
+ {
+ recipient.AddressInfo.Add(new SmsAddressPoint("+4799999999"));
+ recipient.IsReserved = false;
+ }
+ else if (recipient.NationalIdentityNumber == "2")
+ {
+ recipient.AddressInfo.Add(new EmailAddressPoint("2@user.com"));
+ recipient.IsReserved = false;
+ }
+ else if (recipient.NationalIdentityNumber == "3")
+ {
+ recipient.IsReserved = true;
+ }
+ else if (recipient.NationalIdentityNumber == "4")
+ {
+ recipient.IsReserved = false;
+ }
+ }
+ });
+
+ var service = GetTestService(repoMock.Object, contactPointMock.Object, id, createdTime);
+
+ // Act
+ NotificationOrderRequestResponse actual = await service.RegisterNotificationOrder(input);
+
+ // Assert
+ Assert.Equal(RecipientLookupStatus.PartialSuccess, actual.RecipientLookup?.Status);
+ Assert.Single(actual.RecipientLookup!.IsReserved!);
+ Assert.Single(actual.RecipientLookup!.MissingContact!);
+ repoMock.VerifyAll();
+ contactPointMock.VerifyAll();
+ }
+
+ [Fact]
+ public async Task RegisterNotificationOrder_ForSmsPreferred_LookupSuccess_OrderCreated()
+ {
+ // Arrange
+ DateTime sendTime = DateTime.UtcNow;
+ DateTime createdTime = DateTime.UtcNow.AddMinutes(-2);
+ Guid id = Guid.NewGuid();
+
+ NotificationOrderRequest input = new()
+ {
+ Creator = new Creator("ttd"),
+
+ NotificationChannel = NotificationChannel.SmsPreferred,
+ Recipients = [
+ new Recipient() { NationalIdentityNumber = "1" },
+ new Recipient() { NationalIdentityNumber = "2" }
+ ],
+ RequestedSendTime = sendTime,
+ Templates = [
+ new SmsTemplate { Body = "sms-body" },
+ new EmailTemplate { Body = "email-body", FromAddress = "noreply@altinn.no" }]
+ };
+
+ Mock repoMock = new();
+ repoMock
+ .Setup(r => r.Create(It.IsAny()))
+ .ReturnsAsync((NotificationOrder order) => order);
+
+ Mock contactPointMock = new();
+ contactPointMock
+ .Setup(cp => cp.AddPreferredContactPoints(input.NotificationChannel, It.IsAny>(), It.IsAny()))
+ .Callback, string?>((_, recipients, _) =>
+ {
+ foreach (var recipient in recipients)
+ {
+ if (recipient.NationalIdentityNumber == "1")
+ {
+ recipient.AddressInfo.Add(new SmsAddressPoint("+4799999999"));
+ recipient.IsReserved = false;
+ }
+ else if (recipient.NationalIdentityNumber == "2")
+ {
+ recipient.AddressInfo.Add(new EmailAddressPoint("2@user.com"));
+ recipient.IsReserved = false;
+ }
+ }
+ });
+
+ var service = GetTestService(repoMock.Object, contactPointMock.Object, id, createdTime);
+
+ // Act
+ NotificationOrderRequestResponse actual = await service.RegisterNotificationOrder(input);
+
+ // Assert
+ Assert.Equal(RecipientLookupStatus.Success, actual.RecipientLookup?.Status);
+ Assert.Equal(0, actual.RecipientLookup!.IsReserved?.Count);
+ Assert.Equal(0, actual.RecipientLookup!.MissingContact?.Count);
+ repoMock.VerifyAll();
+ contactPointMock.VerifyAll();
+ }
+
public static OrderRequestService GetTestService(IOrderRepository? repository = null, IContactPointService? contactPointService = null, Guid? guid = null, DateTime? dateTime = null)
{
if (repository == null)
diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/PreferredChannelProcessingServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/PreferredChannelProcessingServiceTests.cs
new file mode 100644
index 00000000..f221d6c9
--- /dev/null
+++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/PreferredChannelProcessingServiceTests.cs
@@ -0,0 +1,299 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+using Altinn.Notifications.Core.Enums;
+using Altinn.Notifications.Core.Models;
+using Altinn.Notifications.Core.Models.Address;
+using Altinn.Notifications.Core.Models.Orders;
+using Altinn.Notifications.Core.Services;
+using Altinn.Notifications.Core.Services.Interfaces;
+
+using Moq;
+
+using Xunit;
+
+namespace Altinn.Notifications.Tests.Notifications.Core.TestingServices
+{
+ public class PreferredChannelProcessingServiceTests
+ {
+ ///
+ /// Scenario: 1 recipient with email, 1 recipient with mobile, 1 reserved alid recipient, 1 recipient without contact point
+ /// Expected; Email service is called with three recipients, SMS service is called with one recipient
+ ///
+ ///
+ /// Assertion logic is mostly within the setup of the mocked functions in the processing services
+ ///
+ [Fact]
+ public async Task ProcessOrder_EmailPreferredChannel_WithoutRetry()
+ {
+ // Arrange
+ NotificationOrder order = new()
+ {
+ NotificationChannel = NotificationChannel.EmailPreferred,
+ Recipients = [
+ new Recipient() { NationalIdentityNumber = "1" },
+ new Recipient() { NationalIdentityNumber = "2" },
+ new Recipient() { NationalIdentityNumber = "3" },
+ new Recipient() { NationalIdentityNumber = "4" }]
+ };
+
+ Mock emailProcessingMock = new();
+ emailProcessingMock
+ .Setup(x => x.ProcessOrderWithoutAddressLookup(It.IsAny(), It.Is>(l => l.Count == 3 && !l.Exists(r => r.NationalIdentityNumber == "1"))));
+
+ Mock smsProcessingMock = new();
+ smsProcessingMock
+ .Setup(x => x.ProcessOrderWithoutAddressLookup(It.IsAny(), It.Is>(l => l.Count == 1 && l.Exists(r => r.NationalIdentityNumber == "1"))));
+
+ Mock contactPointMock = new();
+ contactPointMock
+ .Setup(cp => cp.AddPreferredContactPoints(order.NotificationChannel, It.IsAny>(), It.IsAny()))
+ .Callback, string?>((_, recipients, _) =>
+ {
+ foreach (var recipient in recipients)
+ {
+ if (recipient.NationalIdentityNumber == "1")
+ {
+ recipient.AddressInfo.Add(new SmsAddressPoint("+4799999999"));
+ recipient.IsReserved = false;
+ }
+ else if (recipient.NationalIdentityNumber == "2")
+ {
+ recipient.AddressInfo.Add(new EmailAddressPoint("2@user.com"));
+ recipient.IsReserved = false;
+ }
+ else if (recipient.NationalIdentityNumber == "3")
+ {
+ recipient.IsReserved = true;
+ }
+ else if (recipient.NationalIdentityNumber == "4")
+ {
+ recipient.IsReserved = false;
+ }
+ }
+ });
+
+ var preferredChannelProcessingService = new PreferredChannelProcessingService(
+ emailProcessingMock.Object,
+ smsProcessingMock.Object,
+ contactPointMock.Object);
+
+ // Act
+ await preferredChannelProcessingService.ProcessOrder(order);
+
+ // Assert
+ emailProcessingMock.VerifyAll();
+ smsProcessingMock.VerifyAll();
+ }
+
+ ///
+ /// Scenario: 1 recipient with email, 1 recipient with mobile, 1 reserved alid recipient, 1 recipient without contact point
+ /// Expected; Email service is called with three recipients, SMS service is called with one recipient
+ ///
+ ///
+ /// Assertion logic is mostly within the setup of the mocked functions in the processing services
+ ///
+ [Fact]
+ public async Task ProcessOrder_EmailPreferredChannel_WithRetry()
+ {
+ // Arrange
+ NotificationOrder order = new()
+ {
+ NotificationChannel = NotificationChannel.EmailPreferred,
+ Recipients = [
+ new Recipient() { NationalIdentityNumber = "1" },
+ new Recipient() { NationalIdentityNumber = "2" },
+ new Recipient() { NationalIdentityNumber = "3" },
+ new Recipient() { NationalIdentityNumber = "4" }]
+ };
+
+ Mock emailProcessingMock = new();
+ emailProcessingMock
+ .Setup(x => x.ProcessOrderRetryWithoutAddressLookup(It.IsAny(), It.Is>(l => l.Count == 3 && !l.Exists(r => r.NationalIdentityNumber == "1"))));
+
+ Mock smsProcessingMock = new();
+ smsProcessingMock
+ .Setup(x => x.ProcessOrderRetryWithoutAddressLookup(It.IsAny(), It.Is>(l => l.Count == 1 && l.Exists(r => r.NationalIdentityNumber == "1"))));
+
+ Mock contactPointMock = new();
+ contactPointMock
+ .Setup(cp => cp.AddPreferredContactPoints(order.NotificationChannel, It.IsAny>(), It.IsAny()))
+ .Callback, string?>((_, recipients, _) =>
+ {
+ foreach (var recipient in recipients)
+ {
+ if (recipient.NationalIdentityNumber == "1")
+ {
+ recipient.AddressInfo.Add(new SmsAddressPoint("+4799999999"));
+ recipient.IsReserved = false;
+ }
+ else if (recipient.NationalIdentityNumber == "2")
+ {
+ recipient.AddressInfo.Add(new EmailAddressPoint("2@user.com"));
+ recipient.IsReserved = false;
+ }
+ else if (recipient.NationalIdentityNumber == "3")
+ {
+ recipient.IsReserved = true;
+ }
+ else if (recipient.NationalIdentityNumber == "4")
+ {
+ recipient.IsReserved = false;
+ }
+ }
+ });
+
+ var preferredChannelProcessingService = new PreferredChannelProcessingService(
+ emailProcessingMock.Object,
+ smsProcessingMock.Object,
+ contactPointMock.Object);
+
+ // Act
+ await preferredChannelProcessingService.ProcessOrderRetry(order);
+
+ // Assert
+ emailProcessingMock.VerifyAll();
+ smsProcessingMock.VerifyAll();
+ }
+
+ ///
+ /// Scenario: 1 recipient with email, 1 recipient with mobile, 1 reserved alid recipient, 1 recipient without contact point
+ /// Expected; SMS service is called with three recipients, email service is called with one recipient
+ ///
+ ///
+ /// Assertion logic is mostly within the setup of the mocked functions in the processing services
+ ///
+ [Fact]
+ public async Task ProcessOrder_SmsPreferredChannel_WithoutRetry()
+ {
+ // Arrange
+ NotificationOrder order = new()
+ {
+ NotificationChannel = NotificationChannel.SmsPreferred,
+ Recipients = [
+ new Recipient() { NationalIdentityNumber = "1" },
+ new Recipient() { NationalIdentityNumber = "2" },
+ new Recipient() { NationalIdentityNumber = "3" },
+ new Recipient() { NationalIdentityNumber = "4" }]
+ };
+
+ Mock emailProcessingMock = new();
+ emailProcessingMock
+ .Setup(x => x.ProcessOrderWithoutAddressLookup(It.IsAny(), It.Is>(l => l.Count == 1 && l.Exists(r => r.NationalIdentityNumber == "2"))));
+
+ Mock smsProcessingMock = new();
+ smsProcessingMock
+ .Setup(x => x.ProcessOrderWithoutAddressLookup(It.IsAny(), It.Is>(l => l.Count == 3 && !l.Exists(r => r.NationalIdentityNumber == "2"))));
+
+ Mock contactPointMock = new();
+ contactPointMock
+ .Setup(cp => cp.AddPreferredContactPoints(order.NotificationChannel, It.IsAny>(), It.IsAny()))
+ .Callback, string?>((_, recipients, _) =>
+ {
+ foreach (var recipient in recipients)
+ {
+ if (recipient.NationalIdentityNumber == "1")
+ {
+ recipient.AddressInfo.Add(new SmsAddressPoint("+4799999999"));
+ recipient.IsReserved = false;
+ }
+ else if (recipient.NationalIdentityNumber == "2")
+ {
+ recipient.AddressInfo.Add(new EmailAddressPoint("2@user.com"));
+ recipient.IsReserved = false;
+ }
+ else if (recipient.NationalIdentityNumber == "3")
+ {
+ recipient.IsReserved = true;
+ }
+ else if (recipient.NationalIdentityNumber == "4")
+ {
+ recipient.IsReserved = false;
+ }
+ }
+ });
+
+ var preferredChannelProcessingService = new PreferredChannelProcessingService(
+ emailProcessingMock.Object,
+ smsProcessingMock.Object,
+ contactPointMock.Object);
+
+ // Act
+ await preferredChannelProcessingService.ProcessOrder(order);
+
+ // Assert
+ emailProcessingMock.VerifyAll();
+ smsProcessingMock.VerifyAll();
+ }
+
+ ///
+ /// Scenario: 1 recipient with email, 1 recipient with mobile, 1 reserved alid recipient, 1 recipient without contact point
+ /// Expected; SMS service is called with three recipients, email service is called with one recipient
+ ///
+ ///
+ /// Assertion logic is mostly within the setup of the mocked functions in the processing services
+ ///
+ [Fact]
+ public async Task ProcessOrder_SmsPreferredChannel_WithRetry()
+ {
+ // Arrange
+ NotificationOrder order = new()
+ {
+ NotificationChannel = NotificationChannel.SmsPreferred,
+ Recipients = [
+ new Recipient() { NationalIdentityNumber = "1" },
+ new Recipient() { NationalIdentityNumber = "2" },
+ new Recipient() { NationalIdentityNumber = "3" },
+ new Recipient() { NationalIdentityNumber = "4" }]
+ };
+
+ Mock emailProcessingMock = new();
+ emailProcessingMock
+ .Setup(x => x.ProcessOrderRetryWithoutAddressLookup(It.IsAny(), It.Is>(l => l.Count == 1 && l.Exists(r => r.NationalIdentityNumber == "2"))));
+
+ Mock smsProcessingMock = new();
+ smsProcessingMock
+ .Setup(x => x.ProcessOrderRetryWithoutAddressLookup(It.IsAny(), It.Is>(l => l.Count == 3 && !l.Exists(r => r.NationalIdentityNumber == "2"))));
+
+ Mock contactPointMock = new();
+ contactPointMock
+ .Setup(cp => cp.AddPreferredContactPoints(order.NotificationChannel, It.IsAny>(), It.IsAny()))
+ .Callback, string?>((_, recipients, _) =>
+ {
+ foreach (var recipient in recipients)
+ {
+ if (recipient.NationalIdentityNumber == "1")
+ {
+ recipient.AddressInfo.Add(new SmsAddressPoint("+4799999999"));
+ recipient.IsReserved = false;
+ }
+ else if (recipient.NationalIdentityNumber == "2")
+ {
+ recipient.AddressInfo.Add(new EmailAddressPoint("2@user.com"));
+ recipient.IsReserved = false;
+ }
+ else if (recipient.NationalIdentityNumber == "3")
+ {
+ recipient.IsReserved = true;
+ }
+ else if (recipient.NationalIdentityNumber == "4")
+ {
+ recipient.IsReserved = false;
+ }
+ }
+ });
+
+ var preferredChannelProcessingService = new PreferredChannelProcessingService(
+ emailProcessingMock.Object,
+ smsProcessingMock.Object,
+ contactPointMock.Object);
+
+ // Act
+ await preferredChannelProcessingService.ProcessOrderRetry(order);
+
+ // Assert
+ emailProcessingMock.VerifyAll();
+ smsProcessingMock.VerifyAll();
+ }
+ }
+}