diff --git a/src/Altinn.Notifications.Core/Configuration/NotificationOrderConfig.cs b/src/Altinn.Notifications.Core/Configuration/NotificationOrderConfig.cs index 6ce8b285..93bdeeae 100644 --- a/src/Altinn.Notifications.Core/Configuration/NotificationOrderConfig.cs +++ b/src/Altinn.Notifications.Core/Configuration/NotificationOrderConfig.cs @@ -9,4 +9,9 @@ public class NotificationOrderConfig /// Default from address for email notifications /// public string DefaultEmailFromAddress { get; set; } = string.Empty; + + /// + /// Default sender number for sms notifications + /// + public string DefaultSmsSenderNumber { get; set; } = string.Empty; } diff --git a/src/Altinn.Notifications.Core/Enums/NotificationChannel.cs b/src/Altinn.Notifications.Core/Enums/NotificationChannel.cs index 7da282be..49e30aa8 100644 --- a/src/Altinn.Notifications.Core/Enums/NotificationChannel.cs +++ b/src/Altinn.Notifications.Core/Enums/NotificationChannel.cs @@ -8,5 +8,10 @@ public enum NotificationChannel /// /// The selected channel for the notification is email. /// - Email + Email, + + /// + /// The selected channel for the notification is SMS. + /// + Sms } diff --git a/src/Altinn.Notifications.Core/Enums/NotificationTemplateType.cs b/src/Altinn.Notifications.Core/Enums/NotificationTemplateType.cs index ae0de091..de2da775 100644 --- a/src/Altinn.Notifications.Core/Enums/NotificationTemplateType.cs +++ b/src/Altinn.Notifications.Core/Enums/NotificationTemplateType.cs @@ -6,6 +6,7 @@ /// public enum NotificationTemplateType { - Email + Email, + Sms } #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member diff --git a/src/Altinn.Notifications.Core/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.Notifications.Core/Extensions/ServiceCollectionExtensions.cs index 660c7c5e..208a8f3f 100644 --- a/src/Altinn.Notifications.Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.Notifications.Core/Extensions/ServiceCollectionExtensions.cs @@ -32,7 +32,7 @@ public static void AddCoreServices(this IServiceCollection services, IConfigurat .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/Altinn.Notifications.Core/Models/NotificationTemplate/INotificationTemplate.cs b/src/Altinn.Notifications.Core/Models/NotificationTemplate/INotificationTemplate.cs index c8f3ce71..5329fc08 100644 --- a/src/Altinn.Notifications.Core/Models/NotificationTemplate/INotificationTemplate.cs +++ b/src/Altinn.Notifications.Core/Models/NotificationTemplate/INotificationTemplate.cs @@ -8,6 +8,7 @@ namespace Altinn.Notifications.Core.Models.NotificationTemplate; /// Base class for a notification template /// [JsonDerivedType(typeof(EmailTemplate), "email")] +[JsonDerivedType(typeof(SmsTemplate), "sms")] [JsonPolymorphic(TypeDiscriminatorPropertyName = "$")] public interface INotificationTemplate { diff --git a/src/Altinn.Notifications.Core/Models/NotificationTemplate/SmsTemplate.cs b/src/Altinn.Notifications.Core/Models/NotificationTemplate/SmsTemplate.cs new file mode 100644 index 00000000..9d1cb2db --- /dev/null +++ b/src/Altinn.Notifications.Core/Models/NotificationTemplate/SmsTemplate.cs @@ -0,0 +1,39 @@ +using Altinn.Notifications.Core.Enums; + +namespace Altinn.Notifications.Core.Models.NotificationTemplate; + +/// +/// Template for an SMS notification +/// +public class SmsTemplate : INotificationTemplate +{ + /// + public NotificationTemplateType Type { get; internal set; } + + /// + /// Gets the number from which the SMS is created by the template + /// + public string SenderNumber { get; internal set; } = string.Empty; + + /// + /// Gets the body of SMSs created by the template + /// + public string Body { get; internal set; } = string.Empty; + + /// + /// Initializes a new instance of the class. + /// + public SmsTemplate(string? senderNumber, string body) + { + SenderNumber = senderNumber ?? string.Empty; + Body = body; + Type = NotificationTemplateType.Sms; + } + + /// + /// Initializes a new instance of the class. + /// + internal SmsTemplate() + { + } +} diff --git a/src/Altinn.Notifications.Core/Services/Interfaces/IEmailNotificationOrderService.cs b/src/Altinn.Notifications.Core/Services/Interfaces/IOrderRequestService.cs similarity index 59% rename from src/Altinn.Notifications.Core/Services/Interfaces/IEmailNotificationOrderService.cs rename to src/Altinn.Notifications.Core/Services/Interfaces/IOrderRequestService.cs index c817ba1f..b9edf362 100644 --- a/src/Altinn.Notifications.Core/Services/Interfaces/IEmailNotificationOrderService.cs +++ b/src/Altinn.Notifications.Core/Services/Interfaces/IOrderRequestService.cs @@ -4,14 +4,14 @@ namespace Altinn.Notifications.Core.Services.Interfaces; /// -/// Interface for the email notification order service +/// Interface for the notification order service /// -public interface IEmailNotificationOrderService +public interface IOrderRequestService { /// /// Registers a new order /// - /// The email notification order request + /// The notification order request /// The registered notification order - public Task<(NotificationOrder? Order, ServiceError? Error)> RegisterEmailNotificationOrder(NotificationOrderRequest orderRequest); + public Task<(NotificationOrder? Order, ServiceError? Error)> RegisterNotificationOrder(NotificationOrderRequest orderRequest); } diff --git a/src/Altinn.Notifications.Core/Services/EmailNotificationOrderService.cs b/src/Altinn.Notifications.Core/Services/OrderRequestService.cs similarity index 57% rename from src/Altinn.Notifications.Core/Services/EmailNotificationOrderService.cs rename to src/Altinn.Notifications.Core/Services/OrderRequestService.cs index 93515f37..a772e65e 100644 --- a/src/Altinn.Notifications.Core/Services/EmailNotificationOrderService.cs +++ b/src/Altinn.Notifications.Core/Services/OrderRequestService.cs @@ -10,33 +10,35 @@ namespace Altinn.Notifications.Core.Services; /// -/// Implementation of the . +/// Implementation of the . /// -public class EmailNotificationOrderService : IEmailNotificationOrderService +public class OrderRequestService : IOrderRequestService { private readonly IOrderRepository _repository; private readonly IGuidService _guid; private readonly IDateTimeService _dateTime; - private readonly string _defaultFromAddress; + private readonly string _defaultEmailFromAddress; + private readonly string _defaultSmsSender; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public EmailNotificationOrderService(IOrderRepository repository, IGuidService guid, IDateTimeService dateTime, IOptions config) + public OrderRequestService(IOrderRepository repository, IGuidService guid, IDateTimeService dateTime, IOptions config) { _repository = repository; _guid = guid; _dateTime = dateTime; - _defaultFromAddress = config.Value.DefaultEmailFromAddress; + _defaultEmailFromAddress = config.Value.DefaultEmailFromAddress; + _defaultSmsSender = config.Value.DefaultSmsSenderNumber; } /// - public async Task<(NotificationOrder? Order, ServiceError? Error)> RegisterEmailNotificationOrder(NotificationOrderRequest orderRequest) + public async Task<(NotificationOrder? Order, ServiceError? Error)> RegisterNotificationOrder(NotificationOrderRequest orderRequest) { Guid orderId = _guid.NewGuid(); DateTime created = _dateTime.UtcNow(); - var templates = SetFromAddressIfNotDefined(orderRequest.Templates); + var templates = SetSenderIfNotDefined(orderRequest.Templates); var order = new NotificationOrder( orderId, @@ -53,11 +55,16 @@ public EmailNotificationOrderService(IOrderRepository repository, IGuidService g return (savedOrder, null); } - private List SetFromAddressIfNotDefined(List templates) + private List SetSenderIfNotDefined(List templates) { foreach (var template in templates.OfType().Where(template => string.IsNullOrEmpty(template.FromAddress))) { - template.FromAddress = _defaultFromAddress; + template.FromAddress = _defaultEmailFromAddress; + } + + foreach (var template in templates.OfType().Where(template => string.IsNullOrEmpty(template.SenderNumber))) + { + template.SenderNumber = _defaultSmsSender; } return templates; diff --git a/src/Altinn.Notifications/Controllers/EmailNotificationOrdersController.cs b/src/Altinn.Notifications/Controllers/EmailNotificationOrdersController.cs index 7e20473e..ebc63eb6 100644 --- a/src/Altinn.Notifications/Controllers/EmailNotificationOrdersController.cs +++ b/src/Altinn.Notifications/Controllers/EmailNotificationOrdersController.cs @@ -29,15 +29,15 @@ namespace Altinn.Notifications.Controllers; public class EmailNotificationOrdersController : ControllerBase { private readonly IValidator _validator; - private readonly IEmailNotificationOrderService _orderService; + private readonly IOrderRequestService _orderRequestService; /// /// Initializes a new instance of the class. /// - public EmailNotificationOrdersController(IValidator validator, IEmailNotificationOrderService orderService) + public EmailNotificationOrdersController(IValidator validator, IOrderRequestService orderRequestService) { _validator = validator; - _orderService = orderService; + _orderRequestService = orderRequestService; } /// @@ -71,7 +71,7 @@ public async Task> Post(EmailNotificationOrderRequestEx } var orderRequest = emailNotificationOrderRequest.MapToOrderRequest(creator); - (NotificationOrder? registeredOrder, ServiceError? error) = await _orderService.RegisterEmailNotificationOrder(orderRequest); + (NotificationOrder? registeredOrder, ServiceError? error) = await _orderRequestService.RegisterNotificationOrder(orderRequest); if (error != null) { diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications/EmailNotificationsOrderController/EmailNotificationOrdersControllerTests.cs b/test/Altinn.Notifications.IntegrationTests/Notifications/EmailNotificationsOrderController/EmailNotificationOrdersControllerTests.cs index c7ec540e..cfe44357 100644 --- a/test/Altinn.Notifications.IntegrationTests/Notifications/EmailNotificationsOrderController/EmailNotificationOrdersControllerTests.cs +++ b/test/Altinn.Notifications.IntegrationTests/Notifications/EmailNotificationsOrderController/EmailNotificationOrdersControllerTests.cs @@ -172,8 +172,8 @@ public async Task Post_UserClaimsPrincipal_Forbidden() public async Task Post_ServiceReturnsError_ServerError() { // Arrange - Mock serviceMock = new(); - serviceMock.Setup(s => s.RegisterEmailNotificationOrder(It.IsAny())) + Mock serviceMock = new(); + serviceMock.Setup(s => s.RegisterNotificationOrder(It.IsAny())) .ReturnsAsync((null, new ServiceError(500))); HttpClient client = GetTestClient(orderService: serviceMock.Object); @@ -196,8 +196,8 @@ public async Task Post_ServiceReturnsError_ServerError() public async Task Post_ValidScope_ServiceReturnsOrder_Accepted() { // Arrange - Mock serviceMock = new(); - serviceMock.Setup(s => s.RegisterEmailNotificationOrder(It.IsAny())) + Mock serviceMock = new(); + serviceMock.Setup(s => s.RegisterNotificationOrder(It.IsAny())) .Callback(orderRequest => { var emailTemplate = orderRequest.Templates @@ -235,8 +235,8 @@ public async Task Post_ValidScope_ServiceReturnsOrder_Accepted() public async Task Post_ValidAccessToken_ServiceReturnsOrder_Accepted() { // Arrange - Mock serviceMock = new(); - serviceMock.Setup(s => s.RegisterEmailNotificationOrder(It.IsAny())) + Mock serviceMock = new(); + serviceMock.Setup(s => s.RegisterNotificationOrder(It.IsAny())) .Callback(orderRequest => { var emailTemplate = orderRequest.Templates @@ -274,9 +274,9 @@ public async Task Post_ValidAccessToken_ServiceReturnsOrder_Accepted() public async Task Post_OrderWithoutFromAddress_StringEmptyUsedAsServiceInput_Accepted() { // Arrange - Mock serviceMock = new(); + Mock serviceMock = new(); - serviceMock.Setup(s => s.RegisterEmailNotificationOrder(It.IsAny())) + serviceMock.Setup(s => s.RegisterNotificationOrder(It.IsAny())) .Callback(orderRequest => { var emailTemplate = orderRequest.Templates @@ -321,7 +321,7 @@ public async Task Post_OrderWithoutFromAddress_StringEmptyUsedAsServiceInput_Acc serviceMock.VerifyAll(); } - private HttpClient GetTestClient(IValidator? validator = null, IEmailNotificationOrderService? orderService = null) + private HttpClient GetTestClient(IValidator? validator = null, IOrderRequestService? orderService = null) { if (validator == null) { @@ -333,7 +333,7 @@ private HttpClient GetTestClient(IValidator? v if (orderService == null) { - var orderServiceMock = new Mock(); + var orderServiceMock = new Mock(); orderService = orderServiceMock.Object; } diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationOrderServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/OrderRequestServiceTests.cs similarity index 51% rename from test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationOrderServiceTests.cs rename to test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/OrderRequestServiceTests.cs index 67d26f15..281e399d 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationOrderServiceTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/OrderRequestServiceTests.cs @@ -18,10 +18,10 @@ namespace Altinn.Notifications.Tests.Notifications.Core.TestingServices; -public class EmailNotificationOrderServiceTests +public class OrderRequestServiceTests { [Fact] - public async Task RegisterEmailNotificationOrder_ExpectedInputToRepository() + public async Task RegisterNotificationOrder_ForEmail_ExpectedInputToRepository() { // Arrange DateTime sendTime = DateTime.UtcNow; @@ -59,7 +59,7 @@ public async Task RegisterEmailNotificationOrder_ExpectedInputToRepository() var service = GetTestService(repoMock.Object, id, createdTime); // Act - (NotificationOrder? actual, ServiceError? _) = await service.RegisterEmailNotificationOrder(input); + (NotificationOrder? actual, ServiceError? _) = await service.RegisterNotificationOrder(input); // Assert Assert.Equivalent(expected, actual, true); @@ -67,7 +67,7 @@ public async Task RegisterEmailNotificationOrder_ExpectedInputToRepository() } [Fact] - public async Task RegisterEmailNotificationOrder_NoFromAddressDefaultInserted() + public async Task RegisterNotificationOrder_ForEmail_NoFromAddressDefaultInserted() { // Arrange DateTime sendTime = DateTime.UtcNow; @@ -105,14 +105,106 @@ public async Task RegisterEmailNotificationOrder_NoFromAddressDefaultInserted() var service = GetTestService(repoMock.Object, id, createdTime); // Act - (NotificationOrder? actual, ServiceError? _) = await service.RegisterEmailNotificationOrder(input); + (NotificationOrder? actual, ServiceError? _) = await service.RegisterNotificationOrder(input); // Assert Assert.Equivalent(expected, actual, true); repoMock.VerifyAll(); } - public static EmailNotificationOrderService GetTestService(IOrderRepository? repository = null, Guid? guid = null, DateTime? dateTime = null) + [Fact] + public async Task RegisterNotificationOrder_ForSms_ExpectedInputToRepository() + { + // Arrange + DateTime sendTime = DateTime.UtcNow; + DateTime createdTime = DateTime.UtcNow.AddMinutes(-2); + Guid id = Guid.NewGuid(); + + NotificationOrder expected = new() + { + Id = id, + Created = createdTime, + Creator = new("ttd"), + NotificationChannel = NotificationChannel.Sms, + RequestedSendTime = sendTime, + Recipients = { }, + SendersReference = "senders-reference", + Templates = { new SmsTemplate { Body = "sms-body", SenderNumber = "Skatteetaten" } } + }; + + NotificationOrderRequest input = new() + { + Creator = new Creator("ttd"), + + NotificationChannel = NotificationChannel.Sms, + Recipients = { }, + SendersReference = "senders-reference", + RequestedSendTime = sendTime, + Templates = { new SmsTemplate { Body = "sms-body", SenderNumber = "Skatteetaten" } } + }; + + Mock repoMock = new(); + repoMock + .Setup(r => r.Create(It.IsAny())) + .ReturnsAsync((NotificationOrder order) => order); + + var service = GetTestService(repoMock.Object, id, createdTime); + + // Act + (NotificationOrder? actual, ServiceError? _) = await service.RegisterNotificationOrder(input); + + // Assert + Assert.Equivalent(expected, actual, true); + repoMock.VerifyAll(); + } + + [Fact] + public async Task RegisterNotificationOrder_ForSms_NoSenderNumberDefaultInserted() + { + // Arrange + DateTime sendTime = DateTime.UtcNow; + DateTime createdTime = DateTime.UtcNow.AddMinutes(-2); + Guid id = Guid.NewGuid(); + + NotificationOrder expected = new() + { + Id = id, + Created = createdTime, + Creator = new("ttd"), + NotificationChannel = NotificationChannel.Sms, + RequestedSendTime = sendTime, + Recipients = { }, + SendersReference = "senders-reference", + Templates = { new SmsTemplate { Body = "sms-body", SenderNumber = "TestDefaultSmsSenderNumberNumber" } } + }; + + NotificationOrderRequest input = new() + { + Creator = new Creator("ttd"), + + NotificationChannel = NotificationChannel.Sms, + Recipients = { }, + SendersReference = "senders-reference", + RequestedSendTime = sendTime, + Templates = { new SmsTemplate { Body = "sms-body" } } + }; + + Mock repoMock = new(); + repoMock + .Setup(r => r.Create(It.IsAny())) + .ReturnsAsync((NotificationOrder order) => order); + + var service = GetTestService(repoMock.Object, id, createdTime); + + // Act + (NotificationOrder? actual, ServiceError? _) = await service.RegisterNotificationOrder(input); + + // Assert + Assert.Equivalent(expected, actual, true); + repoMock.VerifyAll(); + } + + public static OrderRequestService GetTestService(IOrderRepository? repository = null, Guid? guid = null, DateTime? dateTime = null) { if (repository == null) { @@ -130,8 +222,9 @@ public static EmailNotificationOrderService GetTestService(IOrderRepository? rep var config = Options.Create(new() { - DefaultEmailFromAddress = "noreply@altinn.no" + DefaultEmailFromAddress = "noreply@altinn.no", + DefaultSmsSenderNumber = "TestDefaultSmsSenderNumberNumber" }); - return new EmailNotificationOrderService(repository, guidMock.Object, dateTimeMock.Object, config); + return new OrderRequestService(repository, guidMock.Object, dateTimeMock.Object, config); } }