diff --git a/src/Altinn.Notifications.Core/Enums/SmsNotificationResultType.cs b/src/Altinn.Notifications.Core/Enums/SmsNotificationResultType.cs
index 03b2239a..0d12645e 100644
--- a/src/Altinn.Notifications.Core/Enums/SmsNotificationResultType.cs
+++ b/src/Altinn.Notifications.Core/Enums/SmsNotificationResultType.cs
@@ -28,5 +28,10 @@ public enum SmsNotificationResultType
///
/// Failed, invalid mobilenumber
///
- Failed_InvalidRecipient
+ Failed_InvalidRecipient,
+
+ ///
+ /// Recipient mobile number was not identified
+ ///
+ Failed_RecipientNotIdentified
}
diff --git a/src/Altinn.Notifications.Core/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.Notifications.Core/Extensions/ServiceCollectionExtensions.cs
index 208a8f3f..af20e0c9 100644
--- a/src/Altinn.Notifications.Core/Extensions/ServiceCollectionExtensions.cs
+++ b/src/Altinn.Notifications.Core/Extensions/ServiceCollectionExtensions.cs
@@ -31,10 +31,13 @@ public static void AddCoreServices(this IServiceCollection services, IConfigurat
.AddSingleton()
.AddSingleton()
.AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
.AddSingleton()
.AddSingleton()
.AddSingleton()
.AddSingleton()
+ .AddSingleton()
.AddSingleton()
.AddSingleton()
.Configure(config.GetSection("KafkaSettings"))
diff --git a/src/Altinn.Notifications.Core/Models/Recipients/SmsRecipient.cs b/src/Altinn.Notifications.Core/Models/Recipients/SmsRecipient.cs
new file mode 100644
index 00000000..c182a4f0
--- /dev/null
+++ b/src/Altinn.Notifications.Core/Models/Recipients/SmsRecipient.cs
@@ -0,0 +1,17 @@
+namespace Altinn.Notifications.Core.Models.Recipients;
+
+///
+/// Class representing an sms recipient
+///
+public class SmsRecipient
+{
+ ///
+ /// Gets or sets the recipient id
+ ///
+ public string? RecipientId { get; set; } = null;
+
+ ///
+ /// Gets or sets the mobile number
+ ///
+ public string MobileNumber { get; set; } = string.Empty;
+}
diff --git a/src/Altinn.Notifications.Core/Persistence/IEmailNotificationRepository.cs b/src/Altinn.Notifications.Core/Persistence/IEmailNotificationRepository.cs
index bed06396..f1054d9c 100644
--- a/src/Altinn.Notifications.Core/Persistence/IEmailNotificationRepository.cs
+++ b/src/Altinn.Notifications.Core/Persistence/IEmailNotificationRepository.cs
@@ -27,8 +27,8 @@ public interface IEmailNotificationRepository
public Task UpdateSendStatus(Guid notificationId, EmailNotificationResultType status, string? operationId = null);
///
- /// Retrieves all email recipients for an order
+ /// Retrieves all processed email recipients for an order
///
- /// A list of emails
- public Task> GetRecipients(Guid notificationId);
+ /// A list of email recipients
+ public Task> GetRecipients(Guid orderId);
}
diff --git a/src/Altinn.Notifications.Core/Persistence/ISmsNotificationRepository.cs b/src/Altinn.Notifications.Core/Persistence/ISmsNotificationRepository.cs
index 0aca03e4..89b4d0b7 100644
--- a/src/Altinn.Notifications.Core/Persistence/ISmsNotificationRepository.cs
+++ b/src/Altinn.Notifications.Core/Persistence/ISmsNotificationRepository.cs
@@ -1,5 +1,6 @@
using Altinn.Notifications.Core.Models;
using Altinn.Notifications.Core.Models.Notification;
+using Altinn.Notifications.Core.Models.Recipients;
namespace Altinn.Notifications.Core.Persistence;
@@ -18,4 +19,10 @@ public interface ISmsNotificationRepository
///
/// A list of sms
public Task> GetNewNotifications();
+
+ ///
+ /// Retrieves all processed sms recipients for an order
+ ///
+ /// A list of sms recipients
+ public Task> GetRecipients(Guid orderId);
}
diff --git a/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs b/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs
new file mode 100644
index 00000000..dd0c267a
--- /dev/null
+++ b/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs
@@ -0,0 +1,55 @@
+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.Models.Recipients;
+using Altinn.Notifications.Core.Persistence;
+using Altinn.Notifications.Core.Services.Interfaces;
+
+namespace Altinn.Notifications.Core.Services;
+
+///
+/// Implementation of the
+///
+public class EmailOrderProcessingService : IEmailOrderProcessingService
+{
+ private readonly IEmailNotificationRepository _emailNotificationRepository;
+ private readonly IEmailNotificationService _emailService;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public EmailOrderProcessingService(
+ IEmailNotificationRepository emailNotificationRepository,
+ IEmailNotificationService emailService)
+ {
+ _emailNotificationRepository = emailNotificationRepository;
+ _emailService = emailService;
+ }
+
+ ///
+ public async Task ProcessOrder(NotificationOrder order)
+ {
+ foreach (Recipient recipient in order.Recipients)
+ {
+ await _emailService.CreateNotification(order.Id, order.RequestedSendTime, recipient);
+ }
+ }
+
+ ///
+ public async Task ProcessOrderRetry(NotificationOrder order)
+ {
+ List emailRecipients = await _emailNotificationRepository.GetRecipients(order.Id);
+ foreach (Recipient recipient in order.Recipients)
+ {
+ EmailAddressPoint? addressPoint = recipient.AddressInfo.Find(a => a.AddressType == AddressType.Email) as EmailAddressPoint;
+
+ if (!emailRecipients.Exists(er =>
+ er.RecipientId == (string.IsNullOrEmpty(recipient.RecipientId) ? null : recipient.RecipientId)
+ && er.ToAddress.Equals(addressPoint?.EmailAddress)))
+ {
+ await _emailService.CreateNotification(order.Id, order.RequestedSendTime, recipient);
+ }
+ }
+ }
+}
diff --git a/src/Altinn.Notifications.Core/Services/Interfaces/IEmailOrderProcessingService.cs b/src/Altinn.Notifications.Core/Services/Interfaces/IEmailOrderProcessingService.cs
new file mode 100644
index 00000000..e011af04
--- /dev/null
+++ b/src/Altinn.Notifications.Core/Services/Interfaces/IEmailOrderProcessingService.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 orders
+///
+public interface IEmailOrderProcessingService
+{
+ ///
+ /// 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/ISmsNotificationService.cs b/src/Altinn.Notifications.Core/Services/Interfaces/ISmsNotificationService.cs
new file mode 100644
index 00000000..09d3e892
--- /dev/null
+++ b/src/Altinn.Notifications.Core/Services/Interfaces/ISmsNotificationService.cs
@@ -0,0 +1,14 @@
+using Altinn.Notifications.Core.Models;
+
+namespace Altinn.Notifications.Core.Services.Interfaces;
+
+///
+/// Interface for sms notification service
+///
+public interface ISmsNotificationService
+{
+ ///
+ /// Creates a new sms notification based on the provided orderId and recipient
+ ///
+ public Task CreateNotification(Guid orderId, DateTime requestedSendTime, Recipient recipient);
+}
diff --git a/src/Altinn.Notifications.Core/Services/Interfaces/ISmsOrderProcessingService.cs b/src/Altinn.Notifications.Core/Services/Interfaces/ISmsOrderProcessingService.cs
new file mode 100644
index 00000000..535368dc
--- /dev/null
+++ b/src/Altinn.Notifications.Core/Services/Interfaces/ISmsOrderProcessingService.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 sms orders
+///
+public interface ISmsOrderProcessingService
+{
+ ///
+ /// 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/OrderProcessingService.cs b/src/Altinn.Notifications.Core/Services/OrderProcessingService.cs
index 0d02aaa9..68888568 100644
--- a/src/Altinn.Notifications.Core/Services/OrderProcessingService.cs
+++ b/src/Altinn.Notifications.Core/Services/OrderProcessingService.cs
@@ -1,13 +1,12 @@
using System.Diagnostics;
+
using Altinn.Notifications.Core.Configuration;
using Altinn.Notifications.Core.Enums;
using Altinn.Notifications.Core.Integrations;
-using Altinn.Notifications.Core.Models;
-using Altinn.Notifications.Core.Models.Address;
using Altinn.Notifications.Core.Models.Orders;
-using Altinn.Notifications.Core.Models.Recipients;
using Altinn.Notifications.Core.Persistence;
using Altinn.Notifications.Core.Services.Interfaces;
+
using Microsoft.Extensions.Options;
namespace Altinn.Notifications.Core.Services;
@@ -18,8 +17,8 @@ namespace Altinn.Notifications.Core.Services;
public class OrderProcessingService : IOrderProcessingService
{
private readonly IOrderRepository _orderRepository;
- private readonly IEmailNotificationRepository _emailNotificationRepository;
- private readonly IEmailNotificationService _emailService;
+ private readonly IEmailOrderProcessingService _emailProcessingService;
+ private readonly ISmsOrderProcessingService _smsProcessingService;
private readonly IKafkaProducer _producer;
private readonly string _pastDueOrdersTopic;
@@ -27,15 +26,15 @@ public class OrderProcessingService : IOrderProcessingService
/// Initializes a new instance of the class.
///
public OrderProcessingService(
- IOrderRepository orderRepository,
- IEmailNotificationRepository emailNotificationRepository,
- IEmailNotificationService emailService,
- IKafkaProducer producer,
+ IOrderRepository orderRepository,
+ IEmailOrderProcessingService emailProcessingService,
+ ISmsOrderProcessingService smsProcessingService,
+ IKafkaProducer producer,
IOptions kafkaSettings)
{
_orderRepository = orderRepository;
- _emailNotificationRepository = emailNotificationRepository;
- _emailService = emailService;
+ _emailProcessingService = emailProcessingService;
+ _smsProcessingService = smsProcessingService;
_producer = producer;
_pastDueOrdersTopic = kafkaSettings.Value.PastDueOrdersTopicName;
}
@@ -68,14 +67,14 @@ public async Task ProcessOrder(NotificationOrder order)
{
NotificationChannel ch = order.NotificationChannel;
- foreach (Recipient recipient in order.Recipients)
+ switch (ch)
{
- switch (ch)
- {
- case NotificationChannel.Email:
- await _emailService.CreateNotification(order.Id, order.RequestedSendTime, recipient);
- break;
- }
+ case NotificationChannel.Email:
+ await _emailProcessingService.ProcessOrder(order);
+ break;
+ case NotificationChannel.Sms:
+ await _smsProcessingService.ProcessOrder(order);
+ break;
}
await _orderRepository.SetProcessingStatus(order.Id, OrderProcessingStatus.Completed);
@@ -86,23 +85,14 @@ public async Task ProcessOrderRetry(NotificationOrder order)
{
NotificationChannel ch = order.NotificationChannel;
- List emailRecipients = await _emailNotificationRepository.GetRecipients(order.Id);
-
- foreach (Recipient recipient in order.Recipients)
+ switch (ch)
{
- switch (ch)
- {
- case NotificationChannel.Email:
- EmailAddressPoint? addressPoint = recipient.AddressInfo.Find(a => a.AddressType == AddressType.Email) as EmailAddressPoint;
-
- if (!emailRecipients.Exists(er => er.RecipientId == (string.IsNullOrEmpty(recipient.RecipientId) ? null : recipient.RecipientId)
- && er.ToAddress.Equals(addressPoint?.EmailAddress)))
- {
- await _emailService.CreateNotification(order.Id, order.RequestedSendTime, recipient);
- }
-
- break;
- }
+ case NotificationChannel.Email:
+ await _emailProcessingService.ProcessOrderRetry(order);
+ break;
+ case NotificationChannel.Sms:
+ await _smsProcessingService.ProcessOrderRetry(order);
+ break;
}
await _orderRepository.SetProcessingStatus(order.Id, OrderProcessingStatus.Completed);
diff --git a/src/Altinn.Notifications.Core/Services/SmsNotificationService.cs b/src/Altinn.Notifications.Core/Services/SmsNotificationService.cs
new file mode 100644
index 00000000..57e2a718
--- /dev/null
+++ b/src/Altinn.Notifications.Core/Services/SmsNotificationService.cs
@@ -0,0 +1,61 @@
+using Altinn.Notifications.Core.Enums;
+using Altinn.Notifications.Core.Models;
+using Altinn.Notifications.Core.Models.Address;
+using Altinn.Notifications.Core.Models.Notification;
+using Altinn.Notifications.Core.Persistence;
+using Altinn.Notifications.Core.Services.Interfaces;
+
+namespace Altinn.Notifications.Core.Services;
+
+///
+/// Implementation of
+///
+public class SmsNotificationService : ISmsNotificationService
+{
+ private readonly IGuidService _guid;
+ private readonly IDateTimeService _dateTime;
+ private readonly ISmsNotificationRepository _repository;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SmsNotificationService(
+ IGuidService guid,
+ IDateTimeService dateTime,
+ ISmsNotificationRepository repository)
+ {
+ _guid = guid;
+ _dateTime = dateTime;
+ _repository = repository;
+ }
+
+ ///
+ public async Task CreateNotification(Guid orderId, DateTime requestedSendTime, Recipient recipient)
+ {
+ SmsAddressPoint? addressPoint = recipient.AddressInfo.Find(a => a.AddressType == AddressType.Sms) as SmsAddressPoint;
+
+ if (!string.IsNullOrEmpty(addressPoint?.MobileNumber))
+ {
+ await CreateNotificationForRecipient(orderId, requestedSendTime, recipient.RecipientId, addressPoint!.MobileNumber, SmsNotificationResultType.New);
+ }
+ else
+ {
+ await CreateNotificationForRecipient(orderId, requestedSendTime, recipient.RecipientId, string.Empty, SmsNotificationResultType.Failed_RecipientNotIdentified);
+ }
+ }
+
+ private async Task CreateNotificationForRecipient(Guid orderId, DateTime requestedSendTime, string recipientId, string recipientNumber, SmsNotificationResultType type)
+ {
+ var smsNotification = new SmsNotification()
+ {
+ Id = _guid.NewGuid(),
+ OrderId = orderId,
+ RequestedSendTime = requestedSendTime,
+ RecipientNumber = recipientNumber,
+ RecipientId = string.IsNullOrEmpty(recipientId) ? null : recipientId,
+ SendResult = new(type, _dateTime.UtcNow())
+ };
+
+ await _repository.AddNotification(smsNotification, requestedSendTime.AddHours(1));
+ }
+}
diff --git a/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs b/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs
new file mode 100644
index 00000000..d5535bea
--- /dev/null
+++ b/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs
@@ -0,0 +1,53 @@
+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.Models.Recipients;
+using Altinn.Notifications.Core.Persistence;
+using Altinn.Notifications.Core.Services.Interfaces;
+
+namespace Altinn.Notifications.Core.Services;
+
+///
+/// Implementation of the
+///
+public class SmsOrderProcessingService : ISmsOrderProcessingService
+{
+ private readonly ISmsNotificationRepository _smsNotificationRepository;
+ private readonly ISmsNotificationService _smsService;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SmsOrderProcessingService(ISmsNotificationRepository smsNotificationRepository, ISmsNotificationService smsService)
+ {
+ _smsNotificationRepository = smsNotificationRepository;
+ _smsService = smsService;
+ }
+
+ ///
+ public async Task ProcessOrder(NotificationOrder order)
+ {
+ foreach (Recipient recipient in order.Recipients)
+ {
+ await _smsService.CreateNotification(order.Id, order.RequestedSendTime, recipient);
+ }
+ }
+
+ ///
+ public async Task ProcessOrderRetry(NotificationOrder order)
+ {
+ List smsRecipients = await _smsNotificationRepository.GetRecipients(order.Id);
+ foreach (Recipient recipient in order.Recipients)
+ {
+ SmsAddressPoint? addressPoint = recipient.AddressInfo.Find(a => a.AddressType == AddressType.Sms) as SmsAddressPoint;
+
+ if (!smsRecipients.Exists(sr =>
+ sr.RecipientId == (string.IsNullOrEmpty(recipient.RecipientId) ? null : recipient.RecipientId)
+ && sr.MobileNumber.Equals(addressPoint?.MobileNumber)))
+ {
+ await _smsService.CreateNotification(order.Id, order.RequestedSendTime, recipient);
+ }
+ }
+ }
+}
diff --git a/src/Altinn.Notifications.Persistence/Migration/v0.19/01-alter-types.sql b/src/Altinn.Notifications.Persistence/Migration/v0.19/01-alter-types.sql
new file mode 100644
index 00000000..e4d7ab19
--- /dev/null
+++ b/src/Altinn.Notifications.Persistence/Migration/v0.19/01-alter-types.sql
@@ -0,0 +1 @@
+ALTER TYPE smsnotificationresulttype ADD VALUE 'Failed_RecipientNotIdentified';
diff --git a/src/Altinn.Notifications.Persistence/Migration/v0.19/02-setup-functions.sql b/src/Altinn.Notifications.Persistence/Migration/v0.19/02-setup-functions.sql
new file mode 100644
index 00000000..50546b18
--- /dev/null
+++ b/src/Altinn.Notifications.Persistence/Migration/v0.19/02-setup-functions.sql
@@ -0,0 +1,17 @@
+CREATE OR REPLACE FUNCTION notifications.getsmsrecipients(_orderid uuid)
+RETURNS TABLE(
+ recipientid text,
+ mobilenumber text
+)
+LANGUAGE 'plpgsql'
+AS $BODY$
+DECLARE
+__orderid BIGINT := (SELECT _id from notifications.orders
+ where alternateid = _orderid);
+BEGIN
+RETURN query
+ SELECT s.recipientid, s.mobilenumber
+ FROM notifications.smsnotifications s
+ WHERE s._orderid = __orderid;
+END;
+$BODY$;
\ No newline at end of file
diff --git a/src/Altinn.Notifications.Persistence/Repository/EmailNotificationRepository.cs b/src/Altinn.Notifications.Persistence/Repository/EmailNotificationRepository.cs
index 50ab382d..171a1d27 100644
--- a/src/Altinn.Notifications.Persistence/Repository/EmailNotificationRepository.cs
+++ b/src/Altinn.Notifications.Persistence/Repository/EmailNotificationRepository.cs
@@ -21,7 +21,7 @@ public class EmailNotificationRepository : IEmailNotificationRepository
private const string _insertEmailNotificationSql = "call notifications.insertemailnotification($1, $2, $3, $4, $5, $6, $7)"; // (__orderid, _alternateid, _recipientid, _toaddress, _result, _resulttime, _expirytime)
private const string _getEmailNotificationsSql = "select * from notifications.getemails_statusnew_updatestatus()";
private const string _updateEmailStatus = "call notifications.updateemailstatus($1, $2, $3)"; // (_alternateid, _result, _operationid)
- private const string _getEmailRecipients = "select * from notifications.getemailrecipients($1)"; // (_alternateid)
+ private const string _getEmailRecipients = "select * from notifications.getemailrecipients($1)"; // (_orderid)
///
/// Initializes a new instance of the class.
@@ -94,13 +94,13 @@ public async Task UpdateSendStatus(Guid notificationId, EmailNotificationResultT
}
///
- public async Task> GetRecipients(Guid notificationId)
+ public async Task> GetRecipients(Guid orderId)
{
List searchResult = new();
await using NpgsqlCommand pgcom = _dataSource.CreateCommand(_getEmailRecipients);
using TelemetryTracker tracker = new(_telemetryClient, pgcom);
- pgcom.Parameters.AddWithValue(NpgsqlDbType.Uuid, notificationId);
+ pgcom.Parameters.AddWithValue(NpgsqlDbType.Uuid, orderId);
await using (NpgsqlDataReader reader = await pgcom.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
diff --git a/src/Altinn.Notifications.Persistence/Repository/SmsNotificationRepository.cs b/src/Altinn.Notifications.Persistence/Repository/SmsNotificationRepository.cs
index b4abfb3b..190c324e 100644
--- a/src/Altinn.Notifications.Persistence/Repository/SmsNotificationRepository.cs
+++ b/src/Altinn.Notifications.Persistence/Repository/SmsNotificationRepository.cs
@@ -1,5 +1,6 @@
using Altinn.Notifications.Core.Models;
using Altinn.Notifications.Core.Models.Notification;
+using Altinn.Notifications.Core.Models.Recipients;
using Altinn.Notifications.Core.Persistence;
using Altinn.Notifications.Persistence.Extensions;
@@ -21,6 +22,7 @@ public class SmsNotificationRepository : ISmsNotificationRepository
private const string _insertSmsNotificationSql = "call notifications.insertsmsnotification($1, $2, $3, $4, $5, $6, $7)"; // (__orderid, _alternateid, _recipientid, _mobilenumber, _result, _resulttime, _expirytime)
private const string _getSmsNotificationsSql = "select * from notifications.getsms_statusnew_updatestatus()";
+ private const string _getSmsRecipients = "select * from notifications.getsmsrecipients($1)"; // (_orderid)
///
/// Initializes a new instance of the class.
@@ -51,6 +53,30 @@ public async Task AddNotification(SmsNotification notification, DateTime expiry)
tracker.Track();
}
+ ///
+ public async Task> GetRecipients(Guid orderId)
+ {
+ List searchResult = new();
+
+ await using NpgsqlCommand pgcom = _dataSource.CreateCommand(_getSmsRecipients);
+ using TelemetryTracker tracker = new(_telemetryClient, pgcom);
+ pgcom.Parameters.AddWithValue(NpgsqlDbType.Uuid, orderId);
+ await using (NpgsqlDataReader reader = await pgcom.ExecuteReaderAsync())
+ {
+ while (await reader.ReadAsync())
+ {
+ searchResult.Add(new SmsRecipient()
+ {
+ RecipientId = reader.GetValue("recipientid"),
+ MobileNumber = reader.GetValue("mobilenumber")
+ });
+ }
+ }
+
+ tracker.Track();
+ return searchResult;
+ }
+
///
public async Task> GetNewNotifications()
{
diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications.Persistence/SmsNotificationRepositoryTests.cs b/test/Altinn.Notifications.IntegrationTests/Notifications.Persistence/SmsNotificationRepositoryTests.cs
index 7f7de3b4..5b3bfc5a 100644
--- a/test/Altinn.Notifications.IntegrationTests/Notifications.Persistence/SmsNotificationRepositoryTests.cs
+++ b/test/Altinn.Notifications.IntegrationTests/Notifications.Persistence/SmsNotificationRepositoryTests.cs
@@ -1,6 +1,7 @@
using Altinn.Notifications.Core.Models;
using Altinn.Notifications.Core.Models.Notification;
using Altinn.Notifications.Core.Models.Orders;
+using Altinn.Notifications.Core.Models.Recipients;
using Altinn.Notifications.Core.Persistence;
using Altinn.Notifications.IntegrationTests.Utils;
using Altinn.Notifications.Persistence.Repository;
@@ -80,4 +81,28 @@ public async Task GetNewNotifications()
// Assert
Assert.NotEmpty(smsToBeSent.Where(s => s.NotificationId == smsNotification.Id));
}
+
+ [Fact]
+ public async Task GetRecipients()
+ {
+ // Arrange
+ SmsNotificationRepository repo = (SmsNotificationRepository)ServiceUtil
+ .GetServices(new List() { typeof(ISmsNotificationRepository) })
+ .First(i => i.GetType() == typeof(SmsNotificationRepository));
+
+ (NotificationOrder order, SmsNotification smsNotification) = await PostgreUtil.PopulateDBWithOrderAndSmsNotification();
+ orderIdsToDelete.Add(order.Id);
+ string expectedNumber = smsNotification.RecipientNumber;
+ string? expectedRecipientId = smsNotification.RecipientId;
+
+ // Act
+ List actual = await repo.GetRecipients(order.Id);
+
+ SmsRecipient actualRecipient = actual[0];
+
+ // Assert
+ Assert.Single(actual);
+ Assert.Equal(expectedNumber, actualRecipient.MobileNumber);
+ Assert.Equal(expectedRecipientId, actualRecipient.RecipientId);
+ }
}
diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationServiceTests.cs
index 69df0b12..91512c21 100644
--- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationServiceTests.cs
+++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationServiceTests.cs
@@ -106,7 +106,7 @@ public async Task CreateEmailNotification_ToAddressDefined_ResultNew()
}
[Fact]
- public async Task CreateEmailNotification_ToAddressDefined_ResultFailedRecipientNotDefined()
+ public async Task CreateEmailNotification_ToAddressMissing_ResultFailedRecipientNotDefined()
{
// Arrange
Guid id = Guid.NewGuid();
diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailOrderProcessingServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailOrderProcessingServiceTests.cs
new file mode 100644
index 00000000..591dda68
--- /dev/null
+++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailOrderProcessingServiceTests.cs
@@ -0,0 +1,164 @@
+using System;
+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.Models.Recipients;
+using Altinn.Notifications.Core.Persistence;
+using Altinn.Notifications.Core.Services;
+using Altinn.Notifications.Core.Services.Interfaces;
+
+using Moq;
+
+using Xunit;
+
+namespace Altinn.Notifications.Tests.Notifications.Core.TestingServices;
+
+public class EmailOrderProcessingServiceTests
+{
+ [Fact]
+ public async Task ProcessOrder_ServiceCalledOnceForEachRecipient()
+ {
+ // Arrange
+ var order = new NotificationOrder()
+ {
+ Id = Guid.NewGuid(),
+ NotificationChannel = NotificationChannel.Email,
+ Recipients = new List()
+ {
+ new(),
+ new()
+ }
+ };
+
+ var serviceMock = new Mock();
+ serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()));
+
+ var service = GetTestService(emailService: serviceMock.Object);
+
+ // Act
+ await service.ProcessOrder(order);
+
+ // Assert
+ serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2));
+ }
+
+ [Fact]
+ public async Task ProcessOrder_ExpectedInputToService()
+ {
+ // Arrange
+ DateTime requested = DateTime.UtcNow;
+ Guid orderId = Guid.NewGuid();
+
+ var order = new NotificationOrder()
+ {
+ Id = orderId,
+ NotificationChannel = NotificationChannel.Email,
+ RequestedSendTime = requested,
+ Recipients = new List()
+ {
+ new Recipient("skd", new List() { new EmailAddressPoint("test@test.com") })
+ }
+ };
+
+ Recipient expectedRecipient = new("skd", new List() { new EmailAddressPoint("test@test.com") });
+
+ var serviceMock = new Mock();
+ serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.Is(d => d.Equals(requested)), It.Is(r => AssertUtils.AreEquivalent(expectedRecipient, r))));
+
+ var service = GetTestService(emailService: serviceMock.Object);
+
+ // Act
+ await service.ProcessOrder(order);
+
+ // Assert
+ serviceMock.VerifyAll();
+ }
+
+ [Fact]
+ public async Task ProcessOrder_ServiceThrowsException_RepositoryNotCalled()
+ {
+ // Arrange
+ var order = new NotificationOrder()
+ {
+ NotificationChannel = NotificationChannel.Email,
+ Recipients = new List()
+ {
+ new Recipient()
+ }
+ };
+
+ var serviceMock = new Mock();
+ serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()))
+ .ThrowsAsync(new Exception());
+
+ var repoMock = new Mock();
+ repoMock.Setup(r => r.SetProcessingStatus(It.IsAny(), It.IsAny()));
+
+ var service = GetTestService(emailService: serviceMock.Object);
+
+ // Act
+ await Assert.ThrowsAsync(async () => await service.ProcessOrder(order));
+
+ // Assert
+ serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once);
+ repoMock.Verify(r => r.SetProcessingStatus(It.IsAny(), It.IsAny()), Times.Never);
+ }
+
+ [Fact]
+ public async Task ProcessOrderRetry_ServiceCalledIfRecipientNotInDatabase()
+ {
+ // Arrange
+ var order = new NotificationOrder()
+ {
+ Id = Guid.NewGuid(),
+ NotificationChannel = NotificationChannel.Email,
+ Recipients = new List()
+ {
+ new Recipient(),
+ new Recipient("skd", new List() { new EmailAddressPoint("test@test.com") }),
+ new Recipient(new List() { new EmailAddressPoint("test@domain.com") })
+ }
+ };
+
+ var serviceMock = new Mock();
+ serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()));
+
+ var emailRepoMock = new Mock();
+ emailRepoMock.Setup(e => e.GetRecipients(It.IsAny())).ReturnsAsync(new List() { new EmailRecipient() { RecipientId = "skd", ToAddress = "test@test.com" } });
+
+ var service = GetTestService(emailRepo: emailRepoMock.Object, emailService: serviceMock.Object);
+
+ // Act
+ await service.ProcessOrderRetry(order);
+
+ // Assert
+ serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2));
+ emailRepoMock.Verify(e => e.GetRecipients(It.IsAny()), Times.Once);
+ }
+
+ private static EmailOrderProcessingService GetTestService(
+ IEmailNotificationRepository? emailRepo = null,
+ IEmailNotificationService? emailService = null)
+ {
+ if (emailRepo == null)
+ {
+ var emailRepoMock = new Mock();
+ emailRepo = emailRepoMock.Object;
+ }
+
+ if (emailService == null)
+ {
+ var emailServiceMock = new Mock();
+ emailService = emailServiceMock.Object;
+ }
+
+ var smsRepoMock = new Mock();
+ var smsServiceMock = new Mock();
+
+ return new EmailOrderProcessingService(emailRepo, emailService);
+ }
+}
diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/OrderProcessingServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/OrderProcessingServiceTests.cs
index 68ca7c46..7c252c3f 100644
--- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/OrderProcessingServiceTests.cs
+++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/OrderProcessingServiceTests.cs
@@ -2,17 +2,12 @@
using System.Collections.Generic;
using System.Threading.Tasks;
-using Altinn.Notifications.Core.Configuration;
using Altinn.Notifications.Core.Enums;
using Altinn.Notifications.Core.Integrations;
-using Altinn.Notifications.Core.Models;
-using Altinn.Notifications.Core.Models.Address;
using Altinn.Notifications.Core.Models.Orders;
-using Altinn.Notifications.Core.Models.Recipients;
using Altinn.Notifications.Core.Persistence;
using Altinn.Notifications.Core.Services;
using Altinn.Notifications.Core.Services.Interfaces;
-using Altinn.Notifications.Integrations.Configuration;
using Microsoft.Extensions.Options;
@@ -20,6 +15,8 @@
using Xunit;
+using static Altinn.Authorization.ABAC.Constants.XacmlConstants;
+
namespace Altinn.Notifications.Tests.Notifications.Core.TestingServices;
public class OrderProcessingServiceTests
@@ -50,164 +47,139 @@ public async Task StartProcessingPastDueOrders_ProducerCalledOnceForEachOrder()
}
[Fact]
- public async Task ProcessOrder_EmailNotificationChannel_ServiceCalledOnceForEachRecipient()
+ public async Task ProcessOrder_EmailOrder_EmailServiceCalled()
{
- // Arrange
- var order = new NotificationOrder()
+ // Arrange
+ NotificationOrder order = new()
{
- Id = Guid.NewGuid(),
NotificationChannel = NotificationChannel.Email,
- Recipients = new List()
- {
- new Recipient(),
- new Recipient()
- }
};
- var serviceMock = new Mock();
- serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()));
+ var emailMockService = new Mock();
+ emailMockService.Setup(e => e.ProcessOrder(It.IsAny()));
+
+ var smsMockService = new Mock();
+ smsMockService.Setup(s => s.ProcessOrder(It.IsAny()));
- var service = GetTestService(emailService: serviceMock.Object);
+ var orderProcessingService = GetTestService(emailMock: emailMockService.Object, smsMock: smsMockService.Object);
// Act
- await service.ProcessOrder(order);
+ await orderProcessingService.ProcessOrder(order);
// Assert
- serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2));
+ emailMockService.Verify(e => e.ProcessOrder(It.IsAny()), Times.Once);
+ smsMockService.Verify(s => s.ProcessOrder(It.IsAny()), Times.Never);
}
[Fact]
- public async Task ProcessOrder_EmailNotificationChannel_ExpectedInputToService()
+ public async Task ProcessOrder_SmsOrder_SmsServiceCalled()
{
- // Arrange
- DateTime requested = DateTime.UtcNow;
- Guid orderId = Guid.NewGuid();
-
- var order = new NotificationOrder()
+ // Arrange
+ NotificationOrder order = new()
{
- Id = orderId,
- NotificationChannel = NotificationChannel.Email,
- RequestedSendTime = requested,
- Recipients = new List()
- {
- new Recipient("skd", new List() { new EmailAddressPoint("test@test.com") })
- }
+ NotificationChannel = NotificationChannel.Sms,
};
- Recipient expectedRecipient = new("skd", new List() { new EmailAddressPoint("test@test.com") });
-
- var serviceMock = new Mock();
- serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.Is(d => d.Equals(requested)), It.Is(r => AssertUtils.AreEquivalent(expectedRecipient, r))));
+ var smsMockService = new Mock();
+ smsMockService.Setup(s => s.ProcessOrder(It.IsAny()));
- var repoMock = new Mock();
- repoMock.Setup(r => r.SetProcessingStatus(It.Is(s => s.Equals(orderId)), It.Is(s => s == OrderProcessingStatus.Completed)));
+ var emailMockService = new Mock();
+ emailMockService.Setup(e => e.ProcessOrder(It.IsAny()));
- var service = GetTestService(repo: repoMock.Object, emailService: serviceMock.Object);
+ var orderProcessingService = GetTestService(emailMock: emailMockService.Object, smsMock: smsMockService.Object);
// Act
- await service.ProcessOrder(order);
+ await orderProcessingService.ProcessOrder(order);
// Assert
- serviceMock.VerifyAll();
- repoMock.VerifyAll();
+ smsMockService.Verify(s => s.ProcessOrder(It.IsAny()), Times.Once);
+ emailMockService.Verify(e => e.ProcessOrder(It.IsAny()), Times.Never);
}
[Fact]
- public async Task ProcessOrder_EmailNotificationChannel_ServiceThrowsException_RepositoryNotCalled()
+ public async Task ProcessOrder_SerivceThrowsException_ProcessingStatusIsNotSet()
{
- // Arrange
- var order = new NotificationOrder()
+ // Arrange
+ NotificationOrder order = new()
{
- NotificationChannel = NotificationChannel.Email,
- Recipients = new List()
- {
- new Recipient()
- }
+ NotificationChannel = NotificationChannel.Sms,
};
- var serviceMock = new Mock();
- serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()))
- .ThrowsAsync(new Exception());
+ var smsMockService = new Mock();
+ smsMockService.Setup(s => s.ProcessOrder(It.IsAny())).Throws(new Exception());
var repoMock = new Mock();
- repoMock.Setup(r => r.SetProcessingStatus(It.IsAny(), It.IsAny()));
+ repoMock.Setup(r => r.SetProcessingStatus(It.IsAny(), It.Is(s => s.Equals(OrderProcessingStatus.Completed))));
- var service = GetTestService(repo: repoMock.Object, emailService: serviceMock.Object);
+ var orderProcessingService = GetTestService(repo: repoMock.Object, smsMock: smsMockService.Object);
- // Act
- await Assert.ThrowsAsync(async () => await service.ProcessOrder(order));
+ // Act
+ await Assert.ThrowsAsync(async () => await orderProcessingService.ProcessOrder(order));
// Assert
- serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once);
- repoMock.Verify(r => r.SetProcessingStatus(It.IsAny(), It.IsAny()), Times.Never);
+ smsMockService.Verify(s => s.ProcessOrder(It.IsAny()), Times.Once);
+ repoMock.Verify(
+ r => r.SetProcessingStatus(It.IsAny(), It.Is(s => s.Equals(OrderProcessingStatus.Completed))),
+ Times.Never);
}
[Fact]
- public async Task ProcessOrderRetry_EmailNotificationChannel_ServiceCalledIfEmailNotificationNotCreated()
+ public async Task ProcessOrderRetry_SmsOrder_SmsServiceCalled()
{
- // Arrange
- var order = new NotificationOrder()
+ // Arrange
+ NotificationOrder order = new()
{
- Id = Guid.NewGuid(),
- NotificationChannel = NotificationChannel.Email,
- Recipients = new List()
- {
- new Recipient(),
- new Recipient("skd", new List() { new EmailAddressPoint("test@test.com") })
- }
+ NotificationChannel = NotificationChannel.Sms,
};
- var serviceMock = new Mock();
- serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()));
+ var smsMockService = new Mock();
+ smsMockService.Setup(s => s.ProcessOrderRetry(It.IsAny()));
- var emailRepoMock = new Mock();
- emailRepoMock.Setup(e => e.GetRecipients(It.IsAny())).ReturnsAsync(new List() { new EmailRecipient() { RecipientId = "skd", ToAddress = "test@test.com" } });
+ var emailMockService = new Mock();
+ emailMockService.Setup(e => e.ProcessOrderRetry(It.IsAny()));
- var service = GetTestService(emailRepo: emailRepoMock.Object, emailService: serviceMock.Object);
+ var orderProcessingService = GetTestService(emailMock: emailMockService.Object, smsMock: smsMockService.Object);
// Act
- await service.ProcessOrderRetry(order);
+ await orderProcessingService.ProcessOrderRetry(order);
// Assert
- serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once);
- emailRepoMock.Verify(e => e.GetRecipients(It.IsAny()), Times.Once);
+ smsMockService.Verify(s => s.ProcessOrderRetry(It.IsAny()), Times.Once);
+ emailMockService.Verify(e => e.ProcessOrderRetry(It.IsAny()), Times.Never);
}
[Fact]
- public async Task ProcessOrderRetry_EmailNotificationChannel_ServiceThrowsException_OrderRepositoryNotCalled()
+ public async Task ProcessOrderRetry_SerivceThrowsException_ProcessingStatusIsNotSet()
{
- // Arrange
- var order = new NotificationOrder()
+ // Arrange
+ NotificationOrder order = new()
{
- NotificationChannel = NotificationChannel.Email,
- Recipients = new List()
- {
- new Recipient()
- }
+ NotificationChannel = NotificationChannel.Sms,
};
- var serviceMock = new Mock();
- serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()))
- .ThrowsAsync(new Exception());
+ var smsMockService = new Mock();
+ smsMockService.Setup(s => s.ProcessOrderRetry(It.IsAny())).Throws(new Exception());
var repoMock = new Mock();
- repoMock.Setup(r => r.SetProcessingStatus(It.IsAny(), It.IsAny()));
+ repoMock.Setup(r => r.SetProcessingStatus(It.IsAny(), It.Is(s => s.Equals(OrderProcessingStatus.Completed))));
- var emailRepoMock = new Mock();
- emailRepoMock.Setup(e => e.GetRecipients(It.IsAny())).ReturnsAsync(new List() { new EmailRecipient() { RecipientId = "skd", ToAddress = "test@test.com" } });
+ var orderProcessingService = GetTestService(repo: repoMock.Object, smsMock: smsMockService.Object);
- var service = GetTestService(repo: repoMock.Object, emailRepo: emailRepoMock.Object, emailService: serviceMock.Object);
-
- // Act
- await Assert.ThrowsAsync(async () => await service.ProcessOrderRetry(order));
+ // Act
+ await Assert.ThrowsAsync(async () => await orderProcessingService.ProcessOrderRetry(order));
// Assert
- serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once);
- repoMock.Verify(r => r.SetProcessingStatus(It.IsAny(), It.IsAny()), Times.Never);
- emailRepoMock.Verify(e => e.GetRecipients(It.IsAny()), Times.Once);
+ smsMockService.Verify(s => s.ProcessOrderRetry(It.IsAny()), Times.Once);
+ repoMock.Verify(
+ r => r.SetProcessingStatus(It.IsAny(), It.Is(s => s.Equals(OrderProcessingStatus.Completed))),
+ Times.Never);
}
- private static OrderProcessingService GetTestService(IOrderRepository? repo = null, IEmailNotificationRepository? emailRepo = null, IEmailNotificationService? emailService = null, IKafkaProducer? producer = null)
+ private static OrderProcessingService GetTestService(
+ IOrderRepository? repo = null,
+ IEmailOrderProcessingService? emailMock = null,
+ ISmsOrderProcessingService? smsMock = null,
+ IKafkaProducer? producer = null)
{
if (repo == null)
{
@@ -215,16 +187,16 @@ private static OrderProcessingService GetTestService(IOrderRepository? repo = nu
repo = repoMock.Object;
}
- if (emailRepo == null)
+ if (emailMock == null)
{
- var emailRepoMock = new Mock();
- emailRepo = emailRepoMock.Object;
+ var emailMockService = new Mock();
+ emailMock = emailMockService.Object;
}
- if (emailService == null)
+ if (smsMock == null)
{
- var emailServiceMock = new Mock();
- emailService = emailServiceMock.Object;
+ var smsMockService = new Mock();
+ smsMock = smsMockService.Object;
}
if (producer == null)
@@ -235,6 +207,6 @@ private static OrderProcessingService GetTestService(IOrderRepository? repo = nu
var kafkaSettings = new Altinn.Notifications.Core.Configuration.KafkaSettings() { PastDueOrdersTopicName = _pastDueTopicName };
- return new OrderProcessingService(repo, emailRepo, emailService, producer, Options.Create(kafkaSettings));
+ return new OrderProcessingService(repo, emailMock, smsMock, producer, Options.Create(kafkaSettings));
}
}
diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsNotificationServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsNotificationServiceTests.cs
new file mode 100644
index 00000000..19962323
--- /dev/null
+++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsNotificationServiceTests.cs
@@ -0,0 +1,125 @@
+using System;
+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.Notification;
+using Altinn.Notifications.Core.Persistence;
+using Altinn.Notifications.Core.Services;
+using Altinn.Notifications.Core.Services.Interfaces;
+
+using Moq;
+
+using Xunit;
+
+namespace Altinn.Notifications.Tests.Notifications.Core.TestingServices;
+
+public class SmsNotificationServiceTests
+{
+ [Fact]
+ public async Task CreateNotifications_NewSmsNotification_RepositoryCalledOnce()
+ {
+ // Arrange
+ var repoMock = new Mock();
+ var guidService = new Mock();
+ guidService
+ .Setup(g => g.NewGuid())
+ .Returns(Guid.NewGuid());
+
+ var dateTimeService = new Mock();
+ dateTimeService
+ .Setup(d => d.UtcNow())
+ .Returns(DateTime.UtcNow);
+
+ var service = GetTestService(repo: repoMock.Object);
+
+ // Act
+ await service.CreateNotification(Guid.NewGuid(), DateTime.UtcNow, new Recipient("recipientId", new List() { new SmsAddressPoint("999999999") }));
+
+ // Assert
+ repoMock.Verify(r => r.AddNotification(It.IsAny(), It.IsAny()), Times.Once);
+ }
+
+ [Fact]
+ public async Task CreateNotification_RecipientNumberIsDefined_ResultNew()
+ {
+ // Arrange
+ Guid id = Guid.NewGuid();
+ Guid orderId = Guid.NewGuid();
+ DateTime requestedSendTime = DateTime.UtcNow;
+ DateTime dateTimeOutput = DateTime.UtcNow;
+ DateTime expectedExpiry = requestedSendTime.AddHours(1);
+
+ SmsNotification expected = new()
+ {
+ Id = id,
+ OrderId = orderId,
+ RequestedSendTime = requestedSendTime,
+ RecipientNumber = "+4799999999",
+ SendResult = new(SmsNotificationResultType.New, dateTimeOutput),
+ };
+
+ var repoMock = new Mock();
+ repoMock.Setup(r => r.AddNotification(It.Is(e => AssertUtils.AreEquivalent(expected, e)), It.Is(d => d == expectedExpiry)));
+
+ var service = GetTestService(repo: repoMock.Object, guidOutput: id, dateTimeOutput: dateTimeOutput);
+
+ // Act
+ await service.CreateNotification(orderId, requestedSendTime, new Recipient(new List() { new SmsAddressPoint("+4799999999") }));
+
+ // Assert
+ repoMock.Verify(r => r.AddNotification(It.Is(e => AssertUtils.AreEquivalent(expected, e)), It.Is(d => d == expectedExpiry)), Times.Once);
+ }
+
+ [Fact]
+ public async Task CreateNotification_RecipientNumberMissing_ResultFailedRecipientNotDefined()
+ {
+ // Arrange
+ Guid id = Guid.NewGuid();
+ Guid orderId = Guid.NewGuid();
+ DateTime requestedSendTime = DateTime.UtcNow;
+ DateTime dateTimeOutput = DateTime.UtcNow;
+ DateTime expectedExpiry = dateTimeOutput;
+
+ SmsNotification expected = new()
+ {
+ Id = id,
+ OrderId = orderId,
+ RequestedSendTime = requestedSendTime,
+ SendResult = new(SmsNotificationResultType.Failed_RecipientNotIdentified, dateTimeOutput),
+ };
+
+ var repoMock = new Mock();
+ repoMock.Setup(r => r.AddNotification(It.Is(e => AssertUtils.AreEquivalent(expected, e)), It.Is(d => d == expectedExpiry)));
+
+ var service = GetTestService(repo: repoMock.Object, guidOutput: id, dateTimeOutput: dateTimeOutput);
+
+ // Act
+ await service.CreateNotification(orderId, requestedSendTime, new Recipient(new List()));
+
+ // Assert
+ repoMock.Verify();
+ }
+
+ private static SmsNotificationService GetTestService(ISmsNotificationRepository? repo = null, Guid? guidOutput = null, DateTime? dateTimeOutput = null)
+ {
+ var guidService = new Mock();
+ guidService
+ .Setup(g => g.NewGuid())
+ .Returns(guidOutput ?? Guid.NewGuid());
+
+ var dateTimeService = new Mock();
+ dateTimeService
+ .Setup(d => d.UtcNow())
+ .Returns(dateTimeOutput ?? DateTime.UtcNow);
+ if (repo == null)
+ {
+ var repoMock = new Mock();
+ repo = repoMock.Object;
+ }
+
+ return new SmsNotificationService(guidService.Object, dateTimeService.Object, repo);
+ }
+}
diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsOrderProcessingServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsOrderProcessingServiceTests.cs
new file mode 100644
index 00000000..40127650
--- /dev/null
+++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsOrderProcessingServiceTests.cs
@@ -0,0 +1,131 @@
+using System;
+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.Models.Recipients;
+using Altinn.Notifications.Core.Persistence;
+using Altinn.Notifications.Core.Services;
+using Altinn.Notifications.Core.Services.Interfaces;
+
+using Moq;
+
+using Xunit;
+
+namespace Altinn.Notifications.Tests.Notifications.Core.TestingServices;
+
+public class SmsOrderProcessingServiceTests
+{
+ [Fact]
+ public async Task ProcessOrder_ExpectedInputToService()
+ {
+ // Arrange
+ DateTime requested = DateTime.UtcNow;
+ Guid orderId = Guid.NewGuid();
+
+ var order = new NotificationOrder()
+ {
+ Id = orderId,
+ NotificationChannel = NotificationChannel.Sms,
+ RequestedSendTime = requested,
+ Recipients = new List()
+ {
+ new Recipient("end-user", new List() { new SmsAddressPoint("+4799999999") })
+ }
+ };
+
+ Recipient expectedRecipient = new("end-user", new List() { new SmsAddressPoint("+4799999999") });
+
+ var serviceMock = new Mock();
+ serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.Is(d => d.Equals(requested)), It.Is(r => AssertUtils.AreEquivalent(expectedRecipient, r))));
+
+ var service = GetTestService(smsService: serviceMock.Object);
+
+ // Act
+ await service.ProcessOrder(order);
+
+ // Assert
+ serviceMock.VerifyAll();
+ }
+
+ [Fact]
+ public async Task ProcessOrder_ServiceCalledOnceForEachRecipient()
+ {
+ // Arrange
+ var order = new NotificationOrder()
+ {
+ Id = Guid.NewGuid(),
+ NotificationChannel = NotificationChannel.Sms,
+ Recipients = new List()
+ {
+ new(),
+ new()
+ }
+ };
+
+ var serviceMock = new Mock();
+ serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()));
+
+ var service = GetTestService(smsService: serviceMock.Object);
+
+ // Act
+ await service.ProcessOrder(order);
+
+ // Assert
+ serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2));
+ }
+
+ [Fact]
+ public async Task ProcessOrderRetry_ServiceCalledIfRecipientNotInDatabase()
+ {
+ // Arrange
+ var order = new NotificationOrder()
+ {
+ Id = Guid.NewGuid(),
+ NotificationChannel = NotificationChannel.Sms,
+ Recipients = new List()
+ {
+ new Recipient(),
+ new Recipient("end-user", new List() { new SmsAddressPoint("+4799999999") }),
+ new Recipient(new List() { new SmsAddressPoint("+4749999999") })
+ }
+ };
+
+ var serviceMock = new Mock();
+ serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()));
+
+ var smsRepoMock = new Mock();
+ smsRepoMock.Setup(e => e.GetRecipients(It.IsAny())).ReturnsAsync(new List() { new SmsRecipient() { RecipientId = "end-user", MobileNumber = "+4799999999" } });
+
+ var service = GetTestService(smsRepo: smsRepoMock.Object, smsService: serviceMock.Object);
+
+ // Act
+ await service.ProcessOrderRetry(order);
+
+ // Assert
+ smsRepoMock.Verify(e => e.GetRecipients(It.IsAny()), Times.Once);
+ serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2));
+ }
+
+ private static SmsOrderProcessingService GetTestService(
+ ISmsNotificationRepository? smsRepo = null,
+ ISmsNotificationService? smsService = null)
+ {
+ if (smsRepo == null)
+ {
+ var smsRepoMock = new Mock();
+ smsRepo = smsRepoMock.Object;
+ }
+
+ if (smsService == null)
+ {
+ var smsServiceMock = new Mock();
+ smsService = smsServiceMock.Object;
+ }
+
+ return new SmsOrderProcessingService(smsRepo, smsService);
+ }
+}