diff --git a/src/Altinn.Notifications/Controllers/SmsNotificationsController.cs b/src/Altinn.Notifications/Controllers/SmsNotificationsController.cs new file mode 100644 index 00000000..716b40e0 --- /dev/null +++ b/src/Altinn.Notifications/Controllers/SmsNotificationsController.cs @@ -0,0 +1,61 @@ +using Altinn.Notifications.Configuration; +using Altinn.Notifications.Core.Models.Notification; +using Altinn.Notifications.Core.Services.Interfaces; +using Altinn.Notifications.Core.Shared; +using Altinn.Notifications.Extensions; +using Altinn.Notifications.Mappers; + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +using Swashbuckle.AspNetCore.Annotations; + +namespace Altinn.Notifications.Controllers +{ + /// + /// Controller for all operations related to sms notifications + /// + [Authorize(Policy = AuthorizationConstants.POLICY_CREATE_SCOPE_OR_PLATFORM_ACCESS)] + [Route("notifications/api/v1/orders/{id}/notifications/sms")] + [ApiController] + [SwaggerResponse(401, "Caller is unauthorized")] + [SwaggerResponse(403, "Caller is not authorized to access the requested resource")] + public class SmsNotificationsController : ControllerBase + { + private readonly ISmsNotificationSummaryService _summaryService; + + /// + /// Initializes a new instance of the class. + /// + /// The notifications summary service + public SmsNotificationsController(ISmsNotificationSummaryService summaryService) + { + _summaryService = summaryService; + } + + /// + /// Endpoint for retrieving a summary of all sms notifications related to an order + /// + /// The order id + /// Sumarized order details and a list containing all sms notifications and their send status + [HttpGet] + [Produces("application/json")] + [SwaggerResponse(200, "The notification order was accepted", typeof(SmsNotificationSummaryExt))] + [SwaggerResponse(404, "No notification order mathching the id was found")] + public async Task> Get([FromRoute] Guid id) + { + string? expectedCreator = HttpContext.GetOrg(); + + if (expectedCreator == null) + { + return Forbid(); + } + + Result result = await _summaryService.GetSummary(id, expectedCreator); + + return result.Match( + summary => Ok(summary.MapToSmsNotificationSummaryExt()), + error => StatusCode(error.ErrorCode, error.ErrorMessage)); + } + } +} diff --git a/src/Altinn.Notifications/Mappers/NotificationSummaryMapper.cs b/src/Altinn.Notifications/Mappers/EmailNotificationSummaryMapper.cs similarity index 97% rename from src/Altinn.Notifications/Mappers/NotificationSummaryMapper.cs rename to src/Altinn.Notifications/Mappers/EmailNotificationSummaryMapper.cs index 4f6a11a7..c93f2f2a 100644 --- a/src/Altinn.Notifications/Mappers/NotificationSummaryMapper.cs +++ b/src/Altinn.Notifications/Mappers/EmailNotificationSummaryMapper.cs @@ -5,7 +5,7 @@ namespace Altinn.Notifications.Mappers /// /// Mapper for /// - public static class NotificationSummaryMapper + public static class EmailNotificationSummaryMapper { /// /// Maps a to a diff --git a/src/Altinn.Notifications/Mappers/SmsNotificationSummaryMapper.cs b/src/Altinn.Notifications/Mappers/SmsNotificationSummaryMapper.cs new file mode 100644 index 00000000..9104fb05 --- /dev/null +++ b/src/Altinn.Notifications/Mappers/SmsNotificationSummaryMapper.cs @@ -0,0 +1,57 @@ +using Altinn.Notifications.Core.Models.Notification; + +namespace Altinn.Notifications.Mappers +{ + /// + /// Mapper for + /// + public static class SmsNotificationSummaryMapper + { + /// + /// Maps a to a + /// + public static SmsNotificationSummaryExt MapToSmsNotificationSummaryExt(this SmsNotificationSummary summary) + { + return new SmsNotificationSummaryExt() + { + OrderId = summary.OrderId, + SendersReference = summary.SendersReference, + Generated = summary.Generated, + Succeeded = summary.Succeeded, + Notifications = summary.Notifications.MapToSmsNotificationWithResultExt(), + }; + } + + /// + /// Maps a list of to a list of + /// + public static List MapToSmsNotificationWithResultExt(this List notifications) + { + List result = notifications.Select(n => n.MapToSmsNotificationWithResultExt()).ToList(); + + return result; + } + + /// + /// Maps a to a + /// + public static SmsNotificationWithResultExt MapToSmsNotificationWithResultExt(this SmsNotificationWithResult notification) + { + return new SmsNotificationWithResultExt() + { + Id = notification.Id, + Succeeded = notification.Succeeded, + Recipient = new() + { + MobileNumber = notification.Recipient.MobileNumber + }, + SendStatus = new() + { + Status = notification.ResultStatus.Result.ToString(), + StatusDescription = notification.ResultStatus.ResultDescription, + LastUpdate = notification.ResultStatus.ResultTime + } + }; + } + } +} diff --git a/src/Altinn.Notifications/Models/SmsNotificationSummaryExt.cs b/src/Altinn.Notifications/Models/SmsNotificationSummaryExt.cs new file mode 100644 index 00000000..c7383dfe --- /dev/null +++ b/src/Altinn.Notifications/Models/SmsNotificationSummaryExt.cs @@ -0,0 +1,43 @@ +using System.Text.Json.Serialization; + +namespace Altinn.Notifications.Core.Models.Notification +{ + /// + /// A class representing an sms notification summary + /// + /// + /// External representaion to be used in the API. + /// + public class SmsNotificationSummaryExt + { + /// + /// The order id + /// + [JsonPropertyName("orderId")] + public Guid OrderId { get; set; } + + /// + /// The senders reference + /// + [JsonPropertyName("sendersReference")] + public string? SendersReference { get; set; } + + /// + /// The number of generated sms notifications + /// + [JsonPropertyName("generated")] + public int Generated { get; set; } + + /// + /// The number of sms notifications that were sent successfully + /// + [JsonPropertyName("succeeded")] + public int Succeeded { get; set; } + + /// + /// A list of notifications with send result + /// + [JsonPropertyName("notifications")] + public List Notifications { get; set; } = []; + } +} diff --git a/src/Altinn.Notifications/Models/SmsNotificationWithResultExt.cs b/src/Altinn.Notifications/Models/SmsNotificationWithResultExt.cs new file mode 100644 index 00000000..822fc037 --- /dev/null +++ b/src/Altinn.Notifications/Models/SmsNotificationWithResultExt.cs @@ -0,0 +1,39 @@ +using System.Text.Json.Serialization; + +using Altinn.Notifications.Models; + +namespace Altinn.Notifications.Core.Models.Notification +{ + /// + /// A class representing an sms notification with result + /// + /// + /// External representaion to be used in the API. + /// + public class SmsNotificationWithResultExt + { + /// + /// The notification id + /// + [JsonPropertyName("id")] + public Guid Id { get; set; } + + /// + /// Boolean indicating if the sending of the notification was successful + /// + [JsonPropertyName("succeeded")] + public bool Succeeded { get; set; } + + /// + /// The recipient of the notification + /// + [JsonPropertyName("recipient")] + public RecipientExt Recipient { get; set; } = new(); + + /// + /// The result status of the notification + /// + [JsonPropertyName("sendStatus")] + public StatusExt SendStatus { get; set; } = new(); + } +} diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications/SmsNotificationsController/GetTests.cs b/test/Altinn.Notifications.IntegrationTests/Notifications/SmsNotificationsController/GetTests.cs new file mode 100644 index 00000000..1b842d82 --- /dev/null +++ b/test/Altinn.Notifications.IntegrationTests/Notifications/SmsNotificationsController/GetTests.cs @@ -0,0 +1,133 @@ +using System.Net; +using System.Net.Http.Headers; +using System.Text.Json; + +using Altinn.Common.AccessToken.Services; +using Altinn.Notifications.Core.Models.Notification; +using Altinn.Notifications.Core.Models.Orders; +using Altinn.Notifications.IntegrationTests.Utils; +using Altinn.Notifications.Tests.Notifications.Mocks.Authentication; +using Altinn.Notifications.Tests.Notifications.Utils; + +using AltinnCore.Authentication.JwtCookie; + +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Logging; + +using Xunit; + +namespace Altinn.Notifications.IntegrationTests.Notifications.SmsNotificationsController +{ + public class GetTests : IClassFixture>, IAsyncLifetime + { + private readonly string _basePath; + private readonly IntegrationTestWebApplicationFactory _factory; + private readonly List _orderIdsToDelete; + + public GetTests(IntegrationTestWebApplicationFactory factory) + { + _basePath = $"/notifications/api/v1/orders"; + _factory = factory; + _orderIdsToDelete = new List(); + } + + public async Task InitializeAsync() + { + await Task.CompletedTask; + } + + async Task IAsyncLifetime.DisposeAsync() + { + if (_orderIdsToDelete.Count != 0) + { + string deleteSql = $@"DELETE from notifications.orders o where o.alternateid in ('{string.Join("','", _orderIdsToDelete)}')"; + await PostgreUtil.RunSql(deleteSql); + } + } + + [Fact] + public async Task Get_NonExistingOrder_NotFound() + { + // Arrange + string uri = $"{_basePath}/{Guid.NewGuid()}/notifications/sms"; + + HttpClient client = GetTestClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:serviceowner/notifications.create")); + + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, uri); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task Get_OrderIdForAnotherCreator_NotFound() + { + // Arrange + NotificationOrder order = await PostgreUtil.PopulateDBWithSmsOrder(); + _orderIdsToDelete.Add(order.Id); + + string uri = $"{_basePath}/{order.Id}/notifications/sms"; + + HttpClient client = GetTestClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("nav", scope: "altinn:serviceowner/notifications.create")); + + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, uri); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task Get_ValidOrderId_Ok() + { + // Arrange + (NotificationOrder order, SmsNotification notification) = await PostgreUtil.PopulateDBWithOrderAndSmsNotification(); + string uri = $"{_basePath}/{order.Id}/notifications/sms"; + _orderIdsToDelete.Add(order.Id); + + HttpClient client = GetTestClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:serviceowner/notifications.create")); + + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, uri); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + string responseString = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + SmsNotificationSummaryExt? summary = JsonSerializer.Deserialize(responseString); + Assert.True(summary?.Notifications.Count > 0); + Assert.Equal(order.Id, summary?.OrderId); + Assert.Equal(notification.Id, summary?.Notifications[0].Id); + Assert.Equal(1, summary?.Generated); + Assert.Equal(0, summary?.Succeeded); + } + + private HttpClient GetTestClient() + { + HttpClient client = _factory.WithWebHostBuilder(builder => + { + IdentityModelEventSource.ShowPII = true; + + builder.ConfigureTestServices(services => + { + // Set up mock authentication and authorization + services.AddSingleton, JwtCookiePostConfigureOptionsStub>(); + services.AddSingleton(); + }); + }).CreateClient(); + + return client; + } + } +} diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications/SmsNotificationsController/SmsNotificationsControllerTests.cs b/test/Altinn.Notifications.IntegrationTests/Notifications/SmsNotificationsController/SmsNotificationsControllerTests.cs new file mode 100644 index 00000000..c9c76482 --- /dev/null +++ b/test/Altinn.Notifications.IntegrationTests/Notifications/SmsNotificationsController/SmsNotificationsControllerTests.cs @@ -0,0 +1,230 @@ +using System.Net; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; + +using Altinn.Common.AccessToken.Services; +using Altinn.Notifications.Core.Models.Notification; +using Altinn.Notifications.Core.Services.Interfaces; +using Altinn.Notifications.Core.Shared; +using Altinn.Notifications.Tests.Notifications.Mocks.Authentication; +using Altinn.Notifications.Tests.Notifications.Utils; + +using AltinnCore.Authentication.JwtCookie; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Logging; + +using Moq; + +using Xunit; + +namespace Altinn.Notifications.IntegrationTests.Notifications.SmsNotificationsController; + +public class SmsNotificationsControllerTests : IClassFixture> +{ + private readonly string _basePath; + private readonly string _invalidGuidBase; + private readonly IntegrationTestWebApplicationFactory _factory; + + private readonly JsonSerializerOptions _options; + + public SmsNotificationsControllerTests(IntegrationTestWebApplicationFactory factory) + { + _basePath = $"/notifications/api/v1/orders/{Guid.NewGuid()}/notifications/sms"; + _invalidGuidBase = "/notifications/api/v1/orders/1337;1=1/notifications/sms"; + _factory = factory; + _options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + } + + [Fact] + public async Task Get_MissingBearerToken_Unauthorized() + { + // Arrange + HttpClient client = GetTestClient(); + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, _basePath); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + } + + [Fact] + public async Task Get_InvalidScopeInToken_Forbidden() + { + // Arrange + HttpClient client = GetTestClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:dummmy.scope")); + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, _basePath); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + } + + [Fact] + public async Task Get_EndUserTokenWithCalidScope_Forbidden() + { + // Arrange + HttpClient client = GetTestClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetUserToken(12345, scope: "altinn:serviceowner/notifications.create")); + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, _basePath); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + } + + [Fact] + public async Task Get_InvalidGuid_BadRequest() + { + HttpClient client = GetTestClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:serviceowner/notifications.create")); + + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, _invalidGuidBase) + { + Content = new StringContent(string.Empty, Encoding.UTF8, "application/json") + }; + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + string content = await response.Content.ReadAsStringAsync(); + ProblemDetails? actual = JsonSerializer.Deserialize(content, _options); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + Assert.Equal("One or more validation errors occurred.", actual?.Title); + } + + [Fact] + public async Task Get_ServiceReturnsError_ServerError() + { + // Arrange + Mock serviceMock = new(); + serviceMock.Setup(s => s.GetSummary(It.IsAny(), It.IsAny())) + .ReturnsAsync(new ServiceError(500)); + + HttpClient client = GetTestClient(summaryService: serviceMock.Object); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:serviceowner/notifications.create")); + + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, _basePath); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + serviceMock.VerifyAll(); + } + + [Fact] + public async Task Get_ValidScope_ServiceReturnsNotifications_Ok() + { + // Arrange + Guid id = Guid.NewGuid(); + SmsNotificationSummary output = new(id) + { + SendersReference = "senders-ref", + Generated = 1, + Succeeded = 1, + Notifications = new List() + }; + + Mock serviceMock = new(); + serviceMock.Setup(s => s.GetSummary(It.IsAny(), It.Is(s => s.Equals("ttd")))) + .ReturnsAsync(output); + + HttpClient client = GetTestClient(summaryService: serviceMock.Object); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:serviceowner/notifications.create")); + + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, _basePath); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + string respoonseString = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + SmsNotificationSummaryExt? summaryExt = JsonSerializer.Deserialize(respoonseString); + Assert.NotNull(summaryExt); + Assert.Equal(id, summaryExt.OrderId); + + serviceMock.VerifyAll(); + } + + [Fact] + public async Task Get_ValidAccessToken_ServiceReturnsOrder_Accepted() + { + // Arrange + Guid id = Guid.NewGuid(); + SmsNotificationSummary output = new(id) + { + SendersReference = "senders-ref", + Generated = 1, + Succeeded = 1, + Notifications = new List() + }; + + Mock serviceMock = new(); + serviceMock.Setup(s => s.GetSummary(It.IsAny(), It.Is(s => s.Equals("ttd")))) + .ReturnsAsync(output); + + HttpClient client = GetTestClient(summaryService: serviceMock.Object); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:serviceowner/notifications.create")); + + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, _basePath); + httpRequestMessage.Headers.Add("PlatformAccessToken", PrincipalUtil.GetAccessToken("ttd", "apps-test")); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + string respoonseString = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + SmsNotificationSummaryExt? summaryExt = JsonSerializer.Deserialize(respoonseString); + Assert.NotNull(summaryExt); + Assert.Equal(id, summaryExt.OrderId); + + serviceMock.VerifyAll(); + } + + private HttpClient GetTestClient(ISmsNotificationSummaryService? summaryService = null) + { + if (summaryService == null) + { + var summaryServiceMock = new Mock(); + summaryService = summaryServiceMock.Object; + } + + HttpClient client = _factory.WithWebHostBuilder(builder => + { + IdentityModelEventSource.ShowPII = true; + + builder.ConfigureTestServices(services => + { + services.AddSingleton(summaryService); + + // Set up mock authentication and authorization + services.AddSingleton, JwtCookiePostConfigureOptionsStub>(); + services.AddSingleton(); + }); + }).CreateClient(); + + return client; + } +} diff --git a/test/Altinn.Notifications.Tests/Notifications/TestingMappers/NotificationSummaryMapperTests.cs b/test/Altinn.Notifications.Tests/Notifications/TestingMappers/EmailNotificationSummaryMapperTests.cs similarity index 98% rename from test/Altinn.Notifications.Tests/Notifications/TestingMappers/NotificationSummaryMapperTests.cs rename to test/Altinn.Notifications.Tests/Notifications/TestingMappers/EmailNotificationSummaryMapperTests.cs index c58f5332..6ef64a39 100644 --- a/test/Altinn.Notifications.Tests/Notifications/TestingMappers/NotificationSummaryMapperTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications/TestingMappers/EmailNotificationSummaryMapperTests.cs @@ -10,7 +10,7 @@ namespace Altinn.Notifications.Tests.Notifications.TestingMappers { - public class NotificationSummaryMapperTests + public class EmailNotificationSummaryMapperTests { [Fact] public void MapToEmailNotificationWithResultExt_EmptyList_AreEquivalent() diff --git a/test/Altinn.Notifications.Tests/Notifications/TestingMappers/SmsNotificationSummaryMapperTests.cs b/test/Altinn.Notifications.Tests/Notifications/TestingMappers/SmsNotificationSummaryMapperTests.cs new file mode 100644 index 00000000..0d248d16 --- /dev/null +++ b/test/Altinn.Notifications.Tests/Notifications/TestingMappers/SmsNotificationSummaryMapperTests.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; + +using Altinn.Notifications.Core.Enums; +using Altinn.Notifications.Core.Models.Notification; +using Altinn.Notifications.Core.Models.Recipients; +using Altinn.Notifications.Mappers; + +using Xunit; + +namespace Altinn.Notifications.Tests.Notifications.TestingMappers +{ + public class SmsNotificationSummaryMapperTests + { + [Fact] + public void MapToSmsNotificationWithResultExt_EmptyList_AreEquivalent() + { + // Arrange + List input = new(); + + // Act + var actual = input.MapToSmsNotificationWithResultExt(); + + // Assert + Assert.Empty(actual); + } + + [Fact] + public void MapToSmsNotificationWithResultExt_NotificationWithFailedResult_AreEquivalent() + { + // Arrange + Guid id = Guid.NewGuid(); + DateTime timestamp = DateTime.UtcNow; + SmsNotificationWithResultExt expected = new() + { + Id = id, + Succeeded = false, + Recipient = new() + { + MobileNumber = "+4799999999" + }, + SendStatus = new() + { + LastUpdate = timestamp, + Status = "Failed_RecipientNotIdentified", + StatusDescription = "Failed to send. Could not identify recipient." + } + }; + + SmsNotificationWithResult input = new( + id, + false, + new SmsRecipient() + { + RecipientId = "12345678910", + MobileNumber = "+4799999999" + }, + new NotificationResult( + SmsNotificationResultType.Failed_RecipientNotIdentified, + timestamp)); + + input.ResultStatus.SetResultDescription("Failed to send. Could not identify recipient."); + + // Act + var actual = input.MapToSmsNotificationWithResultExt(); + + // Assert + Assert.Equivalent(expected, actual, false); + } + + [Fact] + public void MapToSmsNotificationWithResultExt_NotificationWithSuccessResult_AreEquivalent() + { + // Arrange + Guid id = Guid.NewGuid(); + DateTime timestamp = DateTime.UtcNow; + SmsNotificationWithResultExt expected = new() + { + Id = id, + Succeeded = true, + Recipient = new() + { + MobileNumber = "+4799999999" + }, + SendStatus = new() + { + LastUpdate = timestamp, + Status = "Accepted", + StatusDescription = "This is the description" + } + }; + + SmsNotificationWithResult input = new( + id, + true, + new SmsRecipient() + { + RecipientId = "12345678910", + MobileNumber = "+4799999999" + }, + new NotificationResult( + SmsNotificationResultType.Accepted, + timestamp)); + + input.ResultStatus.SetResultDescription("This is the description"); + + // Act + var actual = input.MapToSmsNotificationWithResultExt(); + + // Assert + Assert.Equivalent(expected, actual, false); + } + } +}