From eebd4240517816aeb0b156f0716e88b1e5f33267 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Mon, 11 Mar 2024 14:24:39 +0100 Subject: [PATCH 01/22] Added profile client for retrieving contact points --- .../Integrations/IProfileClient.cs | 17 +++++++ .../Models/ContactPoints/UserContactPoints.cs | 32 ++++++++++++ .../Altinn.Notifications.Integrations.csproj | 1 + .../Configuration/AltinnServiceSettings.cs | 13 +++++ .../Extensions/ServiceCollectionExtensions.cs | 17 +++++++ .../Profile/ProfileClient.cs | 50 +++++++++++++++++++ .../Profile/UserContactPointLookup.cs | 13 +++++ src/Altinn.Notifications/Program.cs | 1 + src/Altinn.Notifications/appsettings.json | 8 ++- 9 files changed, 147 insertions(+), 5 deletions(-) create mode 100644 src/Altinn.Notifications.Core/Integrations/IProfileClient.cs create mode 100644 src/Altinn.Notifications.Core/Models/ContactPoints/UserContactPoints.cs create mode 100644 src/Altinn.Notifications.Integrations/Configuration/AltinnServiceSettings.cs create mode 100644 src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs create mode 100644 src/Altinn.Notifications.Integrations/Profile/UserContactPointLookup.cs diff --git a/src/Altinn.Notifications.Core/Integrations/IProfileClient.cs b/src/Altinn.Notifications.Core/Integrations/IProfileClient.cs new file mode 100644 index 00000000..08f2fb73 --- /dev/null +++ b/src/Altinn.Notifications.Core/Integrations/IProfileClient.cs @@ -0,0 +1,17 @@ +using Altinn.Notifications.Core.Models.ContactPoints; +using Altinn.Notifications.Core.Shared; + +namespace Altinn.Notifications.Core.Integrations +{ + /// + /// Interface describing a client for the profile service + /// + public interface IProfileClient + { + /// + /// Retrieves contact points for a user + /// + /// + public Task> GetUserContactPoints(List nationalIdentityNumbers); + } +} diff --git a/src/Altinn.Notifications.Core/Models/ContactPoints/UserContactPoints.cs b/src/Altinn.Notifications.Core/Models/ContactPoints/UserContactPoints.cs new file mode 100644 index 00000000..0ed6923b --- /dev/null +++ b/src/Altinn.Notifications.Core/Models/ContactPoints/UserContactPoints.cs @@ -0,0 +1,32 @@ +namespace Altinn.Notifications.Core.Models.ContactPoints; + +/// +/// Class describing the availability of contact points for a user +/// +public class UserContactPoints +{ + /// + /// Gets or sets the ID of the user + /// + public int UserId { get; set; } + + /// + /// Gets or sets the national identityt number of the user + /// + public string NationalIdentityNumber { get; set; } = string.Empty; + + /// + /// Gets or sets a boolean indicating whether the user has reserved themselves from electronic communication + /// + public bool IsReserved { get; set; } + + /// + /// Gets or sets the mobile number + /// + public string MobileNumber { get; set; } = string.Empty; + + /// + /// Gets or sets the email address + /// + public string Email { get; set; } = string.Empty; +} diff --git a/src/Altinn.Notifications.Integrations/Altinn.Notifications.Integrations.csproj b/src/Altinn.Notifications.Integrations/Altinn.Notifications.Integrations.csproj index 0654c86b..e155a8b5 100644 --- a/src/Altinn.Notifications.Integrations/Altinn.Notifications.Integrations.csproj +++ b/src/Altinn.Notifications.Integrations/Altinn.Notifications.Integrations.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Altinn.Notifications.Integrations/Configuration/AltinnServiceSettings.cs b/src/Altinn.Notifications.Integrations/Configuration/AltinnServiceSettings.cs new file mode 100644 index 00000000..7a4a45eb --- /dev/null +++ b/src/Altinn.Notifications.Integrations/Configuration/AltinnServiceSettings.cs @@ -0,0 +1,13 @@ +namespace Altinn.Notifications.Integrations.Configuration +{ + /// + /// Configuration object used to hold settings for all Altinn integrations. + /// + public class AltinnServiceSettings + { + /// + /// Gets or sets the url for the API profile endpoint + /// + public string ApiProfileEndpoint { get; set; } = string.Empty; + } +} diff --git a/src/Altinn.Notifications.Integrations/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.Notifications.Integrations/Extensions/ServiceCollectionExtensions.cs index e0aa0ca8..4f8f1e7f 100644 --- a/src/Altinn.Notifications.Integrations/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.Notifications.Integrations/Extensions/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using Altinn.Notifications.Core.Integrations; +using Altinn.Notifications.Integrations.Clients; using Altinn.Notifications.Integrations.Configuration; using Altinn.Notifications.Integrations.Health; using Altinn.Notifications.Integrations.Kafka.Consumers; @@ -35,6 +36,22 @@ public static void AddKafkaServices(this IServiceCollection services, IConfigura .Configure(config.GetSection(nameof(KafkaSettings))); } + /// + /// Adds Altinn clients and configurations to DI container. + /// + /// service collection. + /// the configuration collection + public static void AddAltinnClients(this IServiceCollection services, IConfiguration config) + { + _ = config.GetSection(nameof(AltinnServiceSettings)) + .Get() + ?? throw new ArgumentNullException(nameof(config), "Required AltinnServiceSettings is missing from application configuration"); + + services + .Configure(config.GetSection(nameof(AltinnServiceSettings))) + .AddHttpClient(); + } + /// /// Adds kafka health checks /// diff --git a/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs b/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs new file mode 100644 index 00000000..9b6ff63e --- /dev/null +++ b/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs @@ -0,0 +1,50 @@ +using System.Text; +using System.Text.Json; + +using Altinn.Notifications.Core.Integrations; +using Altinn.Notifications.Core.Models.ContactPoints; +using Altinn.Notifications.Core.Shared; +using Altinn.Notifications.Integrations.Configuration; +using Altinn.Notifications.Integrations.Profile; + +namespace Altinn.Notifications.Integrations.Clients +{ + /// + /// Implementation of the + /// + public class ProfileClient : IProfileClient + { + private readonly HttpClient _client; + + /// + /// Initializes a new instance of the class. + /// + public ProfileClient(HttpClient client, AltinnServiceSettings settings) + { + _client = client; + _client.BaseAddress = new Uri(settings.ApiProfileEndpoint); + } + + /// + public async Task> GetUserContactPoints(List nationalIdentityNumbers) + { + var lookupObject = new UserContactPointLookup + { + NationalIdentityNumbers = nationalIdentityNumbers + }; + + HttpContent content = new StringContent(JsonSerializer.Serialize(lookupObject), Encoding.UTF8, "application/json"); + + var response = await _client.PostAsync("contactpoint/lookup", content); + + if (!response.IsSuccessStatusCode) + { + throw new PlatformHttpException(response, $"ProfileClient.GetUserContactPoints failed with status code {response.StatusCode}"); + } + + string responseContent = await response.Content.ReadAsStringAsync(); + List? contactPoints = JsonSerializer.Deserialize>(responseContent); + return contactPoints!; + } + } +} diff --git a/src/Altinn.Notifications.Integrations/Profile/UserContactPointLookup.cs b/src/Altinn.Notifications.Integrations/Profile/UserContactPointLookup.cs new file mode 100644 index 00000000..92acc954 --- /dev/null +++ b/src/Altinn.Notifications.Integrations/Profile/UserContactPointLookup.cs @@ -0,0 +1,13 @@ +namespace Altinn.Notifications.Integrations.Profile +{ + /// + /// A class respresenting a user contact point lookup object + /// + public class UserContactPointLookup + { + /// + /// A list of national identity numbers to look up contact points or contact point availability for + /// + public List NationalIdentityNumbers { get; set; } = []; + } +} diff --git a/src/Altinn.Notifications/Program.cs b/src/Altinn.Notifications/Program.cs index fba68ad5..dc3f9b6c 100644 --- a/src/Altinn.Notifications/Program.cs +++ b/src/Altinn.Notifications/Program.cs @@ -183,6 +183,7 @@ void ConfigureServices(IServiceCollection services, IConfiguration config) services.AddCoreServices(config); services.AddKafkaServices(config); + services.AddAltinnClients(config); services.AddPostgresRepositories(config); } diff --git a/src/Altinn.Notifications/appsettings.json b/src/Altinn.Notifications/appsettings.json index bb0334cc..89628eee 100644 --- a/src/Altinn.Notifications/appsettings.json +++ b/src/Altinn.Notifications/appsettings.json @@ -1,8 +1,6 @@ { - "PlatformSettings": { - "ProfileEndpointAddress": "url", - "ProfileSubscriptionKeyHeaderName": "Ocp-Apim-Subscription-Key", - "ProfileSubscriptionKey": "obtained at runtime" + "AltinnServiceSettings": { + "ApiProfileEndpoint": "http://localhost:5101/profil/api/v1/" }, "PostgreSQLSettings": { "MigrationScriptPath": "Migration", @@ -14,7 +12,7 @@ }, "NotificationOrderConfig": { "DefaultEmailFromAddress": "noreply@altinn.no", - "DefaultSmsSenderNumber": "Altinn" + "DefaultSmsSenderNumber": "Altinn" }, "KafkaSettings": { "BrokerAddress": "localhost:9092", From 9d7599dbc633493d48c7096a6c768a4991825926 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Mon, 11 Mar 2024 14:44:26 +0100 Subject: [PATCH 02/22] Added missing endpoint --- .../Integrations/IProfileClient.cs | 13 +++++-- .../UserContactPointAvailability.cs | 33 +++++++++++++++++ .../Shared/PlatformHttpException.cs | 37 +++++++++++++++++++ .../Profile/ProfileClient.cs | 24 +++++++++++- .../UserContactPointAvailabilityList.cs | 14 +++++++ .../Profile/UserContactPointsList.cs | 14 +++++++ 6 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 src/Altinn.Notifications.Core/Models/ContactPoints/UserContactPointAvailability.cs create mode 100644 src/Altinn.Notifications.Core/Shared/PlatformHttpException.cs create mode 100644 src/Altinn.Notifications.Integrations/Profile/UserContactPointAvailabilityList.cs create mode 100644 src/Altinn.Notifications.Integrations/Profile/UserContactPointsList.cs diff --git a/src/Altinn.Notifications.Core/Integrations/IProfileClient.cs b/src/Altinn.Notifications.Core/Integrations/IProfileClient.cs index 08f2fb73..fd6152c0 100644 --- a/src/Altinn.Notifications.Core/Integrations/IProfileClient.cs +++ b/src/Altinn.Notifications.Core/Integrations/IProfileClient.cs @@ -1,5 +1,4 @@ using Altinn.Notifications.Core.Models.ContactPoints; -using Altinn.Notifications.Core.Shared; namespace Altinn.Notifications.Core.Integrations { @@ -9,9 +8,17 @@ namespace Altinn.Notifications.Core.Integrations public interface IProfileClient { /// - /// Retrieves contact points for a user + /// Retrieves contact points for a list of users corresponding to a list of national identity numbers /// - /// + /// A list of national identity numbers to look up contact points for + /// A list of contact points for the provided national identity numbers public Task> GetUserContactPoints(List nationalIdentityNumbers); + + /// + /// Retrieves contact point availability for a list of users corresponding to a list of national identity numbers + /// + /// A list of national identity numbers to look up contact point availability for + /// A list of for the provided national identity numbers + public Task> GetUserContactPointAvailabilities(List nationalIdentityNumbers); } } diff --git a/src/Altinn.Notifications.Core/Models/ContactPoints/UserContactPointAvailability.cs b/src/Altinn.Notifications.Core/Models/ContactPoints/UserContactPointAvailability.cs new file mode 100644 index 00000000..cdb00927 --- /dev/null +++ b/src/Altinn.Notifications.Core/Models/ContactPoints/UserContactPointAvailability.cs @@ -0,0 +1,33 @@ +namespace Altinn.Notifications.Core.Models.ContactPoints +{ + /// + /// Class describing the contact points of a user + /// + public class UserContactPointAvailability + { + /// + /// Gets or sets the ID of the user + /// + public int UserId { get; set; } + + /// + /// Gets or sets the national identityt number of the user + /// + public string NationalIdentityNumber { get; set; } = string.Empty; + + /// + /// Gets or sets a boolean indicating whether the user has reserved themselves from electronic communication + /// + public bool IsReserved { get; set; } + + /// + /// Gets or sets a boolean indicating whether the user has registered a mobile number + /// + public bool MobileNumberRegistered { get; set; } + + /// + /// Gets or sets a boolean indicating whether the user has registered an email address + /// + public bool EmailRegistered { get; set; } + } +} diff --git a/src/Altinn.Notifications.Core/Shared/PlatformHttpException.cs b/src/Altinn.Notifications.Core/Shared/PlatformHttpException.cs new file mode 100644 index 00000000..3b159f43 --- /dev/null +++ b/src/Altinn.Notifications.Core/Shared/PlatformHttpException.cs @@ -0,0 +1,37 @@ +namespace Altinn.Notifications.Core.Shared +{ + /// + /// Exception class to hold exceptions when interacting with other Altinn platform REST services + /// + public class PlatformHttpException : Exception + { + /// + /// Responsible for holding an http request exception towards platform (storage). + /// + public HttpResponseMessage Response { get; } + + /// + /// Copy the response for further investigations + /// + /// the response + /// A description of the cause of the exception. + public PlatformHttpException(HttpResponseMessage response, string message) : base(message) + { + this.Response = response; + } + + /// + /// Create a new by reading the + /// content asynchronously. + /// + /// The to read. + /// A new . + public static async Task CreateAsync(HttpResponseMessage response) + { + string content = await response.Content.ReadAsStringAsync(); + string message = $"{(int)response.StatusCode} - {response.ReasonPhrase} - {content}"; + + return new PlatformHttpException(response, message); + } + } +} diff --git a/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs b/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs index 9b6ff63e..1a414703 100644 --- a/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs +++ b/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs @@ -43,7 +43,29 @@ public async Task> GetUserContactPoints(List nat } string responseContent = await response.Content.ReadAsStringAsync(); - List? contactPoints = JsonSerializer.Deserialize>(responseContent); + List? contactPoints = JsonSerializer.Deserialize(responseContent)!.ContactPointList; + return contactPoints!; + } + + /// + public async Task> GetUserContactPointAvailabilities(List nationalIdentityNumbers) + { + var lookupObject = new UserContactPointLookup + { + NationalIdentityNumbers = nationalIdentityNumbers + }; + + HttpContent content = new StringContent(JsonSerializer.Serialize(lookupObject), Encoding.UTF8, "application/json"); + + var response = await _client.PostAsync("contactpoint/availability", content); + + if (!response.IsSuccessStatusCode) + { + throw new PlatformHttpException(response, $"ProfileClient.GetUserContactPointAvailabilities failed with status code {response.StatusCode}"); + } + + string responseContent = await response.Content.ReadAsStringAsync(); + List? contactPoints = JsonSerializer.Deserialize(responseContent)!.AvailabilityList; return contactPoints!; } } diff --git a/src/Altinn.Notifications.Integrations/Profile/UserContactPointAvailabilityList.cs b/src/Altinn.Notifications.Integrations/Profile/UserContactPointAvailabilityList.cs new file mode 100644 index 00000000..eedbf7aa --- /dev/null +++ b/src/Altinn.Notifications.Integrations/Profile/UserContactPointAvailabilityList.cs @@ -0,0 +1,14 @@ +using Altinn.Notifications.Core.Models.ContactPoints; + +namespace Altinn.Notifications.Integrations.Profile; + +/// +/// A list representation of +/// +public class UserContactPointAvailabilityList +{ + /// + /// A list containing contact point availabiliy for users + /// + public List AvailabilityList { get; set; } = []; +} diff --git a/src/Altinn.Notifications.Integrations/Profile/UserContactPointsList.cs b/src/Altinn.Notifications.Integrations/Profile/UserContactPointsList.cs new file mode 100644 index 00000000..342bb804 --- /dev/null +++ b/src/Altinn.Notifications.Integrations/Profile/UserContactPointsList.cs @@ -0,0 +1,14 @@ +using Altinn.Notifications.Core.Models.ContactPoints; + +namespace Altinn.Notifications.Integrations.Profile; + +/// +/// A list representation of +/// +public class UserContactPointsList +{ + /// + /// A list containing contact points for users + /// + public List ContactPointList { get; set; } = []; +} From 4301d78e6536db0709b992e14f3504325459323c Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Mon, 11 Mar 2024 16:28:00 +0100 Subject: [PATCH 03/22] implemented tests - not run -kafka not working --- .../Integrations/IProfileClient.cs | 33 ++- .../UserContactPointAvailability.cs | 49 ++-- .../Shared/PlatformHttpException.cs | 59 +++-- .../Configuration/AltinnServiceSettings.cs | 17 +- .../Profile/ProfileClient.cs | 89 ++++--- .../Profile/UserContactPointLookup.cs | 17 +- .../DelegatingHandlerStub.cs | 24 ++ .../Profile/ProfileClientTests.cs | 228 ++++++++++++++++++ .../WebApplicationFactorySetup.cs | 61 +++++ .../appsettings.IntegrationTest.json | 3 + 10 files changed, 445 insertions(+), 135 deletions(-) create mode 100644 test/Altinn.Notifications.IntegrationTests/DelegatingHandlerStub.cs create mode 100644 test/Altinn.Notifications.IntegrationTests/Notifications.Integrations/Profile/ProfileClientTests.cs create mode 100644 test/Altinn.Notifications.IntegrationTests/WebApplicationFactorySetup.cs diff --git a/src/Altinn.Notifications.Core/Integrations/IProfileClient.cs b/src/Altinn.Notifications.Core/Integrations/IProfileClient.cs index fd6152c0..0ad923ac 100644 --- a/src/Altinn.Notifications.Core/Integrations/IProfileClient.cs +++ b/src/Altinn.Notifications.Core/Integrations/IProfileClient.cs @@ -1,24 +1,23 @@ using Altinn.Notifications.Core.Models.ContactPoints; -namespace Altinn.Notifications.Core.Integrations +namespace Altinn.Notifications.Core.Integrations; + +/// +/// Interface describing a client for the profile service +/// +public interface IProfileClient { /// - /// Interface describing a client for the profile service + /// Retrieves contact points for a list of users corresponding to a list of national identity numbers /// - public interface IProfileClient - { - /// - /// Retrieves contact points for a list of users corresponding to a list of national identity numbers - /// - /// A list of national identity numbers to look up contact points for - /// A list of contact points for the provided national identity numbers - public Task> GetUserContactPoints(List nationalIdentityNumbers); + /// A list of national identity numbers to look up contact points for + /// A list of contact points for the provided national identity numbers + public Task> GetUserContactPoints(List nationalIdentityNumbers); - /// - /// Retrieves contact point availability for a list of users corresponding to a list of national identity numbers - /// - /// A list of national identity numbers to look up contact point availability for - /// A list of for the provided national identity numbers - public Task> GetUserContactPointAvailabilities(List nationalIdentityNumbers); - } + /// + /// Retrieves contact point availability for a list of users corresponding to a list of national identity numbers + /// + /// A list of national identity numbers to look up contact point availability for + /// A list of for the provided national identity numbers + public Task> GetUserContactPointAvailabilities(List nationalIdentityNumbers); } diff --git a/src/Altinn.Notifications.Core/Models/ContactPoints/UserContactPointAvailability.cs b/src/Altinn.Notifications.Core/Models/ContactPoints/UserContactPointAvailability.cs index cdb00927..020e83c3 100644 --- a/src/Altinn.Notifications.Core/Models/ContactPoints/UserContactPointAvailability.cs +++ b/src/Altinn.Notifications.Core/Models/ContactPoints/UserContactPointAvailability.cs @@ -1,33 +1,32 @@ -namespace Altinn.Notifications.Core.Models.ContactPoints +namespace Altinn.Notifications.Core.Models.ContactPoints; + +/// +/// Class describing the contact points of a user +/// +public class UserContactPointAvailability { /// - /// Class describing the contact points of a user + /// Gets or sets the ID of the user /// - public class UserContactPointAvailability - { - /// - /// Gets or sets the ID of the user - /// - public int UserId { get; set; } + public int UserId { get; set; } - /// - /// Gets or sets the national identityt number of the user - /// - public string NationalIdentityNumber { get; set; } = string.Empty; + /// + /// Gets or sets the national identityt number of the user + /// + public string NationalIdentityNumber { get; set; } = string.Empty; - /// - /// Gets or sets a boolean indicating whether the user has reserved themselves from electronic communication - /// - public bool IsReserved { get; set; } + /// + /// Gets or sets a boolean indicating whether the user has reserved themselves from electronic communication + /// + public bool IsReserved { get; set; } - /// - /// Gets or sets a boolean indicating whether the user has registered a mobile number - /// - public bool MobileNumberRegistered { get; set; } + /// + /// Gets or sets a boolean indicating whether the user has registered a mobile number + /// + public bool MobileNumberRegistered { get; set; } - /// - /// Gets or sets a boolean indicating whether the user has registered an email address - /// - public bool EmailRegistered { get; set; } - } + /// + /// Gets or sets a boolean indicating whether the user has registered an email address + /// + public bool EmailRegistered { get; set; } } diff --git a/src/Altinn.Notifications.Core/Shared/PlatformHttpException.cs b/src/Altinn.Notifications.Core/Shared/PlatformHttpException.cs index 3b159f43..f49b2fef 100644 --- a/src/Altinn.Notifications.Core/Shared/PlatformHttpException.cs +++ b/src/Altinn.Notifications.Core/Shared/PlatformHttpException.cs @@ -1,37 +1,36 @@ -namespace Altinn.Notifications.Core.Shared +namespace Altinn.Notifications.Core.Shared; + +/// +/// Exception class to hold exceptions when interacting with other Altinn platform REST services +/// +public class PlatformHttpException : Exception { /// - /// Exception class to hold exceptions when interacting with other Altinn platform REST services - /// - public class PlatformHttpException : Exception - { - /// - /// Responsible for holding an http request exception towards platform (storage). - /// - public HttpResponseMessage Response { get; } + /// Responsible for holding an http request exception towards platform (storage). + /// + public HttpResponseMessage Response { get; } - /// - /// Copy the response for further investigations - /// - /// the response - /// A description of the cause of the exception. - public PlatformHttpException(HttpResponseMessage response, string message) : base(message) - { - this.Response = response; - } + /// + /// Copy the response for further investigations + /// + /// the response + /// A description of the cause of the exception. + public PlatformHttpException(HttpResponseMessage response, string message) : base(message) + { + this.Response = response; + } - /// - /// Create a new by reading the - /// content asynchronously. - /// - /// The to read. - /// A new . - public static async Task CreateAsync(HttpResponseMessage response) - { - string content = await response.Content.ReadAsStringAsync(); - string message = $"{(int)response.StatusCode} - {response.ReasonPhrase} - {content}"; + /// + /// Create a new by reading the + /// content asynchronously. + /// + /// The to read. + /// A new . + public static async Task CreateAsync(HttpResponseMessage response) + { + string content = await response.Content.ReadAsStringAsync(); + string message = $"{(int)response.StatusCode} - {response.ReasonPhrase} - {content}"; - return new PlatformHttpException(response, message); - } + return new PlatformHttpException(response, message); } } diff --git a/src/Altinn.Notifications.Integrations/Configuration/AltinnServiceSettings.cs b/src/Altinn.Notifications.Integrations/Configuration/AltinnServiceSettings.cs index 7a4a45eb..efc8de1d 100644 --- a/src/Altinn.Notifications.Integrations/Configuration/AltinnServiceSettings.cs +++ b/src/Altinn.Notifications.Integrations/Configuration/AltinnServiceSettings.cs @@ -1,13 +1,12 @@ -namespace Altinn.Notifications.Integrations.Configuration +namespace Altinn.Notifications.Integrations.Configuration; + +/// +/// Configuration object used to hold settings for all Altinn integrations. +/// +public class AltinnServiceSettings { /// - /// Configuration object used to hold settings for all Altinn integrations. + /// Gets or sets the url for the API profile endpoint /// - public class AltinnServiceSettings - { - /// - /// Gets or sets the url for the API profile endpoint - /// - public string ApiProfileEndpoint { get; set; } = string.Empty; - } + public string ApiProfileEndpoint { get; set; } = string.Empty; } diff --git a/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs b/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs index 1a414703..952915c0 100644 --- a/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs +++ b/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs @@ -7,66 +7,65 @@ using Altinn.Notifications.Integrations.Configuration; using Altinn.Notifications.Integrations.Profile; -namespace Altinn.Notifications.Integrations.Clients +namespace Altinn.Notifications.Integrations.Clients; + +/// +/// Implementation of the +/// +public class ProfileClient : IProfileClient { + private readonly HttpClient _client; + /// - /// Implementation of the + /// Initializes a new instance of the class. /// - public class ProfileClient : IProfileClient + public ProfileClient(HttpClient client, AltinnServiceSettings settings) { - private readonly HttpClient _client; - - /// - /// Initializes a new instance of the class. - /// - public ProfileClient(HttpClient client, AltinnServiceSettings settings) - { - _client = client; - _client.BaseAddress = new Uri(settings.ApiProfileEndpoint); - } + _client = client; + _client.BaseAddress = new Uri(settings.ApiProfileEndpoint); + } - /// - public async Task> GetUserContactPoints(List nationalIdentityNumbers) + /// + public async Task> GetUserContactPoints(List nationalIdentityNumbers) + { + var lookupObject = new UserContactPointLookup { - var lookupObject = new UserContactPointLookup - { - NationalIdentityNumbers = nationalIdentityNumbers - }; - - HttpContent content = new StringContent(JsonSerializer.Serialize(lookupObject), Encoding.UTF8, "application/json"); + NationalIdentityNumbers = nationalIdentityNumbers + }; - var response = await _client.PostAsync("contactpoint/lookup", content); + HttpContent content = new StringContent(JsonSerializer.Serialize(lookupObject), Encoding.UTF8, "application/json"); - if (!response.IsSuccessStatusCode) - { - throw new PlatformHttpException(response, $"ProfileClient.GetUserContactPoints failed with status code {response.StatusCode}"); - } + var response = await _client.PostAsync("users/contactpoint/lookup", content); - string responseContent = await response.Content.ReadAsStringAsync(); - List? contactPoints = JsonSerializer.Deserialize(responseContent)!.ContactPointList; - return contactPoints!; + if (!response.IsSuccessStatusCode) + { + throw new PlatformHttpException(response, $"ProfileClient.GetUserContactPoints failed with status code {response.StatusCode}"); } - /// - public async Task> GetUserContactPointAvailabilities(List nationalIdentityNumbers) - { - var lookupObject = new UserContactPointLookup - { - NationalIdentityNumbers = nationalIdentityNumbers - }; + string responseContent = await response.Content.ReadAsStringAsync(); + List? contactPoints = JsonSerializer.Deserialize(responseContent)!.ContactPointList; + return contactPoints!; + } - HttpContent content = new StringContent(JsonSerializer.Serialize(lookupObject), Encoding.UTF8, "application/json"); + /// + public async Task> GetUserContactPointAvailabilities(List nationalIdentityNumbers) + { + var lookupObject = new UserContactPointLookup + { + NationalIdentityNumbers = nationalIdentityNumbers + }; - var response = await _client.PostAsync("contactpoint/availability", content); + HttpContent content = new StringContent(JsonSerializer.Serialize(lookupObject), Encoding.UTF8, "application/json"); - if (!response.IsSuccessStatusCode) - { - throw new PlatformHttpException(response, $"ProfileClient.GetUserContactPointAvailabilities failed with status code {response.StatusCode}"); - } + var response = await _client.PostAsync("users/contactpoint/availability", content); - string responseContent = await response.Content.ReadAsStringAsync(); - List? contactPoints = JsonSerializer.Deserialize(responseContent)!.AvailabilityList; - return contactPoints!; + if (!response.IsSuccessStatusCode) + { + throw new PlatformHttpException(response, $"ProfileClient.GetUserContactPointAvailabilities failed with status code {response.StatusCode}"); } + + string responseContent = await response.Content.ReadAsStringAsync(); + List? contactPoints = JsonSerializer.Deserialize(responseContent)!.AvailabilityList; + return contactPoints!; } } diff --git a/src/Altinn.Notifications.Integrations/Profile/UserContactPointLookup.cs b/src/Altinn.Notifications.Integrations/Profile/UserContactPointLookup.cs index 92acc954..33040092 100644 --- a/src/Altinn.Notifications.Integrations/Profile/UserContactPointLookup.cs +++ b/src/Altinn.Notifications.Integrations/Profile/UserContactPointLookup.cs @@ -1,13 +1,12 @@ -namespace Altinn.Notifications.Integrations.Profile +namespace Altinn.Notifications.Integrations.Profile; + +/// +/// A class respresenting a user contact point lookup object +/// +public class UserContactPointLookup { /// - /// A class respresenting a user contact point lookup object + /// A list of national identity numbers to look up contact points or contact point availability for /// - public class UserContactPointLookup - { - /// - /// A list of national identity numbers to look up contact points or contact point availability for - /// - public List NationalIdentityNumbers { get; set; } = []; - } + public List NationalIdentityNumbers { get; set; } = []; } diff --git a/test/Altinn.Notifications.IntegrationTests/DelegatingHandlerStub.cs b/test/Altinn.Notifications.IntegrationTests/DelegatingHandlerStub.cs new file mode 100644 index 00000000..7d4739ef --- /dev/null +++ b/test/Altinn.Notifications.IntegrationTests/DelegatingHandlerStub.cs @@ -0,0 +1,24 @@ +using System.Net; + +namespace Altinn.Notifications.IntegrationTests +{ + public class DelegatingHandlerStub : DelegatingHandler + { + private readonly Func> _handlerFunc; + + public DelegatingHandlerStub() + { + _handlerFunc = (request, cancellationToken) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); + } + + public DelegatingHandlerStub(Func> handlerFunc) + { + _handlerFunc = handlerFunc; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return _handlerFunc(request, cancellationToken); + } + } +} diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications.Integrations/Profile/ProfileClientTests.cs b/test/Altinn.Notifications.IntegrationTests/Notifications.Integrations/Profile/ProfileClientTests.cs new file mode 100644 index 00000000..a1c48897 --- /dev/null +++ b/test/Altinn.Notifications.IntegrationTests/Notifications.Integrations/Profile/ProfileClientTests.cs @@ -0,0 +1,228 @@ +using System.Net; +using System.Net.Http.Json; +using System.Text.Json; + +using Altinn.Notifications.Core.Models.ContactPoints; +using Altinn.Notifications.Core.Shared; +using Altinn.Notifications.Integrations.Clients; +using Altinn.Notifications.Integrations.Profile; + +using Xunit; + +namespace Altinn.Notifications.IntegrationTests.Notifications.Integrations.Profile; + +public class ProfileClientTests : IClassFixture> +{ + private readonly WebApplicationFactorySetup _factorySetup; + + private readonly JsonSerializerOptions _serializerOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + public ProfileClientTests(IntegrationTestWebApplicationFactory factory) + { + _factorySetup = new WebApplicationFactorySetup(factory) + { + SblBridgeHttpMessageHandler = new DelegatingHandlerStub(async (request, token) => + { + if (request!.RequestUri!.AbsolutePath.EndsWith("contactpoint/lookup")) + { + UserContactPointLookup? lookup = JsonSerializer.Deserialize(await request!.Content!.ReadAsStringAsync(token)); + return await GetResponse(lookup!); + } + else if (request!.RequestUri!.AbsolutePath.EndsWith("contactpoint/availability")) + { + UserContactPointLookup? lookup = JsonSerializer.Deserialize(await request!.Content!.ReadAsStringAsync(token)); + return await GetResponse(lookup!); + } + + return new HttpResponseMessage(HttpStatusCode.NotFound); + }) + }; + } + + [Fact] + public async Task GetUserContactPoints_SuccessResponse_NoMatches() + { + // Arrange + var client = _factorySetup.GetTestServerClient(); + UserContactPointLookup lookup = new() { NationalIdentityNumbers = ["empty-list"] }; + + HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, "users/contactpoint/lookup"); + + httpRequestMessage.Content = new StringContent(JsonSerializer.Serialize(lookup, _serializerOptions), System.Text.Encoding.UTF8, "application/json"); + + // Act + var response = await client.SendAsync(httpRequestMessage); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + UserContactPointsList contactPoints = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync())!; + Assert.Empty(contactPoints.ContactPointList); + } + + [Fact] + public async Task GetUserContactPoints_SuccessResponse_TwoElementsInResponse() + { + // Arrange + var client = _factorySetup.GetTestServerClient(); + UserContactPointLookup lookup = new() { NationalIdentityNumbers = ["populated-list"] }; + + HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, "users/contactpoint/lookup"); + + httpRequestMessage.Content = new StringContent(JsonSerializer.Serialize(lookup, _serializerOptions), System.Text.Encoding.UTF8, "application/json"); + + // Act + var response = await client.SendAsync(httpRequestMessage); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + UserContactPointsList contactPoints = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync())!; + Assert.True(contactPoints.ContactPointList.Count == 2); + Assert.Contains("01025101038", contactPoints.ContactPointList.Select(cp => cp.NationalIdentityNumber)); + } + + [Fact] + public async Task GetUserContactPoints_FailureResponse_ExceptionIsThrown() + { + // Arrange + var client = _factorySetup.GetTestServerClient(); + UserContactPointLookup lookup = new() { NationalIdentityNumbers = ["unavailable"] }; + + HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, "users/contactpoint/lookup"); + + httpRequestMessage.Content = new StringContent(JsonSerializer.Serialize(lookup, _serializerOptions), System.Text.Encoding.UTF8, "application/json"); + + // Act + var exception = await Assert.ThrowsAsync(() => client.SendAsync(httpRequestMessage)); + + // Now you can access properties or perform specific validations on the exception + Assert.StartsWith("ProfileClient.GetUserContactPoints failed with status code", exception.Message); + } + + [Fact] + public async Task GetUserContactPointAvailabilities_SuccessResponse_NoMatches() + { + // Arrange + var client = _factorySetup.GetTestServerClient(); + UserContactPointLookup lookup = new() { NationalIdentityNumbers = ["empty-list"] }; + + HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, "users/contactpoint/availability"); + + httpRequestMessage.Content = new StringContent(JsonSerializer.Serialize(lookup, _serializerOptions), System.Text.Encoding.UTF8, "application/json"); + + // Act + var response = await client.SendAsync(httpRequestMessage); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + UserContactPointAvailabilityList contactPoints = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync())!; + Assert.Empty(contactPoints.AvailabilityList); + } + + [Fact] + public async Task GetUserContactPointAvailabilities_SuccessResponse_TwoElementsInResponse() + { + // Arrange + var client = _factorySetup.GetTestServerClient(); + UserContactPointLookup lookup = new() { NationalIdentityNumbers = ["populated-list"] }; + + HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, "users/contactpoint/availability"); + + httpRequestMessage.Content = new StringContent(JsonSerializer.Serialize(lookup, _serializerOptions), System.Text.Encoding.UTF8, "application/json"); + + // Act + var response = await client.SendAsync(httpRequestMessage); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + UserContactPointAvailabilityList contactPoints = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync())!; + Assert.True(contactPoints.AvailabilityList.Count == 2); + Assert.Contains("01025101038", contactPoints.AvailabilityList.Select(cp => cp.NationalIdentityNumber)); + } + + [Fact] + public async Task GetUserContactPointAvailabilities_FailureResponse_ExceptionIsThrown() + { + // Arrange + var client = _factorySetup.GetTestServerClient(); + UserContactPointLookup lookup = new() { NationalIdentityNumbers = ["unavailable"] }; + + HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, "users/contactpoint/availability"); + + httpRequestMessage.Content = new StringContent(JsonSerializer.Serialize(lookup, _serializerOptions), System.Text.Encoding.UTF8, "application/json"); + + // Act + var exception = await Assert.ThrowsAsync(() => client.SendAsync(httpRequestMessage)); + + // Now you can access properties or perform specific validations on the exception + Assert.StartsWith("ProfileClient.GetUserContactPointAvailabilities failed with status code", exception.Message); + } + + private Task GetResponse(UserContactPointLookup lookup) + { + HttpStatusCode statusCode = HttpStatusCode.OK; + object? contentData = null; + + switch (lookup.NationalIdentityNumbers[0]) + { + case "empty-list": + contentData = GetEmptyListContent(); + break; + case "populated-list": + contentData = GetPopulatedListContent(); + break; + case "unavailable": + statusCode = HttpStatusCode.ServiceUnavailable; + break; + } + + JsonContent? content = (contentData != null) ? JsonContent.Create(contentData, options: _serializerOptions) : null; + + return Task.FromResult( + new HttpResponseMessage() + { + StatusCode = statusCode, + Content = content + }); + } + + private object? GetEmptyListContent() + { + if (typeof(T) == typeof(UserContactPointAvailability)) + { + return new UserContactPointAvailabilityList() { AvailabilityList = new List() }; + } + else if (typeof(T) == typeof(UserContactPoints)) + { + return new UserContactPointsList() { ContactPointList = new List() }; + } + + return null; + } + + private object? GetPopulatedListContent() + { + if (typeof(T) == typeof(UserContactPointAvailability)) + { + var availabilityList = new List + { + new UserContactPointAvailability() { NationalIdentityNumber = "01025101038", EmailRegistered = true }, + new UserContactPointAvailability() { NationalIdentityNumber = "01025101037", EmailRegistered = false } + }; + return new UserContactPointAvailabilityList() { AvailabilityList = availabilityList }; + } + else if (typeof(T) == typeof(UserContactPoints)) + { + var contactPointsList = new List + { + new UserContactPoints() { NationalIdentityNumber = "01025101038", Email = string.Empty }, + new UserContactPoints() { NationalIdentityNumber = "01025101037", Email = string.Empty } + }; + return new UserContactPointsList() { ContactPointList = contactPointsList }; + } + + return null; + } +} diff --git a/test/Altinn.Notifications.IntegrationTests/WebApplicationFactorySetup.cs b/test/Altinn.Notifications.IntegrationTests/WebApplicationFactorySetup.cs new file mode 100644 index 00000000..c773d24e --- /dev/null +++ b/test/Altinn.Notifications.IntegrationTests/WebApplicationFactorySetup.cs @@ -0,0 +1,61 @@ +using Altinn.Common.AccessToken.Services; +using Altinn.Notifications.Core.Integrations; +using Altinn.Notifications.Integrations.Clients; +using Altinn.Notifications.Integrations.Configuration; +using Altinn.Notifications.Tests.Notifications.Mocks.Authentication; + +using AltinnCore.Authentication.JwtCookie; + +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +using Moq; + +namespace Altinn.Notifications.IntegrationTests; + +public class WebApplicationFactorySetup + where T : class +{ + private readonly IntegrationTestWebApplicationFactory _webApplicationFactory; + + public WebApplicationFactorySetup(IntegrationTestWebApplicationFactory webApplicationFactory) + { + _webApplicationFactory = webApplicationFactory; + } + + public Mock> ProfileClientLogger { get; set; } = new(); + + public AltinnServiceSettings AltinnServiceSettingsOptions { get; set; } = new(); + + public HttpMessageHandler SblBridgeHttpMessageHandler { get; set; } = new DelegatingHandlerStub(); + + public HttpClient GetTestServerClient() + { + MemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions()); + + AltinnServiceSettings altinnServiceSettings = new() + { + ApiProfileEndpoint = "https://at22.altinn.cloud/sblbridge/profile/api/" + }; + + return _webApplicationFactory.WithWebHostBuilder(builder => + { + builder.ConfigureTestServices(services => + { + services.AddSingleton, JwtCookiePostConfigureOptionsStub>(); + services.AddSingleton(); + services.AddSingleton(memoryCache); + + // Using the real/actual implementation of IUserProfiles, but with a mocked message handler. + // Haven't found any other ways of injecting a mocked message handler to simulate SBL Bridge. + services.AddSingleton( + new ProfileClient( + new HttpClient(SblBridgeHttpMessageHandler), + altinnServiceSettings)); + }); + }).CreateClient(); + } +} diff --git a/test/Altinn.Notifications.IntegrationTests/appsettings.IntegrationTest.json b/test/Altinn.Notifications.IntegrationTests/appsettings.IntegrationTest.json index 7d8c5736..a21ed87b 100644 --- a/test/Altinn.Notifications.IntegrationTests/appsettings.IntegrationTest.json +++ b/test/Altinn.Notifications.IntegrationTests/appsettings.IntegrationTest.json @@ -1,4 +1,7 @@ { + "AltinnServiceSettings": { + "ApiProfileEndpoint": "http://internal.platform.at22.altinn.cloud/profil/api/v1/" + }, "PostgreSQLSettings": { "EnableDBConnection": true, "AdminConnectionString": "Host=localhost;Port=5432;Username=platform_notifications_admin;Password={0};Database=notificationsdb;Connection Idle Lifetime=30", From 2120b3919656d80af66e2fcdcf1a75573e6a0cca Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Tue, 12 Mar 2024 13:09:20 +0100 Subject: [PATCH 04/22] added unit tests --- .../JsonSerializerOptionsProvider.cs | 3 +- .../Profile/ProfileClientTests.cs | 228 ------------------ .../WebApplicationFactorySetup.cs | 61 ----- .../DelegatingHandlerStub.cs | 28 +++ .../Profile/ProfileClientTests.cs | 185 ++++++++++++++ 5 files changed, 215 insertions(+), 290 deletions(-) delete mode 100644 test/Altinn.Notifications.IntegrationTests/Notifications.Integrations/Profile/ProfileClientTests.cs delete mode 100644 test/Altinn.Notifications.IntegrationTests/WebApplicationFactorySetup.cs create mode 100644 test/Altinn.Notifications.Tests/DelegatingHandlerStub.cs create mode 100644 test/Altinn.Notifications.Tests/Notifications.Integrations/Profile/ProfileClientTests.cs diff --git a/src/Altinn.Notifications.Core/JsonSerializerOptionsProvider.cs b/src/Altinn.Notifications.Core/JsonSerializerOptionsProvider.cs index 58146671..890b9d4b 100644 --- a/src/Altinn.Notifications.Core/JsonSerializerOptionsProvider.cs +++ b/src/Altinn.Notifications.Core/JsonSerializerOptionsProvider.cs @@ -15,7 +15,8 @@ public static class JsonSerializerOptionsProvider { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = { new JsonStringEnumConverter() } + Converters = { new JsonStringEnumConverter() }, + PropertyNameCaseInsensitive = true }; } } diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications.Integrations/Profile/ProfileClientTests.cs b/test/Altinn.Notifications.IntegrationTests/Notifications.Integrations/Profile/ProfileClientTests.cs deleted file mode 100644 index a1c48897..00000000 --- a/test/Altinn.Notifications.IntegrationTests/Notifications.Integrations/Profile/ProfileClientTests.cs +++ /dev/null @@ -1,228 +0,0 @@ -using System.Net; -using System.Net.Http.Json; -using System.Text.Json; - -using Altinn.Notifications.Core.Models.ContactPoints; -using Altinn.Notifications.Core.Shared; -using Altinn.Notifications.Integrations.Clients; -using Altinn.Notifications.Integrations.Profile; - -using Xunit; - -namespace Altinn.Notifications.IntegrationTests.Notifications.Integrations.Profile; - -public class ProfileClientTests : IClassFixture> -{ - private readonly WebApplicationFactorySetup _factorySetup; - - private readonly JsonSerializerOptions _serializerOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - - public ProfileClientTests(IntegrationTestWebApplicationFactory factory) - { - _factorySetup = new WebApplicationFactorySetup(factory) - { - SblBridgeHttpMessageHandler = new DelegatingHandlerStub(async (request, token) => - { - if (request!.RequestUri!.AbsolutePath.EndsWith("contactpoint/lookup")) - { - UserContactPointLookup? lookup = JsonSerializer.Deserialize(await request!.Content!.ReadAsStringAsync(token)); - return await GetResponse(lookup!); - } - else if (request!.RequestUri!.AbsolutePath.EndsWith("contactpoint/availability")) - { - UserContactPointLookup? lookup = JsonSerializer.Deserialize(await request!.Content!.ReadAsStringAsync(token)); - return await GetResponse(lookup!); - } - - return new HttpResponseMessage(HttpStatusCode.NotFound); - }) - }; - } - - [Fact] - public async Task GetUserContactPoints_SuccessResponse_NoMatches() - { - // Arrange - var client = _factorySetup.GetTestServerClient(); - UserContactPointLookup lookup = new() { NationalIdentityNumbers = ["empty-list"] }; - - HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, "users/contactpoint/lookup"); - - httpRequestMessage.Content = new StringContent(JsonSerializer.Serialize(lookup, _serializerOptions), System.Text.Encoding.UTF8, "application/json"); - - // Act - var response = await client.SendAsync(httpRequestMessage); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - UserContactPointsList contactPoints = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync())!; - Assert.Empty(contactPoints.ContactPointList); - } - - [Fact] - public async Task GetUserContactPoints_SuccessResponse_TwoElementsInResponse() - { - // Arrange - var client = _factorySetup.GetTestServerClient(); - UserContactPointLookup lookup = new() { NationalIdentityNumbers = ["populated-list"] }; - - HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, "users/contactpoint/lookup"); - - httpRequestMessage.Content = new StringContent(JsonSerializer.Serialize(lookup, _serializerOptions), System.Text.Encoding.UTF8, "application/json"); - - // Act - var response = await client.SendAsync(httpRequestMessage); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - UserContactPointsList contactPoints = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync())!; - Assert.True(contactPoints.ContactPointList.Count == 2); - Assert.Contains("01025101038", contactPoints.ContactPointList.Select(cp => cp.NationalIdentityNumber)); - } - - [Fact] - public async Task GetUserContactPoints_FailureResponse_ExceptionIsThrown() - { - // Arrange - var client = _factorySetup.GetTestServerClient(); - UserContactPointLookup lookup = new() { NationalIdentityNumbers = ["unavailable"] }; - - HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, "users/contactpoint/lookup"); - - httpRequestMessage.Content = new StringContent(JsonSerializer.Serialize(lookup, _serializerOptions), System.Text.Encoding.UTF8, "application/json"); - - // Act - var exception = await Assert.ThrowsAsync(() => client.SendAsync(httpRequestMessage)); - - // Now you can access properties or perform specific validations on the exception - Assert.StartsWith("ProfileClient.GetUserContactPoints failed with status code", exception.Message); - } - - [Fact] - public async Task GetUserContactPointAvailabilities_SuccessResponse_NoMatches() - { - // Arrange - var client = _factorySetup.GetTestServerClient(); - UserContactPointLookup lookup = new() { NationalIdentityNumbers = ["empty-list"] }; - - HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, "users/contactpoint/availability"); - - httpRequestMessage.Content = new StringContent(JsonSerializer.Serialize(lookup, _serializerOptions), System.Text.Encoding.UTF8, "application/json"); - - // Act - var response = await client.SendAsync(httpRequestMessage); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - UserContactPointAvailabilityList contactPoints = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync())!; - Assert.Empty(contactPoints.AvailabilityList); - } - - [Fact] - public async Task GetUserContactPointAvailabilities_SuccessResponse_TwoElementsInResponse() - { - // Arrange - var client = _factorySetup.GetTestServerClient(); - UserContactPointLookup lookup = new() { NationalIdentityNumbers = ["populated-list"] }; - - HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, "users/contactpoint/availability"); - - httpRequestMessage.Content = new StringContent(JsonSerializer.Serialize(lookup, _serializerOptions), System.Text.Encoding.UTF8, "application/json"); - - // Act - var response = await client.SendAsync(httpRequestMessage); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - UserContactPointAvailabilityList contactPoints = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync())!; - Assert.True(contactPoints.AvailabilityList.Count == 2); - Assert.Contains("01025101038", contactPoints.AvailabilityList.Select(cp => cp.NationalIdentityNumber)); - } - - [Fact] - public async Task GetUserContactPointAvailabilities_FailureResponse_ExceptionIsThrown() - { - // Arrange - var client = _factorySetup.GetTestServerClient(); - UserContactPointLookup lookup = new() { NationalIdentityNumbers = ["unavailable"] }; - - HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, "users/contactpoint/availability"); - - httpRequestMessage.Content = new StringContent(JsonSerializer.Serialize(lookup, _serializerOptions), System.Text.Encoding.UTF8, "application/json"); - - // Act - var exception = await Assert.ThrowsAsync(() => client.SendAsync(httpRequestMessage)); - - // Now you can access properties or perform specific validations on the exception - Assert.StartsWith("ProfileClient.GetUserContactPointAvailabilities failed with status code", exception.Message); - } - - private Task GetResponse(UserContactPointLookup lookup) - { - HttpStatusCode statusCode = HttpStatusCode.OK; - object? contentData = null; - - switch (lookup.NationalIdentityNumbers[0]) - { - case "empty-list": - contentData = GetEmptyListContent(); - break; - case "populated-list": - contentData = GetPopulatedListContent(); - break; - case "unavailable": - statusCode = HttpStatusCode.ServiceUnavailable; - break; - } - - JsonContent? content = (contentData != null) ? JsonContent.Create(contentData, options: _serializerOptions) : null; - - return Task.FromResult( - new HttpResponseMessage() - { - StatusCode = statusCode, - Content = content - }); - } - - private object? GetEmptyListContent() - { - if (typeof(T) == typeof(UserContactPointAvailability)) - { - return new UserContactPointAvailabilityList() { AvailabilityList = new List() }; - } - else if (typeof(T) == typeof(UserContactPoints)) - { - return new UserContactPointsList() { ContactPointList = new List() }; - } - - return null; - } - - private object? GetPopulatedListContent() - { - if (typeof(T) == typeof(UserContactPointAvailability)) - { - var availabilityList = new List - { - new UserContactPointAvailability() { NationalIdentityNumber = "01025101038", EmailRegistered = true }, - new UserContactPointAvailability() { NationalIdentityNumber = "01025101037", EmailRegistered = false } - }; - return new UserContactPointAvailabilityList() { AvailabilityList = availabilityList }; - } - else if (typeof(T) == typeof(UserContactPoints)) - { - var contactPointsList = new List - { - new UserContactPoints() { NationalIdentityNumber = "01025101038", Email = string.Empty }, - new UserContactPoints() { NationalIdentityNumber = "01025101037", Email = string.Empty } - }; - return new UserContactPointsList() { ContactPointList = contactPointsList }; - } - - return null; - } -} diff --git a/test/Altinn.Notifications.IntegrationTests/WebApplicationFactorySetup.cs b/test/Altinn.Notifications.IntegrationTests/WebApplicationFactorySetup.cs deleted file mode 100644 index c773d24e..00000000 --- a/test/Altinn.Notifications.IntegrationTests/WebApplicationFactorySetup.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Altinn.Common.AccessToken.Services; -using Altinn.Notifications.Core.Integrations; -using Altinn.Notifications.Integrations.Clients; -using Altinn.Notifications.Integrations.Configuration; -using Altinn.Notifications.Tests.Notifications.Mocks.Authentication; - -using AltinnCore.Authentication.JwtCookie; - -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -using Moq; - -namespace Altinn.Notifications.IntegrationTests; - -public class WebApplicationFactorySetup - where T : class -{ - private readonly IntegrationTestWebApplicationFactory _webApplicationFactory; - - public WebApplicationFactorySetup(IntegrationTestWebApplicationFactory webApplicationFactory) - { - _webApplicationFactory = webApplicationFactory; - } - - public Mock> ProfileClientLogger { get; set; } = new(); - - public AltinnServiceSettings AltinnServiceSettingsOptions { get; set; } = new(); - - public HttpMessageHandler SblBridgeHttpMessageHandler { get; set; } = new DelegatingHandlerStub(); - - public HttpClient GetTestServerClient() - { - MemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions()); - - AltinnServiceSettings altinnServiceSettings = new() - { - ApiProfileEndpoint = "https://at22.altinn.cloud/sblbridge/profile/api/" - }; - - return _webApplicationFactory.WithWebHostBuilder(builder => - { - builder.ConfigureTestServices(services => - { - services.AddSingleton, JwtCookiePostConfigureOptionsStub>(); - services.AddSingleton(); - services.AddSingleton(memoryCache); - - // Using the real/actual implementation of IUserProfiles, but with a mocked message handler. - // Haven't found any other ways of injecting a mocked message handler to simulate SBL Bridge. - services.AddSingleton( - new ProfileClient( - new HttpClient(SblBridgeHttpMessageHandler), - altinnServiceSettings)); - }); - }).CreateClient(); - } -} diff --git a/test/Altinn.Notifications.Tests/DelegatingHandlerStub.cs b/test/Altinn.Notifications.Tests/DelegatingHandlerStub.cs new file mode 100644 index 00000000..6f69e94e --- /dev/null +++ b/test/Altinn.Notifications.Tests/DelegatingHandlerStub.cs @@ -0,0 +1,28 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Altinn.Notifications.IntegrationTests +{ + public class DelegatingHandlerStub : DelegatingHandler + { + private readonly Func> _handlerFunc; + + public DelegatingHandlerStub() + { + _handlerFunc = (request, cancellationToken) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); + } + + public DelegatingHandlerStub(Func> handlerFunc) + { + _handlerFunc = handlerFunc; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return _handlerFunc(request, cancellationToken); + } + } +} diff --git a/test/Altinn.Notifications.Tests/Notifications.Integrations/Profile/ProfileClientTests.cs b/test/Altinn.Notifications.Tests/Notifications.Integrations/Profile/ProfileClientTests.cs new file mode 100644 index 00000000..46d2c618 --- /dev/null +++ b/test/Altinn.Notifications.Tests/Notifications.Integrations/Profile/ProfileClientTests.cs @@ -0,0 +1,185 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text.Json; +using System.Threading.Tasks; + +using Altinn.Notifications.Core; +using Altinn.Notifications.Core.Integrations; +using Altinn.Notifications.Core.Models.ContactPoints; +using Altinn.Notifications.Core.Shared; +using Altinn.Notifications.Integrations.Clients; +using Altinn.Notifications.Integrations.Configuration; +using Altinn.Notifications.Integrations.Profile; + +using Xunit; + +namespace Altinn.Notifications.IntegrationTests.Notifications.Integrations.Profile; + +public class ProfileClientTests +{ + private readonly JsonSerializerOptions _serializerOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + private readonly IProfileClient _profileClient; + + public ProfileClientTests() + { + var sblBridgeHttpMessageHandler = new DelegatingHandlerStub(async (request, token) => + { + if (request!.RequestUri!.AbsolutePath.EndsWith("contactpoint/lookup")) + { + UserContactPointLookup? lookup = JsonSerializer.Deserialize(await request!.Content!.ReadAsStringAsync(token), JsonSerializerOptionsProvider.Options); + return await GetResponse(lookup!); + } + else if (request!.RequestUri!.AbsolutePath.EndsWith("contactpoint/availability")) + { + UserContactPointLookup? lookup = JsonSerializer.Deserialize(await request!.Content!.ReadAsStringAsync(token), JsonSerializerOptionsProvider.Options); + return await GetResponse(lookup!); + } + + return new HttpResponseMessage(HttpStatusCode.NotFound); + }); + + AltinnServiceSettings settings = new() + { + ApiProfileEndpoint = "https://platform.at22.altinn.cloud/profile/api/v1/" + }; + + _profileClient = new ProfileClient( + new HttpClient(sblBridgeHttpMessageHandler), + settings); + + } + + [Fact] + public async Task GetUserContactPoints_SuccessResponse_NoMatches() + { + // Act + List actual = await _profileClient.GetUserContactPoints(["empty-list"]); + + // Assert + Assert.Empty(actual); + } + + [Fact] + public async Task GetUserContactPoints_SuccessResponse_TwoElementsInResponse() + { + // Act + List actual = await _profileClient.GetUserContactPoints(["populated-list"]); + + // Assert + Assert.True(actual.Count == 2); + Assert.Contains("01025101038", actual.Select(cp => cp.NationalIdentityNumber)); + } + + [Fact] + public async Task GetUserContactPoints_FailureResponse_ExceptionIsThrown() + { + // Act + var exception = await Assert.ThrowsAsync(async () => await _profileClient.GetUserContactPoints(["unavailable"])); + + Assert.StartsWith("ProfileClient.GetUserContactPoints failed with status code", exception.Message); + } + + [Fact] + public async Task GetUserContactPointAvailabilities_SuccessResponse_NoMatches() + { + // Act + List actual = await _profileClient.GetUserContactPointAvailabilities(["empty-list"]); + + // Assert + Assert.Empty(actual); + } + + [Fact] + public async Task GetUserContactPointAvailabilities_SuccessResponse_TwoElementsInResponse() + { + // Act + List actual = await _profileClient.GetUserContactPointAvailabilities(["populated-list"]); + + // Assert + Assert.True(actual.Count == 2); + Assert.Contains("01025101038", actual.Select(cp => cp.NationalIdentityNumber)); + } + + [Fact] + public async Task GetUserContactPointAvailabilities_FailureResponse_ExceptionIsThrown() + { + // Act + var exception = await Assert.ThrowsAsync(async () => await _profileClient.GetUserContactPointAvailabilities(["unavailable"])); + + // Assert + Assert.StartsWith("ProfileClient.GetUserContactPointAvailabilities failed with status code", exception.Message); + } + + private Task GetResponse(UserContactPointLookup lookup) + { + HttpStatusCode statusCode = HttpStatusCode.OK; + object? contentData = null; + + switch (lookup.NationalIdentityNumbers[0]) + { + case "empty-list": + contentData = GetEmptyListContent(); + break; + case "populated-list": + contentData = GetPopulatedListContent(); + break; + case "unavailable": + statusCode = HttpStatusCode.ServiceUnavailable; + break; + } + + JsonContent? content = (contentData != null) ? JsonContent.Create(contentData, options: _serializerOptions) : null; + + return Task.FromResult( + new HttpResponseMessage() + { + StatusCode = statusCode, + Content = content + }); + } + + private object? GetEmptyListContent() + { + if (typeof(T) == typeof(UserContactPointAvailabilityList)) + { + return new UserContactPointAvailabilityList() { AvailabilityList = new List() }; + } + else if (typeof(T) == typeof(UserContactPointsList)) + { + return new UserContactPointsList() { ContactPointList = new List() }; + } + + return null; + } + + private object? GetPopulatedListContent() + { + if (typeof(T) == typeof(UserContactPointAvailabilityList)) + { + var availabilityList = new List + { + new UserContactPointAvailability() { NationalIdentityNumber = "01025101038", EmailRegistered = true }, + new UserContactPointAvailability() { NationalIdentityNumber = "01025101037", EmailRegistered = false } + }; + return new UserContactPointAvailabilityList() { AvailabilityList = availabilityList }; + } + else if (typeof(T) == typeof(UserContactPointsList)) + { + var contactPointsList = new List + { + new UserContactPoints() { NationalIdentityNumber = "01025101038", Email = string.Empty }, + new UserContactPoints() { NationalIdentityNumber = "01025101037", Email = string.Empty } + }; + return new UserContactPointsList() { ContactPointList = contactPointsList }; + } + + return null; + } +} From 3d204ce573f0994b603c5858e0f4501a9e259587 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Tue, 12 Mar 2024 13:30:03 +0100 Subject: [PATCH 05/22] added missing changes in profile client --- .../Profile/ProfileClient.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs b/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs index 952915c0..9fe69c06 100644 --- a/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs +++ b/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs @@ -1,6 +1,7 @@ using System.Text; using System.Text.Json; +using Altinn.Notifications.Core; using Altinn.Notifications.Core.Integrations; using Altinn.Notifications.Core.Models.ContactPoints; using Altinn.Notifications.Core.Shared; @@ -33,17 +34,17 @@ public async Task> GetUserContactPoints(List nat NationalIdentityNumbers = nationalIdentityNumbers }; - HttpContent content = new StringContent(JsonSerializer.Serialize(lookupObject), Encoding.UTF8, "application/json"); + HttpContent content = new StringContent(JsonSerializer.Serialize(lookupObject, JsonSerializerOptionsProvider.Options), Encoding.UTF8, "application/json"); var response = await _client.PostAsync("users/contactpoint/lookup", content); if (!response.IsSuccessStatusCode) { - throw new PlatformHttpException(response, $"ProfileClient.GetUserContactPoints failed with status code {response.StatusCode}"); + throw new PlatformHttpException(response, $"ProfileClient.GetUserContactPoints failed with status code {response.StatusCode}"); } string responseContent = await response.Content.ReadAsStringAsync(); - List? contactPoints = JsonSerializer.Deserialize(responseContent)!.ContactPointList; + List? contactPoints = JsonSerializer.Deserialize(responseContent, JsonSerializerOptionsProvider.Options)!.ContactPointList; return contactPoints!; } @@ -55,7 +56,7 @@ public async Task> GetUserContactPointAvailab NationalIdentityNumbers = nationalIdentityNumbers }; - HttpContent content = new StringContent(JsonSerializer.Serialize(lookupObject), Encoding.UTF8, "application/json"); + HttpContent content = new StringContent(JsonSerializer.Serialize(lookupObject, JsonSerializerOptionsProvider.Options), Encoding.UTF8, "application/json"); var response = await _client.PostAsync("users/contactpoint/availability", content); @@ -65,7 +66,7 @@ public async Task> GetUserContactPointAvailab } string responseContent = await response.Content.ReadAsStringAsync(); - List? contactPoints = JsonSerializer.Deserialize(responseContent)!.AvailabilityList; + List? contactPoints = JsonSerializer.Deserialize(responseContent, JsonSerializerOptionsProvider.Options)!.AvailabilityList; return contactPoints!; } } From 2c755b318ad6720419db7d0687b8ab3aa25a09c5 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Tue, 12 Mar 2024 13:47:30 +0100 Subject: [PATCH 06/22] fixed code smells --- .../Profile/ProfileClientTests.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/Altinn.Notifications.Tests/Notifications.Integrations/Profile/ProfileClientTests.cs b/test/Altinn.Notifications.Tests/Notifications.Integrations/Profile/ProfileClientTests.cs index 46d2c618..4fdb8b27 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Integrations/Profile/ProfileClientTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Integrations/Profile/ProfileClientTests.cs @@ -25,7 +25,7 @@ public class ProfileClientTests PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - private readonly IProfileClient _profileClient; + private readonly ProfileClient _profileClient; public ProfileClientTests() { @@ -53,7 +53,6 @@ public ProfileClientTests() _profileClient = new ProfileClient( new HttpClient(sblBridgeHttpMessageHandler), settings); - } [Fact] @@ -73,7 +72,7 @@ public async Task GetUserContactPoints_SuccessResponse_TwoElementsInResponse() List actual = await _profileClient.GetUserContactPoints(["populated-list"]); // Assert - Assert.True(actual.Count == 2); + Assert.Equal(2, actual.Count); Assert.Contains("01025101038", actual.Select(cp => cp.NationalIdentityNumber)); } @@ -103,7 +102,7 @@ public async Task GetUserContactPointAvailabilities_SuccessResponse_TwoElementsI List actual = await _profileClient.GetUserContactPointAvailabilities(["populated-list"]); // Assert - Assert.True(actual.Count == 2); + Assert.Equal(2, actual.Count); Assert.Contains("01025101038", actual.Select(cp => cp.NationalIdentityNumber)); } @@ -145,7 +144,7 @@ private Task GetResponse(UserContactPointLookup lookup) }); } - private object? GetEmptyListContent() + private static object? GetEmptyListContent() { if (typeof(T) == typeof(UserContactPointAvailabilityList)) { @@ -159,7 +158,7 @@ private Task GetResponse(UserContactPointLookup lookup) return null; } - private object? GetPopulatedListContent() + private static object? GetPopulatedListContent() { if (typeof(T) == typeof(UserContactPointAvailabilityList)) { From b7c3aff414ed405595315e618d607e3a48f15d33 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Tue, 12 Mar 2024 13:49:53 +0100 Subject: [PATCH 07/22] undid changes in int tests --- .../DelegatingHandlerStub.cs | 24 ------------------- .../appsettings.IntegrationTest.json | 3 --- 2 files changed, 27 deletions(-) delete mode 100644 test/Altinn.Notifications.IntegrationTests/DelegatingHandlerStub.cs diff --git a/test/Altinn.Notifications.IntegrationTests/DelegatingHandlerStub.cs b/test/Altinn.Notifications.IntegrationTests/DelegatingHandlerStub.cs deleted file mode 100644 index 7d4739ef..00000000 --- a/test/Altinn.Notifications.IntegrationTests/DelegatingHandlerStub.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Net; - -namespace Altinn.Notifications.IntegrationTests -{ - public class DelegatingHandlerStub : DelegatingHandler - { - private readonly Func> _handlerFunc; - - public DelegatingHandlerStub() - { - _handlerFunc = (request, cancellationToken) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); - } - - public DelegatingHandlerStub(Func> handlerFunc) - { - _handlerFunc = handlerFunc; - } - - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - return _handlerFunc(request, cancellationToken); - } - } -} diff --git a/test/Altinn.Notifications.IntegrationTests/appsettings.IntegrationTest.json b/test/Altinn.Notifications.IntegrationTests/appsettings.IntegrationTest.json index a21ed87b..7d8c5736 100644 --- a/test/Altinn.Notifications.IntegrationTests/appsettings.IntegrationTest.json +++ b/test/Altinn.Notifications.IntegrationTests/appsettings.IntegrationTest.json @@ -1,7 +1,4 @@ { - "AltinnServiceSettings": { - "ApiProfileEndpoint": "http://internal.platform.at22.altinn.cloud/profil/api/v1/" - }, "PostgreSQLSettings": { "EnableDBConnection": true, "AdminConnectionString": "Host=localhost;Port=5432;Username=platform_notifications_admin;Password={0};Database=notificationsdb;Connection Idle Lifetime=30", From b322ab8e4fab74304560a884178aaca8ef31b787 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Tue, 12 Mar 2024 16:45:12 +0100 Subject: [PATCH 08/22] fixed options for settings --- .../Profile/ProfileClient.cs | 6 ++++-- .../Profile/ProfileClientTests.cs | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs b/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs index 9fe69c06..86573e6b 100644 --- a/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs +++ b/src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs @@ -8,6 +8,8 @@ using Altinn.Notifications.Integrations.Configuration; using Altinn.Notifications.Integrations.Profile; +using Microsoft.Extensions.Options; + namespace Altinn.Notifications.Integrations.Clients; /// @@ -20,10 +22,10 @@ public class ProfileClient : IProfileClient /// /// Initializes a new instance of the class. /// - public ProfileClient(HttpClient client, AltinnServiceSettings settings) + public ProfileClient(HttpClient client, IOptions settings) { _client = client; - _client.BaseAddress = new Uri(settings.ApiProfileEndpoint); + _client.BaseAddress = new Uri(settings.Value.ApiProfileEndpoint); } /// diff --git a/test/Altinn.Notifications.Tests/Notifications.Integrations/Profile/ProfileClientTests.cs b/test/Altinn.Notifications.Tests/Notifications.Integrations/Profile/ProfileClientTests.cs index 4fdb8b27..f667c765 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Integrations/Profile/ProfileClientTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Integrations/Profile/ProfileClientTests.cs @@ -14,6 +14,8 @@ using Altinn.Notifications.Integrations.Configuration; using Altinn.Notifications.Integrations.Profile; +using Microsoft.Extensions.Options; + using Xunit; namespace Altinn.Notifications.IntegrationTests.Notifications.Integrations.Profile; @@ -52,7 +54,7 @@ public ProfileClientTests() _profileClient = new ProfileClient( new HttpClient(sblBridgeHttpMessageHandler), - settings); + Options.Create(settings)); } [Fact] @@ -144,7 +146,7 @@ private Task GetResponse(UserContactPointLookup lookup) }); } - private static object? GetEmptyListContent() + private static object? GetEmptyListContent() { if (typeof(T) == typeof(UserContactPointAvailabilityList)) { From 0e8c8dbe3fd05551a0667f275ebfc7287cb307c6 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Tue, 12 Mar 2024 16:57:15 +0100 Subject: [PATCH 09/22] added unit test for exception --- .../Notifications.Integrations/Profile/ProfileClientTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/Altinn.Notifications.Tests/Notifications.Integrations/Profile/ProfileClientTests.cs b/test/Altinn.Notifications.Tests/Notifications.Integrations/Profile/ProfileClientTests.cs index f667c765..e6b29ab1 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Integrations/Profile/ProfileClientTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Integrations/Profile/ProfileClientTests.cs @@ -85,6 +85,7 @@ public async Task GetUserContactPoints_FailureResponse_ExceptionIsThrown() var exception = await Assert.ThrowsAsync(async () => await _profileClient.GetUserContactPoints(["unavailable"])); Assert.StartsWith("ProfileClient.GetUserContactPoints failed with status code", exception.Message); + Assert.Equal(HttpStatusCode.ServiceUnavailable, exception.Response?.StatusCode); } [Fact] @@ -116,6 +117,7 @@ public async Task GetUserContactPointAvailabilities_FailureResponse_ExceptionIsT // Assert Assert.StartsWith("ProfileClient.GetUserContactPointAvailabilities failed with status code", exception.Message); + Assert.Equal(HttpStatusCode.ServiceUnavailable, exception.Response?.StatusCode); } private Task GetResponse(UserContactPointLookup lookup) From fe6e4851386a581ebfc8b6f9f75278638585ec40 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Tue, 12 Mar 2024 17:05:39 +0100 Subject: [PATCH 10/22] core logic for generating notification from nin --- .../Enums/EmailNotificationResultType.cs | 5 + .../Enums/SmsNotificationResultType.cs | 15 ++- .../Extensions/ServiceCollectionExtensions.cs | 1 + .../Models/Orders/NotificationOrder.cs | 8 +- .../Models/Orders/NotificationOrderRequest.cs | 8 +- .../Models/Recipient.cs | 5 + .../Services/ContactPointService.cs | 100 ++++++++++++++++++ .../Services/EmailNotificationService.cs | 17 ++- .../EmailNotificationSummaryService.cs | 1 + .../Services/EmailOrderProcessingService.cs | 24 ++++- .../Interfaces/IContactPointService.cs | 30 ++++++ .../Interfaces/IEmailNotificationService.cs | 2 +- .../Interfaces/ISmsNotificationService.cs | 2 +- .../Services/SmsNotificationService.cs | 13 ++- .../Services/SmsNotificationSummaryService.cs | 5 +- .../Services/SmsOrderProcessingService.cs | 19 +++- .../Utils/ServiceUtil.cs | 1 + .../TestingModels/NotificationOrderTests.cs | 9 +- .../EmailNotificationServiceTests.cs | 2 +- .../EmailNotificationSummaryServiceTests.cs | 3 + .../EmailOrderProcessingServiceTests.cs | 39 +++++-- .../SmsNotificationServiceTests.cs | 2 +- .../SmsNotificationSummaryServiceTests.cs | 3 +- .../SmsOrderProcessingServiceTests.cs | 41 +++++-- 24 files changed, 303 insertions(+), 52 deletions(-) create mode 100644 src/Altinn.Notifications.Core/Services/ContactPointService.cs create mode 100644 src/Altinn.Notifications.Core/Services/Interfaces/IContactPointService.cs diff --git a/src/Altinn.Notifications.Core/Enums/EmailNotificationResultType.cs b/src/Altinn.Notifications.Core/Enums/EmailNotificationResultType.cs index f29ef042..8761074f 100644 --- a/src/Altinn.Notifications.Core/Enums/EmailNotificationResultType.cs +++ b/src/Altinn.Notifications.Core/Enums/EmailNotificationResultType.cs @@ -30,6 +30,11 @@ public enum EmailNotificationResultType /// Failed, + /// + /// Failed, recipient is reserved in KRR + /// + Failed_RecipientReserved, + /// /// Recipient to address was not identified /// diff --git a/src/Altinn.Notifications.Core/Enums/SmsNotificationResultType.cs b/src/Altinn.Notifications.Core/Enums/SmsNotificationResultType.cs index 3f40ff78..77fdf705 100644 --- a/src/Altinn.Notifications.Core/Enums/SmsNotificationResultType.cs +++ b/src/Altinn.Notifications.Core/Enums/SmsNotificationResultType.cs @@ -30,6 +30,16 @@ public enum SmsNotificationResultType /// Failed, + /// + /// Sms notification send operation failed due to invalid recipient + /// + Failed_InvalidRecipient, + + /// + /// Failed, recipient is reserved in KRR + /// + Failed_RecipientReserved, + /// /// Sms notification send operation failed because the receiver number is barred/blocked/not in use. /// @@ -45,11 +55,6 @@ public enum SmsNotificationResultType /// Failed_Expired, - /// - /// Sms notification send operation failed due to invalid recipient - /// - Failed_InvalidRecipient, - /// /// Sms notification send operation failed due to the SMS being undeliverable. /// diff --git a/src/Altinn.Notifications.Core/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.Notifications.Core/Extensions/ServiceCollectionExtensions.cs index e2c10c61..de83935e 100644 --- a/src/Altinn.Notifications.Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.Notifications.Core/Extensions/ServiceCollectionExtensions.cs @@ -39,6 +39,7 @@ public static void AddCoreServices(this IServiceCollection services, IConfigurat .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/Altinn.Notifications.Core/Models/Orders/NotificationOrder.cs b/src/Altinn.Notifications.Core/Models/Orders/NotificationOrder.cs index 2c053e67..ea1024bc 100644 --- a/src/Altinn.Notifications.Core/Models/Orders/NotificationOrder.cs +++ b/src/Altinn.Notifications.Core/Models/Orders/NotificationOrder.cs @@ -38,10 +38,15 @@ public class NotificationOrder : IBaseNotificationOrder /// public List Recipients { get; internal set; } = new List(); + /// + /// Gets the boolean indicating if the KRR reservation should be ignored + /// + public bool IgnoreReservation { get; internal set; } + /// /// Initializes a new instance of the class. /// - public NotificationOrder(Guid id, string? sendersReference, List templates, DateTime requestedSendTime, NotificationChannel notificationChannel, Creator creator, DateTime created, List recipients) + public NotificationOrder(Guid id, string? sendersReference, List templates, DateTime requestedSendTime, NotificationChannel notificationChannel, Creator creator, DateTime created, List recipients, bool ignoreReservation = false) { Id = id; SendersReference = sendersReference; @@ -51,6 +56,7 @@ public NotificationOrder(Guid id, string? sendersReference, List diff --git a/src/Altinn.Notifications.Core/Models/Orders/NotificationOrderRequest.cs b/src/Altinn.Notifications.Core/Models/Orders/NotificationOrderRequest.cs index 31baf656..e08a2177 100644 --- a/src/Altinn.Notifications.Core/Models/Orders/NotificationOrderRequest.cs +++ b/src/Altinn.Notifications.Core/Models/Orders/NotificationOrderRequest.cs @@ -38,10 +38,15 @@ public class NotificationOrderRequest /// public Creator Creator { get; internal set; } + /// + /// Gets the boolean indicating if the KRR reservation should be ignored + /// + public bool IgnoreReservation { get; internal set; } + /// /// Initializes a new instance of the class. /// - public NotificationOrderRequest(string? sendersReference, string creatorShortName, List templates, DateTime requestedSendTime, NotificationChannel notificationChannel, List recipients) + public NotificationOrderRequest(string? sendersReference, string creatorShortName, List templates, DateTime requestedSendTime, NotificationChannel notificationChannel, List recipients, bool ignoreReservation = false) { SendersReference = sendersReference; Creator = new(creatorShortName); @@ -49,6 +54,7 @@ public NotificationOrderRequest(string? sendersReference, string creatorShortNam RequestedSendTime = requestedSendTime; NotificationChannel = notificationChannel; Recipients = recipients; + IgnoreReservation = ignoreReservation; } /// diff --git a/src/Altinn.Notifications.Core/Models/Recipient.cs b/src/Altinn.Notifications.Core/Models/Recipient.cs index ab3ca0f0..92d85dfe 100644 --- a/src/Altinn.Notifications.Core/Models/Recipient.cs +++ b/src/Altinn.Notifications.Core/Models/Recipient.cs @@ -17,6 +17,11 @@ public class Recipient /// public string? NationalIdentityNumber { get; set; } = null; + /// + /// Gets or sets a value indicating whether the recipient is reserved from digital communication + /// + public bool IsReserved { get; set; } + /// /// Gets a list of address points for the recipient /// diff --git a/src/Altinn.Notifications.Core/Services/ContactPointService.cs b/src/Altinn.Notifications.Core/Services/ContactPointService.cs new file mode 100644 index 00000000..20275f22 --- /dev/null +++ b/src/Altinn.Notifications.Core/Services/ContactPointService.cs @@ -0,0 +1,100 @@ +using Altinn.Notifications.Core.Integrations; +using Altinn.Notifications.Core.Models; +using Altinn.Notifications.Core.Models.Address; +using Altinn.Notifications.Core.Models.ContactPoints; +using Altinn.Notifications.Core.Services.Interfaces; + +namespace Altinn.Notifications.Core.Services +{ + /// + /// Implementation of the using Altinn platform services to lookup contact points + /// + public class ContactPointService : IContactPointService + { + private readonly IProfileClient _profileClient; + + /// + /// Initializes a new instance of the class. + /// + public ContactPointService(IProfileClient profile) + { + _profileClient = profile; + } + + /// + public async Task> GetEmailContactPoints(List recipients) + { + return await AugmentRecipients( + recipients, + (recipient, userContactPoints) => + { + if (userContactPoints.IsReserved) + { + recipient.IsReserved = userContactPoints.IsReserved; + } + + recipient.AddressInfo.Add(new EmailAddressPoint(userContactPoints.Email)); + + return recipient; + }); + } + + /// + public async Task> GetSmsContactPoints(List recipients) + { + return await AugmentRecipients( + recipients, + (recipient, userContactPoints) => + { + if (userContactPoints.IsReserved) + { + recipient.IsReserved = userContactPoints.IsReserved; + } + + recipient.AddressInfo.Add(new SmsAddressPoint(userContactPoints.MobileNumber)); + + return recipient; + }); + } + + private async Task> AugmentRecipients(List recipients, Func createContactPoint) + { + List augmentedRecipients = []; + + List userContactPointsList = await LookupContactPoints(recipients); + foreach (Recipient recipient in recipients) + { + if (!string.IsNullOrEmpty(recipient.NationalIdentityNumber)) + { + UserContactPoints? userContactPoints = userContactPointsList! + .Find(u => u.NationalIdentityNumber == recipient.NationalIdentityNumber); + + if (userContactPointsList != null) + { + augmentedRecipients.Add(createContactPoint(recipient, userContactPoints!)); + } + } + } + + return augmentedRecipients; + } + + private async Task> LookupContactPoints(List recipients) + { + List nins = recipients + .Where(r => !string.IsNullOrEmpty(r.NationalIdentityNumber)) + .Select(r => r.NationalIdentityNumber!) + .ToList(); + + Task> ninLookupTask = nins.Count > 0 + ? _profileClient.GetUserContactPoints(nins) + : Task.FromResult(new List()); + + await Task.WhenAll(ninLookupTask); + + List userContactPoints = ninLookupTask.Result; + + return userContactPoints; + } + } +} diff --git a/src/Altinn.Notifications.Core/Services/EmailNotificationService.cs b/src/Altinn.Notifications.Core/Services/EmailNotificationService.cs index 9e055446..7209c0e3 100644 --- a/src/Altinn.Notifications.Core/Services/EmailNotificationService.cs +++ b/src/Altinn.Notifications.Core/Services/EmailNotificationService.cs @@ -1,4 +1,6 @@ -using Altinn.Notifications.Core.Configuration; +using System.ComponentModel.Design; + +using Altinn.Notifications.Core.Configuration; using Altinn.Notifications.Core.Enums; using Altinn.Notifications.Core.Integrations; using Altinn.Notifications.Core.Models; @@ -41,7 +43,7 @@ public EmailNotificationService( } /// - public async Task CreateNotification(Guid orderId, DateTime requestedSendTime, Recipient recipient) + public async Task CreateNotification(Guid orderId, DateTime requestedSendTime, Recipient recipient, bool ignoreReservation = false) { EmailAddressPoint? addressPoint = recipient.AddressInfo.Find(a => a.AddressType == AddressType.Email) as EmailAddressPoint; @@ -52,14 +54,19 @@ public async Task CreateNotification(Guid orderId, DateTime requestedSendTime, R ToAddress = addressPoint?.EmailAddress ?? string.Empty }; - if (!string.IsNullOrEmpty(addressPoint?.EmailAddress)) + if (recipient.IsReserved && !ignoreReservation) { - await CreateNotificationForRecipient(orderId, requestedSendTime, emailRecipient, EmailNotificationResultType.New); + emailRecipient.ToAddress = string.Empty; // not persisting email address for reserved recipients + await CreateNotificationForRecipient(orderId, requestedSendTime, emailRecipient, EmailNotificationResultType.Failed_RecipientReserved); + return; } - else + else if (string.IsNullOrEmpty(addressPoint?.EmailAddress)) { await CreateNotificationForRecipient(orderId, requestedSendTime, emailRecipient, EmailNotificationResultType.Failed_RecipientNotIdentified); + return; } + + await CreateNotificationForRecipient(orderId, requestedSendTime, emailRecipient, EmailNotificationResultType.New); } /// diff --git a/src/Altinn.Notifications.Core/Services/EmailNotificationSummaryService.cs b/src/Altinn.Notifications.Core/Services/EmailNotificationSummaryService.cs index cb2a1651..87c129f2 100644 --- a/src/Altinn.Notifications.Core/Services/EmailNotificationSummaryService.cs +++ b/src/Altinn.Notifications.Core/Services/EmailNotificationSummaryService.cs @@ -19,6 +19,7 @@ public class EmailNotificationSummaryService : IEmailNotificationSummaryService { EmailNotificationResultType.Succeeded, "The email has been accepted by the third party email service and will be sent shortly." }, { EmailNotificationResultType.Delivered, "The email was delivered to the recipient. No errors reported, making it likely it was received by the recipient." }, { EmailNotificationResultType.Failed, "The email was not sent due to an unspecified failure." }, + { EmailNotificationResultType.Failed_RecipientReserved, "The email was not sent because the recipient has reserved themselves from electronic communication." }, { EmailNotificationResultType.Failed_RecipientNotIdentified, "The email was not sent because the recipient's email address was not found." }, { EmailNotificationResultType.Failed_InvalidEmailFormat, "The email was not sent because the recipient’s email address is in an invalid format." }, { EmailNotificationResultType.Failed_SupressedRecipient, "The email was not sent because the recipient’s email address is suppressed by the third party email service." }, diff --git a/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs b/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs index dcbbbff8..9942d39e 100644 --- a/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs +++ b/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs @@ -15,24 +15,39 @@ public class EmailOrderProcessingService : IEmailOrderProcessingService { private readonly IEmailNotificationRepository _emailNotificationRepository; private readonly IEmailNotificationService _emailService; + private readonly IContactPointService _contactPointService; /// /// Initializes a new instance of the class. /// public EmailOrderProcessingService( IEmailNotificationRepository emailNotificationRepository, - IEmailNotificationService emailService) + IEmailNotificationService emailService, + IContactPointService contactPointService) { _emailNotificationRepository = emailNotificationRepository; _emailService = emailService; + _contactPointService = contactPointService; } /// public async Task ProcessOrder(NotificationOrder order) { - foreach (Recipient recipient in order.Recipients) + var recipients = order.Recipients; + var recipientsWithoutEmail = recipients.Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Email)).ToList(); + + List agumentedRecipients = await _contactPointService.GetEmailContactPoints(recipientsWithoutEmail); + + var augmentedRecipientDictionary = agumentedRecipients.ToDictionary(ar => $"{ar.NationalIdentityNumber}-{ar.OrganisationNumber}"); + + foreach (Recipient originalRecipient in recipients) { - await _emailService.CreateNotification(order.Id, order.RequestedSendTime, recipient); + if (augmentedRecipientDictionary.TryGetValue($"{originalRecipient.NationalIdentityNumber}-{originalRecipient.OrganisationNumber}", out Recipient? augmentedRecipient)) + { + originalRecipient.AddressInfo.AddRange(augmentedRecipient!.AddressInfo); + } + + await _emailService.CreateNotification(order.Id, order.RequestedSendTime, originalRecipient, order.IgnoreReservation); } } @@ -40,6 +55,7 @@ public async Task ProcessOrder(NotificationOrder order) 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; @@ -49,7 +65,7 @@ public async Task ProcessOrderRetry(NotificationOrder order) && er.OrganisationNumber == recipient.OrganisationNumber && er.ToAddress == addressPoint?.EmailAddress)) { - await _emailService.CreateNotification(order.Id, order.RequestedSendTime, recipient); + await _emailService.CreateNotification(order.Id, order.RequestedSendTime, recipient, order.IgnoreReservation); } } } diff --git a/src/Altinn.Notifications.Core/Services/Interfaces/IContactPointService.cs b/src/Altinn.Notifications.Core/Services/Interfaces/IContactPointService.cs new file mode 100644 index 00000000..fe6eaff6 --- /dev/null +++ b/src/Altinn.Notifications.Core/Services/Interfaces/IContactPointService.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Altinn.Notifications.Core.Models; + +namespace Altinn.Notifications.Core.Services.Interfaces +{ + /// + /// Service for retrieving contact points for recipients + /// + public interface IContactPointService + { + /// + /// Retrieves email contact points for recipients based on their national identity number or organisation number + /// + /// List of recipients to retrieve contact points for + /// The list of recipients augumented with email address points where available + public Task> GetEmailContactPoints(List recipients); + + /// + /// Retrieves SMS contact points for recipients based on their national identity number or organisation number + /// + /// List of recipients to retrieve contact points for + /// The list of recipients augumented with SMS address points where available + public Task> GetSmsContactPoints(List recipients); + } +} diff --git a/src/Altinn.Notifications.Core/Services/Interfaces/IEmailNotificationService.cs b/src/Altinn.Notifications.Core/Services/Interfaces/IEmailNotificationService.cs index 63aa456d..70f9b68a 100644 --- a/src/Altinn.Notifications.Core/Services/Interfaces/IEmailNotificationService.cs +++ b/src/Altinn.Notifications.Core/Services/Interfaces/IEmailNotificationService.cs @@ -11,7 +11,7 @@ public interface IEmailNotificationService /// /// Creates a new email notification based on the provided orderId and recipient /// - public Task CreateNotification(Guid orderId, DateTime requestedSendTime, Recipient recipient); + public Task CreateNotification(Guid orderId, DateTime requestedSendTime, Recipient recipient, bool ignoreReservation = false); /// /// Starts the process of sending all ready email notifications diff --git a/src/Altinn.Notifications.Core/Services/Interfaces/ISmsNotificationService.cs b/src/Altinn.Notifications.Core/Services/Interfaces/ISmsNotificationService.cs index 3007f06a..099c7550 100644 --- a/src/Altinn.Notifications.Core/Services/Interfaces/ISmsNotificationService.cs +++ b/src/Altinn.Notifications.Core/Services/Interfaces/ISmsNotificationService.cs @@ -11,7 +11,7 @@ public interface ISmsNotificationService /// /// Creates a new sms notification based on the provided orderId and recipient /// - public Task CreateNotification(Guid orderId, DateTime requestedSendTime, Recipient recipient, int smsCount); + public Task CreateNotification(Guid orderId, DateTime requestedSendTime, Recipient recipient, int smsCount, bool ignoreReservation = false); /// /// Starts the process of sending all ready sms notifications diff --git a/src/Altinn.Notifications.Core/Services/SmsNotificationService.cs b/src/Altinn.Notifications.Core/Services/SmsNotificationService.cs index 3e7662e8..f6ad8fd2 100644 --- a/src/Altinn.Notifications.Core/Services/SmsNotificationService.cs +++ b/src/Altinn.Notifications.Core/Services/SmsNotificationService.cs @@ -41,7 +41,7 @@ public SmsNotificationService( } /// - public async Task CreateNotification(Guid orderId, DateTime requestedSendTime, Recipient recipient, int smsCount) + public async Task CreateNotification(Guid orderId, DateTime requestedSendTime, Recipient recipient, int smsCount, bool ignoreReservation = false) { SmsAddressPoint? addressPoint = recipient.AddressInfo.Find(a => a.AddressType == AddressType.Sms) as SmsAddressPoint; @@ -52,14 +52,19 @@ public async Task CreateNotification(Guid orderId, DateTime requestedSendTime, R MobileNumber = addressPoint?.MobileNumber ?? string.Empty }; - if (!string.IsNullOrEmpty(addressPoint?.MobileNumber)) + if (recipient.IsReserved && !ignoreReservation) { - await CreateNotificationForRecipient(orderId, requestedSendTime, smsRecipient, SmsNotificationResultType.New, smsCount); + smsRecipient.MobileNumber = string.Empty; // not persisting mobile number for reserved recipient + await CreateNotificationForRecipient(orderId, requestedSendTime, smsRecipient, SmsNotificationResultType.Failed_RecipientReserved); + return; } - else + else if (string.IsNullOrEmpty(addressPoint?.MobileNumber)) { await CreateNotificationForRecipient(orderId, requestedSendTime, smsRecipient, SmsNotificationResultType.Failed_RecipientNotIdentified); + return; } + + await CreateNotificationForRecipient(orderId, requestedSendTime, smsRecipient, SmsNotificationResultType.New, smsCount); } /// diff --git a/src/Altinn.Notifications.Core/Services/SmsNotificationSummaryService.cs b/src/Altinn.Notifications.Core/Services/SmsNotificationSummaryService.cs index 7a0a50b7..2478a2ed 100644 --- a/src/Altinn.Notifications.Core/Services/SmsNotificationSummaryService.cs +++ b/src/Altinn.Notifications.Core/Services/SmsNotificationSummaryService.cs @@ -19,10 +19,11 @@ public class SmsNotificationSummaryService : ISmsNotificationSummaryService { SmsNotificationResultType.Accepted, "The SMS has been accepted by the gateway service and will be sent shortly." }, { SmsNotificationResultType.Delivered, "The SMS was successfully delivered to its destination." }, { SmsNotificationResultType.Failed, "The SMS was not delivered due to an unspecified failure." }, + { SmsNotificationResultType.Failed_InvalidRecipient, "The SMS was not delivered because the recipient's mobile number was invalid." }, + { SmsNotificationResultType.Failed_RecipientReserved, "The SMS was not sent because the recipient has reserved themselves from electronic communication." }, { SmsNotificationResultType.Failed_BarredReceiver, "The SMS was not delivered because the recipient's number is barred, blocked or not in use." }, - { SmsNotificationResultType.Failed_Deleted, "The SMS was not delivered because the message has been deleted." }, + { SmsNotificationResultType.Failed_Deleted, "The SMS was not delivered because the message has been deleted." }, { SmsNotificationResultType.Failed_Expired, "The SMS was not delivered because it has expired." }, - { SmsNotificationResultType.Failed_InvalidRecipient, "The SMS was not delivered because the recipient's mobile number was invalid." }, { SmsNotificationResultType.Failed_Undelivered, "The SMS was not delivered due to invalid number or no available route to destination." }, { SmsNotificationResultType.Failed_RecipientNotIdentified, "The SMS was not delivered because the recipient's mobile number was not found." }, { SmsNotificationResultType.Failed_Rejected, "The SMS was not delivered because it was rejected." }, diff --git a/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs b/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs index a9e3889d..457757c2 100644 --- a/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs +++ b/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs @@ -19,24 +19,37 @@ public class SmsOrderProcessingService : ISmsOrderProcessingService { private readonly ISmsNotificationRepository _smsNotificationRepository; private readonly ISmsNotificationService _smsService; + private readonly IContactPointService _contactPointService; /// /// Initializes a new instance of the class. /// - public SmsOrderProcessingService(ISmsNotificationRepository smsNotificationRepository, ISmsNotificationService smsService) + public SmsOrderProcessingService(ISmsNotificationRepository smsNotificationRepository, ISmsNotificationService smsService, IContactPointService contactPointService) { _smsNotificationRepository = smsNotificationRepository; _smsService = smsService; + _contactPointService = contactPointService; } /// public async Task ProcessOrder(NotificationOrder order) { + var recipients = order.Recipients; + var recipientsWithoutMobileNumber = recipients.Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Sms)).ToList(); + + List agumentedRecipients = await _contactPointService.GetSmsContactPoints(recipientsWithoutMobileNumber); + + var augmentedRecipientDictionary = agumentedRecipients.ToDictionary(ar => $"{ar.NationalIdentityNumber}-{ar.OrganisationNumber}"); int smsCount = GetSmsCountForOrder(order); - foreach (Recipient recipient in order.Recipients) + foreach (Recipient originalRecipient in recipients) { - await _smsService.CreateNotification(order.Id, order.RequestedSendTime, recipient, smsCount); + if (augmentedRecipientDictionary.TryGetValue($"{originalRecipient.NationalIdentityNumber}-{originalRecipient.OrganisationNumber}", out Recipient? augmentedRecipient)) + { + originalRecipient.AddressInfo.AddRange(augmentedRecipient!.AddressInfo); + } + + await _smsService.CreateNotification(order.Id, order.RequestedSendTime, originalRecipient, smsCount, order.IgnoreReservation); } } diff --git a/test/Altinn.Notifications.IntegrationTests/Utils/ServiceUtil.cs b/test/Altinn.Notifications.IntegrationTests/Utils/ServiceUtil.cs index 115e2a8c..0784f37f 100644 --- a/test/Altinn.Notifications.IntegrationTests/Utils/ServiceUtil.cs +++ b/test/Altinn.Notifications.IntegrationTests/Utils/ServiceUtil.cs @@ -37,6 +37,7 @@ public static List GetServices(List interfaceTypes, Dictionary outputServices = new(); diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingModels/NotificationOrderTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingModels/NotificationOrderTests.cs index 69f13232..10ce1f1f 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingModels/NotificationOrderTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingModels/NotificationOrderTests.cs @@ -47,6 +47,7 @@ public NotificationOrderTests() new Recipient() { NationalIdentityNumber = "nationalidentitynumber", + IsReserved = false, AddressInfo = new() { new EmailAddressPoint() @@ -94,6 +95,9 @@ public NotificationOrderTests() { "nationalIdentityNumber", "nationalidentitynumber" }, + { + "isReserved", false + }, { "addressInfo", new JsonArray() { @@ -105,8 +109,11 @@ public NotificationOrderTests() } } } - }, + } } + }, + { + "ignoreReservation", false } }.ToJsonString(); } diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationServiceTests.cs index 374ccd6c..5f74000c 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationServiceTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationServiceTests.cs @@ -109,7 +109,7 @@ public async Task CreateEmailNotification_ToAddressDefined_ResultNew() } [Fact] - public async Task CreateEmailNotification_ToAddressMissing_ResultFailedRecipientNotDefined() + public async Task CreateEmailNotification_ToAddressMissing_LookupFails_ResultFailedRecipientNotDefined() { // Arrange Guid id = Guid.NewGuid(); diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationSummaryServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationSummaryServiceTests.cs index 4cee6feb..c03590e0 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationSummaryServiceTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationSummaryServiceTests.cs @@ -29,8 +29,11 @@ public void IsSuccessResult_CheckResultForAllEnums(EmailNotificationResultType r [InlineData(EmailNotificationResultType.Succeeded, "The email has been accepted by the third party email service and will be sent shortly.")] [InlineData(EmailNotificationResultType.Delivered, "The email was delivered to the recipient. No errors reported, making it likely it was received by the recipient.")] [InlineData(EmailNotificationResultType.Failed, "The email was not sent due to an unspecified failure.")] + [InlineData(EmailNotificationResultType.Failed_RecipientReserved, "The email was not sent because the recipient has reserved themselves from electronic communication.")] [InlineData(EmailNotificationResultType.Failed_RecipientNotIdentified, "The email was not sent because the recipient's email address was not found.")] [InlineData(EmailNotificationResultType.Failed_InvalidEmailFormat, "The email was not sent because the recipient’s email address is in an invalid format.")] + [InlineData(EmailNotificationResultType.Failed_SupressedRecipient, "The email was not sent because the recipient’s email address is suppressed by the third party email service.")] + [InlineData(EmailNotificationResultType.Failed_TransientError, "The email was not sent due to a transient error. We will retry sending the email.")] [InlineData(EmailNotificationResultType.Failed_Bounced, "The email hard bounced, which may have happened because the email address does not exist or the domain is invalid.")] [InlineData(EmailNotificationResultType.Failed_FilteredSpam, "The email was was identified as spam, and was rejected or blocked (not quarantined).")] [InlineData(EmailNotificationResultType.Failed_Quarantined, "The email was quarantined (as spam, bulk mail, or phising).")] diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailOrderProcessingServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailOrderProcessingServiceTests.cs index 6df242e6..889793ce 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailOrderProcessingServiceTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailOrderProcessingServiceTests.cs @@ -29,13 +29,21 @@ public async Task ProcessOrder_ServiceCalledOnceForEachRecipient() NotificationChannel = NotificationChannel.Email, Recipients = new List() { - new(), new() + { + OrganisationNumber = "123456", + AddressInfo = [new EmailAddressPoint("email@test.com")] + }, + new() + { + OrganisationNumber = "654321", + AddressInfo = [new EmailAddressPoint("email@test.com")] + } } }; var serviceMock = new Mock(); - serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny())); + serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); var service = GetTestService(emailService: serviceMock.Object); @@ -43,7 +51,7 @@ public async Task ProcessOrder_ServiceCalledOnceForEachRecipient() await service.ProcessOrder(order); // Assert - serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); } [Fact] @@ -67,7 +75,7 @@ public async Task ProcessOrder_ExpectedInputToService() Recipient expectedRecipient = new(new List() { new EmailAddressPoint("test@test.com") }, organisationNumber: "skd-orgno"); var serviceMock = new Mock(); - serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.Is(d => d.Equals(requested)), It.Is(r => AssertUtils.AreEquivalent(expectedRecipient, r)))); + serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.Is(d => d.Equals(requested)), It.Is(r => AssertUtils.AreEquivalent(expectedRecipient, r)), It.IsAny())); var service = GetTestService(emailService: serviceMock.Object); @@ -92,7 +100,7 @@ public async Task ProcessOrder_ServiceThrowsException_RepositoryNotCalled() }; var serviceMock = new Mock(); - serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny())) + serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ThrowsAsync(new Exception()); var repoMock = new Mock(); @@ -104,7 +112,7 @@ public async Task ProcessOrder_ServiceThrowsException_RepositoryNotCalled() await Assert.ThrowsAsync(async () => await service.ProcessOrder(order)); // Assert - serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); repoMock.Verify(r => r.SetProcessingStatus(It.IsAny(), It.IsAny()), Times.Never); } @@ -126,7 +134,7 @@ public async Task ProcessOrderRetry_ServiceCalledIfRecipientNotInDatabase() }; var serviceMock = new Mock(); - serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny())); + serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); var emailRepoMock = new Mock(); emailRepoMock.Setup(e => e.GetRecipients(It.IsAny())).ReturnsAsync(new List() @@ -142,12 +150,13 @@ public async Task ProcessOrderRetry_ServiceCalledIfRecipientNotInDatabase() // Assert emailRepoMock.Verify(e => e.GetRecipients(It.IsAny()), Times.Once); - serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); } private static EmailOrderProcessingService GetTestService( IEmailNotificationRepository? emailRepo = null, - IEmailNotificationService? emailService = null) + IEmailNotificationService? emailService = null, + IContactPointService? contactPointService = null) { if (emailRepo == null) { @@ -161,6 +170,16 @@ private static EmailOrderProcessingService GetTestService( emailService = emailServiceMock.Object; } - return new EmailOrderProcessingService(emailRepo, emailService); + if (contactPointService == null) + { + var contactPointServiceMock = new Mock(); + contactPointServiceMock + .Setup(e => e.GetEmailContactPoints(It.IsAny>())) + .ReturnsAsync( + (List recipients) => recipients); + contactPointService = contactPointServiceMock.Object; + } + + return new EmailOrderProcessingService(emailRepo, emailService, contactPointService); } } diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsNotificationServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsNotificationServiceTests.cs index ab063d34..35e46aa7 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsNotificationServiceTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsNotificationServiceTests.cs @@ -84,7 +84,7 @@ public async Task CreateNotification_RecipientNumberIsDefined_ResultNew() } [Fact] - public async Task CreateNotification_RecipientNumberMissing_ResultFailedRecipientNotDefined() + public async Task CreateNotification_RecipientNumberMissing_LookupFails_ResultFailedRecipientNotDefined() { // Arrange Guid id = Guid.NewGuid(); diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsNotificationSummaryServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsNotificationSummaryServiceTests.cs index 9307f449..c28710be 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsNotificationSummaryServiceTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsNotificationSummaryServiceTests.cs @@ -34,10 +34,11 @@ public void IsSuccessResult_CheckResultForAllEnums(SmsNotificationResultType res [InlineData(SmsNotificationResultType.Accepted, "The SMS has been accepted by the gateway service and will be sent shortly.")] [InlineData(SmsNotificationResultType.Delivered, "The SMS was successfully delivered to its destination.")] [InlineData(SmsNotificationResultType.Failed, "The SMS was not delivered due to an unspecified failure.")] + [InlineData(SmsNotificationResultType.Failed_InvalidRecipient, "The SMS was not delivered because the recipient's mobile number was invalid.")] + [InlineData(SmsNotificationResultType.Failed_RecipientReserved, "The SMS was not sent because the recipient has reserved themselves from electronic communication.")] [InlineData(SmsNotificationResultType.Failed_BarredReceiver, "The SMS was not delivered because the recipient's number is barred, blocked or not in use.")] [InlineData(SmsNotificationResultType.Failed_Deleted, "The SMS was not delivered because the message has been deleted.")] [InlineData(SmsNotificationResultType.Failed_Expired, "The SMS was not delivered because it has expired.")] - [InlineData(SmsNotificationResultType.Failed_InvalidRecipient, "The SMS was not delivered because the recipient's mobile number was invalid.")] [InlineData(SmsNotificationResultType.Failed_Undelivered, "The SMS was not delivered due to invalid number or no available route to destination.")] [InlineData(SmsNotificationResultType.Failed_RecipientNotIdentified, "The SMS was not delivered because the recipient's mobile number was not found.")] [InlineData(SmsNotificationResultType.Failed_Rejected, "The SMS was not delivered because it was rejected.")] diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsOrderProcessingServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsOrderProcessingServiceTests.cs index 8db3ff62..36cd287c 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsOrderProcessingServiceTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsOrderProcessingServiceTests.cs @@ -44,7 +44,7 @@ public async Task ProcessOrder_ExpectedInputToService() Recipient expectedRecipient = new(new List() { new SmsAddressPoint("+4799999999") }, nationalIdentityNumber: "enduser-nin"); var serviceMock = new Mock(); - serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.Is(d => d.Equals(requested)), It.Is(r => AssertUtils.AreEquivalent(expectedRecipient, r)), It.IsAny())); + serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.Is(d => d.Equals(requested)), It.Is(r => AssertUtils.AreEquivalent(expectedRecipient, r)), It.IsAny(), It.IsAny())); var service = GetTestService(smsService: serviceMock.Object); @@ -65,14 +65,22 @@ public async Task ProcessOrder_ServiceCalledOnceForEachRecipient() NotificationChannel = NotificationChannel.Sms, Recipients = new List() { - new(), new() + { + OrganisationNumber = "123456", + AddressInfo = [new SmsAddressPoint("+4799999999")] + }, + new() + { + OrganisationNumber = "654321", + AddressInfo = [new SmsAddressPoint("+4799999999")] + } }, Templates = [new SmsTemplate("Altinn", "this is the body")] }; var serviceMock = new Mock(); - serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); + serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); var service = GetTestService(smsService: serviceMock.Object); @@ -80,7 +88,7 @@ public async Task ProcessOrder_ServiceCalledOnceForEachRecipient() await service.ProcessOrder(order); // Assert - serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); } [Fact] @@ -102,15 +110,14 @@ public async Task ProcessOrderRetry_ServiceCalledIfRecipientNotInDatabase() }; var serviceMock = new Mock(); - serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); + serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); var smsRepoMock = new Mock(); smsRepoMock.Setup(e => e.GetRecipients(It.IsAny())).ReturnsAsync( - new List() - { + [ new SmsRecipient() { NationalIdentityNumber = "enduser-nin", MobileNumber = "+4799999999" }, new SmsRecipient() { OrganisationNumber = "skd-orgNo", MobileNumber = "+4799999999" } - }); + ]); var service = GetTestService(smsRepo: smsRepoMock.Object, smsService: serviceMock.Object); @@ -119,7 +126,7 @@ public async Task ProcessOrderRetry_ServiceCalledIfRecipientNotInDatabase() // Assert smsRepoMock.Verify(e => e.GetRecipients(It.IsAny()), Times.Once); - serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); } [Theory] @@ -141,7 +148,8 @@ public void CalculateNumberOfMessages_MessageWithSymbolsAreEncodedBeforeCalculat private static SmsOrderProcessingService GetTestService( ISmsNotificationRepository? smsRepo = null, - ISmsNotificationService? smsService = null) + ISmsNotificationService? smsService = null, + IContactPointService? contactPointService = null) { if (smsRepo == null) { @@ -155,6 +163,17 @@ private static SmsOrderProcessingService GetTestService( smsService = smsServiceMock.Object; } - return new SmsOrderProcessingService(smsRepo, smsService); + if (contactPointService == null) + { + var contactPointServiceMock = new Mock(); + contactPointServiceMock + .Setup(e => e.GetSmsContactPoints(It.IsAny>())) + .ReturnsAsync( + (List recipients) => recipients); + + contactPointService = contactPointServiceMock.Object; + } + + return new SmsOrderProcessingService(smsRepo, smsService, contactPointService); } } From 6bfbcc41c904cf2cbf90a0267fd0fcd29f5b5f5d Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Wed, 13 Mar 2024 15:42:41 +0100 Subject: [PATCH 11/22] removed obsolete code --- .../Services/EmailNotificationService.cs | 4 +--- .../Configuration/AltinnServiceSettings.cs | 12 ------------ 2 files changed, 1 insertion(+), 15 deletions(-) delete mode 100644 src/Altinn.Notifications.Integrations/Configuration/AltinnServiceSettings.cs diff --git a/src/Altinn.Notifications.Core/Services/EmailNotificationService.cs b/src/Altinn.Notifications.Core/Services/EmailNotificationService.cs index 7209c0e3..071ca262 100644 --- a/src/Altinn.Notifications.Core/Services/EmailNotificationService.cs +++ b/src/Altinn.Notifications.Core/Services/EmailNotificationService.cs @@ -1,6 +1,4 @@ -using System.ComponentModel.Design; - -using Altinn.Notifications.Core.Configuration; +using Altinn.Notifications.Core.Configuration; using Altinn.Notifications.Core.Enums; using Altinn.Notifications.Core.Integrations; using Altinn.Notifications.Core.Models; diff --git a/src/Altinn.Notifications.Integrations/Configuration/AltinnServiceSettings.cs b/src/Altinn.Notifications.Integrations/Configuration/AltinnServiceSettings.cs deleted file mode 100644 index efc8de1d..00000000 --- a/src/Altinn.Notifications.Integrations/Configuration/AltinnServiceSettings.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Altinn.Notifications.Integrations.Configuration; - -/// -/// Configuration object used to hold settings for all Altinn integrations. -/// -public class AltinnServiceSettings -{ - /// - /// Gets or sets the url for the API profile endpoint - /// - public string ApiProfileEndpoint { get; set; } = string.Empty; -} From ee8cf43c8f8bab4da898e93f0730407fdc65f501 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Wed, 13 Mar 2024 16:34:49 +0100 Subject: [PATCH 12/22] fixed code smell --- .../Services/ContactPointService.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Altinn.Notifications.Core/Services/ContactPointService.cs b/src/Altinn.Notifications.Core/Services/ContactPointService.cs index 20275f22..088092b5 100644 --- a/src/Altinn.Notifications.Core/Services/ContactPointService.cs +++ b/src/Altinn.Notifications.Core/Services/ContactPointService.cs @@ -69,10 +69,7 @@ private async Task> AugmentRecipients(List recipients UserContactPoints? userContactPoints = userContactPointsList! .Find(u => u.NationalIdentityNumber == recipient.NationalIdentityNumber); - if (userContactPointsList != null) - { - augmentedRecipients.Add(createContactPoint(recipient, userContactPoints!)); - } + augmentedRecipients.Add(createContactPoint(recipient, userContactPoints!)); } } From 74b7108c0fd9fca873c89f8610f884dcf4198290 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Wed, 13 Mar 2024 16:38:55 +0100 Subject: [PATCH 13/22] added order processing unit tests --- .../EmailOrderProcessingServiceTests.cs | 51 ++++++++++++- .../SmsOrderProcessingServiceTests.cs | 75 +++++++++++++++---- 2 files changed, 108 insertions(+), 18 deletions(-) diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailOrderProcessingServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailOrderProcessingServiceTests.cs index 889793ce..62c0d1e6 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailOrderProcessingServiceTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailOrderProcessingServiceTests.cs @@ -5,6 +5,7 @@ using Altinn.Notifications.Core.Enums; using Altinn.Notifications.Core.Models; using Altinn.Notifications.Core.Models.Address; +using Altinn.Notifications.Core.Models.NotificationTemplate; using Altinn.Notifications.Core.Models.Orders; using Altinn.Notifications.Core.Models.Recipients; using Altinn.Notifications.Core.Persistence; @@ -20,7 +21,7 @@ namespace Altinn.Notifications.Tests.Notifications.Core.TestingServices; public class EmailOrderProcessingServiceTests { [Fact] - public async Task ProcessOrder_ServiceCalledOnceForEachRecipient() + public async Task ProcessOrder_NotificationServiceCalledOnceForEachRecipient() { // Arrange var order = new NotificationOrder() @@ -55,7 +56,7 @@ public async Task ProcessOrder_ServiceCalledOnceForEachRecipient() } [Fact] - public async Task ProcessOrder_ExpectedInputToService() + public async Task ProcessOrder_ExpectedInputToNotificationService() { // Arrange DateTime requested = DateTime.UtcNow; @@ -87,7 +88,7 @@ public async Task ProcessOrder_ExpectedInputToService() } [Fact] - public async Task ProcessOrder_ServiceThrowsException_RepositoryNotCalled() + public async Task ProcessOrder_NotificationServiceThrowsException_RepositoryNotCalled() { // Arrange var order = new NotificationOrder() @@ -116,6 +117,50 @@ public async Task ProcessOrder_ServiceThrowsException_RepositoryNotCalled() repoMock.Verify(r => r.SetProcessingStatus(It.IsAny(), It.IsAny()), Times.Never); } + [Fact] + public async Task ProcessOrder_RecipientMissingEmail_ContactPointServiceCalled() + { + // Arrange + var order = new NotificationOrder() + { + Id = Guid.NewGuid(), + NotificationChannel = NotificationChannel.Sms, + Recipients = new List() + { + new() + { + NationalIdentityNumber = "123456", + } + }, + Templates = [new EmailTemplate(null, "subject", "body", EmailContentType.Plain)] + }; + + var notificationServiceMock = new Mock(); + notificationServiceMock.Setup( + s => s.CreateNotification( + It.IsAny(), + It.IsAny(), + It.Is(r => (r.NationalIdentityNumber == "123456" && r.AddressInfo.Count == 1)), + It.IsAny())); + + var contactPointServiceMock = new Mock(); + contactPointServiceMock.Setup(c => c.GetEmailContactPoints(It.Is>(r => r.Count == 1))) + .ReturnsAsync((List r) => + { + Recipient augumentedRecipient = new() { AddressInfo = [new EmailAddressPoint("test@test.com")], NationalIdentityNumber = r[0].NationalIdentityNumber }; + return new List() { augumentedRecipient }; + }); + + var service = GetTestService(emailService: notificationServiceMock.Object, contactPointService: contactPointServiceMock.Object); + + // Act + await service.ProcessOrder(order); + + // Assert + contactPointServiceMock.Verify(c => c.GetEmailContactPoints(It.Is>(r => r.Count == 1)), Times.Once); + notificationServiceMock.VerifyAll(); + } + [Fact] public async Task ProcessOrderRetry_ServiceCalledIfRecipientNotInDatabase() { diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsOrderProcessingServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsOrderProcessingServiceTests.cs index 36cd287c..2fcef88b 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsOrderProcessingServiceTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsOrderProcessingServiceTests.cs @@ -43,20 +43,20 @@ public async Task ProcessOrder_ExpectedInputToService() Recipient expectedRecipient = new(new List() { new SmsAddressPoint("+4799999999") }, nationalIdentityNumber: "enduser-nin"); - var serviceMock = new Mock(); - serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.Is(d => d.Equals(requested)), It.Is(r => AssertUtils.AreEquivalent(expectedRecipient, r)), It.IsAny(), It.IsAny())); + var notificationServiceMock = new Mock(); + notificationServiceMock.Setup(s => s.CreateNotification(It.IsAny(), It.Is(d => d.Equals(requested)), It.Is(r => AssertUtils.AreEquivalent(expectedRecipient, r)), It.IsAny(), It.IsAny())); - var service = GetTestService(smsService: serviceMock.Object); + var service = GetTestService(smsService: notificationServiceMock.Object); // Act await service.ProcessOrder(order); // Assert - serviceMock.VerifyAll(); + notificationServiceMock.VerifyAll(); } [Fact] - public async Task ProcessOrder_ServiceCalledOnceForEachRecipient() + public async Task ProcessOrder_NotificationServiceCalledOnceForEachRecipient() { // Arrange var order = new NotificationOrder() @@ -73,26 +73,71 @@ public async Task ProcessOrder_ServiceCalledOnceForEachRecipient() new() { OrganisationNumber = "654321", - AddressInfo = [new SmsAddressPoint("+4799999999")] + AddressInfo = [new SmsAddressPoint("+4799999999")] + } + }, + Templates = [new SmsTemplate("Altinn", "this is the body")] + }; + + var notificationServiceMock = new Mock(); + notificationServiceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); + + var service = GetTestService(smsService: notificationServiceMock.Object); + + // Act + await service.ProcessOrder(order); + + // Assert + notificationServiceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + } + + [Fact] + public async Task ProcessOrder_RecipientMissingMobileNumber_ContactPointServiceCalled() + { + // Arrange + var order = new NotificationOrder() + { + Id = Guid.NewGuid(), + NotificationChannel = NotificationChannel.Sms, + Recipients = new List() + { + new() + { + NationalIdentityNumber = "123456", } }, Templates = [new SmsTemplate("Altinn", "this is the body")] }; - var serviceMock = new Mock(); - serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); + var notificationServiceMock = new Mock(); + notificationServiceMock.Setup( + s => s.CreateNotification( + It.IsAny(), + It.IsAny(), + It.Is(r => (r.NationalIdentityNumber == "123456" && r.AddressInfo.Count == 1)), + It.IsAny(), + It.IsAny())); + + var contactPointServiceMock = new Mock(); + contactPointServiceMock.Setup(c => c.GetSmsContactPoints(It.Is>(r => r.Count == 1))) + .ReturnsAsync((List r) => + { + Recipient augumentedRecipient = new() { AddressInfo = [new SmsAddressPoint("+4712345678")], NationalIdentityNumber = r[0].NationalIdentityNumber }; + return new List() { augumentedRecipient }; + }); - var service = GetTestService(smsService: serviceMock.Object); + var service = GetTestService(smsService: notificationServiceMock.Object, contactPointService: contactPointServiceMock.Object); // Act await service.ProcessOrder(order); // Assert - serviceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + contactPointServiceMock.Verify(c => c.GetSmsContactPoints(It.Is>(r => r.Count == 1)), Times.Once); + notificationServiceMock.VerifyAll(); } [Fact] - public async Task ProcessOrderRetry_ServiceCalledIfRecipientNotInDatabase() + public async Task ProcessOrderRetry_NotificationServiceCalledIfRecipientNotInDatabase() { // Arrange var order = new NotificationOrder() @@ -109,8 +154,8 @@ public async Task ProcessOrderRetry_ServiceCalledIfRecipientNotInDatabase() Templates = [new SmsTemplate("Altinn", "this is the body")] }; - var serviceMock = new Mock(); - serviceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); + var notificationServiceMock = new Mock(); + notificationServiceMock.Setup(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); var smsRepoMock = new Mock(); smsRepoMock.Setup(e => e.GetRecipients(It.IsAny())).ReturnsAsync( @@ -119,14 +164,14 @@ public async Task ProcessOrderRetry_ServiceCalledIfRecipientNotInDatabase() new SmsRecipient() { OrganisationNumber = "skd-orgNo", MobileNumber = "+4799999999" } ]); - var service = GetTestService(smsRepo: smsRepoMock.Object, smsService: serviceMock.Object); + var service = GetTestService(smsRepo: smsRepoMock.Object, smsService: notificationServiceMock.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(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + notificationServiceMock.Verify(s => s.CreateNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); } [Theory] From dee5144ea8aa6294c5c448856598cd6a6cb2131c Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Wed, 13 Mar 2024 17:17:44 +0100 Subject: [PATCH 14/22] added unit test for notification services --- .../Models/Recipients/EmailRecipient.cs | 5 ++ .../Models/Recipients/SmsRecipient.cs | 5 ++ .../Services/EmailNotificationService.cs | 3 +- .../Services/SmsNotificationService.cs | 3 +- .../EmailNotificationServiceTests.cs | 81 +++++++++++++++++-- .../SmsNotificationServiceTests.cs | 69 ++++++++++++++++ 6 files changed, 158 insertions(+), 8 deletions(-) diff --git a/src/Altinn.Notifications.Core/Models/Recipients/EmailRecipient.cs b/src/Altinn.Notifications.Core/Models/Recipients/EmailRecipient.cs index 82d9477b..d4b7cb1e 100644 --- a/src/Altinn.Notifications.Core/Models/Recipients/EmailRecipient.cs +++ b/src/Altinn.Notifications.Core/Models/Recipients/EmailRecipient.cs @@ -19,4 +19,9 @@ public class EmailRecipient /// Gets or sets the toaddress /// public string ToAddress { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether the recipient is reserved from digital communication + /// + public bool IsReserved { get; set; } } diff --git a/src/Altinn.Notifications.Core/Models/Recipients/SmsRecipient.cs b/src/Altinn.Notifications.Core/Models/Recipients/SmsRecipient.cs index e70c7976..f0764a5c 100644 --- a/src/Altinn.Notifications.Core/Models/Recipients/SmsRecipient.cs +++ b/src/Altinn.Notifications.Core/Models/Recipients/SmsRecipient.cs @@ -19,4 +19,9 @@ public class SmsRecipient /// Gets or sets the mobile number /// public string MobileNumber { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether the recipient is reserved from digital communication + /// + public bool IsReserved { get; set; } } diff --git a/src/Altinn.Notifications.Core/Services/EmailNotificationService.cs b/src/Altinn.Notifications.Core/Services/EmailNotificationService.cs index 071ca262..192330e5 100644 --- a/src/Altinn.Notifications.Core/Services/EmailNotificationService.cs +++ b/src/Altinn.Notifications.Core/Services/EmailNotificationService.cs @@ -49,7 +49,8 @@ public async Task CreateNotification(Guid orderId, DateTime requestedSendTime, R { OrganisationNumber = recipient.OrganisationNumber, NationalIdentityNumber = recipient.NationalIdentityNumber, - ToAddress = addressPoint?.EmailAddress ?? string.Empty + ToAddress = addressPoint?.EmailAddress ?? string.Empty, + IsReserved = recipient.IsReserved }; if (recipient.IsReserved && !ignoreReservation) diff --git a/src/Altinn.Notifications.Core/Services/SmsNotificationService.cs b/src/Altinn.Notifications.Core/Services/SmsNotificationService.cs index f6ad8fd2..03f1f0f3 100644 --- a/src/Altinn.Notifications.Core/Services/SmsNotificationService.cs +++ b/src/Altinn.Notifications.Core/Services/SmsNotificationService.cs @@ -49,7 +49,8 @@ public async Task CreateNotification(Guid orderId, DateTime requestedSendTime, R { OrganisationNumber = recipient.OrganisationNumber, NationalIdentityNumber = recipient.NationalIdentityNumber, - MobileNumber = addressPoint?.MobileNumber ?? string.Empty + MobileNumber = addressPoint?.MobileNumber ?? string.Empty, + IsReserved = recipient.IsReserved }; if (recipient.IsReserved && !ignoreReservation) diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationServiceTests.cs index 5f74000c..6e5cf6aa 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationServiceTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationServiceTests.cs @@ -74,7 +74,7 @@ public async Task SendNotifications_ProducerReturnsFalse_RepositoryCalledToUpdat } [Fact] - public async Task CreateEmailNotification_ToAddressDefined_ResultNew() + public async Task CreateNotification_ToAddressDefined_ResultNew() { // Arrange Guid id = Guid.NewGuid(); @@ -93,7 +93,7 @@ public async Task CreateEmailNotification_ToAddressDefined_ResultNew() ToAddress = "skd@norge.no" }, RequestedSendTime = requestedSendTime, - SendResult = new(EmailNotificationResultType.New, dateTimeOutput) + SendResult = new(EmailNotificationResultType.New, dateTimeOutput) }; var repoMock = new Mock(); @@ -109,7 +109,76 @@ public async Task CreateEmailNotification_ToAddressDefined_ResultNew() } [Fact] - public async Task CreateEmailNotification_ToAddressMissing_LookupFails_ResultFailedRecipientNotDefined() + public async Task CreateNotification_RecipientIsReserved_IgnoreReservationsFalse_ResultFailedRecipientReserved() + { + // Arrange + Guid id = Guid.NewGuid(); + Guid orderId = Guid.NewGuid(); + DateTime requestedSendTime = DateTime.UtcNow; + DateTime dateTimeOutput = DateTime.UtcNow; + DateTime expectedExpiry = requestedSendTime.AddHours(1); + + EmailNotification expected = new() + { + Id = id, + OrderId = orderId, + Recipient = new() + { + IsReserved = true + }, + RequestedSendTime = requestedSendTime, + SendResult = new(EmailNotificationResultType.Failed_RecipientReserved, 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() { IsReserved = true }); + + // 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_RecipientIsReserved_IgnoreReservationsTrue_ResultNew() + { + // Arrange + Guid id = Guid.NewGuid(); + Guid orderId = Guid.NewGuid(); + DateTime requestedSendTime = DateTime.UtcNow; + DateTime dateTimeOutput = DateTime.UtcNow; + DateTime expectedExpiry = requestedSendTime.AddHours(1); + + EmailNotification expected = new() + { + Id = id, + OrderId = orderId, + Recipient = new() + { + IsReserved = true, + ToAddress = "email@domain.com" + }, + RequestedSendTime = requestedSendTime, + SendResult = new(EmailNotificationResultType.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() { IsReserved = true, AddressInfo = [new EmailAddressPoint("email@domain.com")] }, true); + + // 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_ToAddressMissing_LookupFails_ResultFailedRecipientNotDefined() { // Arrange Guid id = Guid.NewGuid(); @@ -150,7 +219,7 @@ public async Task UpdateSendStatus_SendResultDefined_Succeded() string operationId = Guid.NewGuid().ToString(); EmailSendOperationResult sendOperationResult = new() - { + { NotificationId = notificationid, OperationId = operationId, SendResult = EmailNotificationResultType.Succeeded @@ -184,8 +253,8 @@ public async Task UpdateSendStatus_TransientErrorResult_ConvertedToNew() var repoMock = new Mock(); repoMock.Setup(r => r.UpdateSendStatus( - It.Is(n => n == notificationid), - It.Is(e => e == EmailNotificationResultType.New), + It.Is(n => n == notificationid), + It.Is(e => e == EmailNotificationResultType.New), It.Is(s => s.Equals(operationId)))); var service = GetTestService(repo: repoMock.Object); diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsNotificationServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsNotificationServiceTests.cs index 35e46aa7..677e34e3 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsNotificationServiceTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsNotificationServiceTests.cs @@ -83,6 +83,75 @@ public async Task CreateNotification_RecipientNumberIsDefined_ResultNew() repoMock.Verify(r => r.AddNotification(It.Is(e => AssertUtils.AreEquivalent(expected, e)), It.Is(d => d == expectedExpiry), It.IsAny()), Times.Once); } + [Fact] + public async Task CreateNotification_RecipientIsReserved_IgnoreReservationsFalse_ResultFailedRecipientReserved() + { + // 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, + Recipient = new() + { + IsReserved = true + }, + SendResult = new(SmsNotificationResultType.Failed_RecipientReserved, dateTimeOutput), + }; + + var repoMock = new Mock(); + repoMock.Setup(r => r.AddNotification(It.Is(e => AssertUtils.AreEquivalent(expected, e)), It.Is(d => d == expectedExpiry), It.IsAny())); + + var service = GetTestService(repo: repoMock.Object, guidOutput: id, dateTimeOutput: dateTimeOutput); + + // Act + await service.CreateNotification(orderId, requestedSendTime, new Recipient() { IsReserved = true }, 1); + + // Assert + repoMock.Verify(r => r.AddNotification(It.Is(e => AssertUtils.AreEquivalent(expected, e)), It.Is(d => d == expectedExpiry), It.IsAny()), Times.Once); + } + + [Fact] + public async Task CreateNotification_RecipientIsReserved_IgnoreReservationsTrue_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, + Recipient = new() + { + IsReserved = true, + MobileNumber = "+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), It.IsAny())); + + var service = GetTestService(repo: repoMock.Object, guidOutput: id, dateTimeOutput: dateTimeOutput); + + // Act + await service.CreateNotification(orderId, requestedSendTime, new Recipient() { IsReserved = true, AddressInfo = [new SmsAddressPoint("+4799999999")] }, 1, true); + + // Assert + repoMock.Verify(r => r.AddNotification(It.Is(e => AssertUtils.AreEquivalent(expected, e)), It.Is(d => d == expectedExpiry), It.IsAny()), Times.Once); + } + [Fact] public async Task CreateNotification_RecipientNumberMissing_LookupFails_ResultFailedRecipientNotDefined() { From 7c99cd60b5ea4f49b58efa1ce4246153c9688474 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Wed, 13 Mar 2024 17:48:16 +0100 Subject: [PATCH 15/22] added contactpoint service tests --- .../Services/ContactPointService.cs | 2 +- .../ContactPointServiceTests.cs | 97 +++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs diff --git a/src/Altinn.Notifications.Core/Services/ContactPointService.cs b/src/Altinn.Notifications.Core/Services/ContactPointService.cs index 088092b5..14b46f8d 100644 --- a/src/Altinn.Notifications.Core/Services/ContactPointService.cs +++ b/src/Altinn.Notifications.Core/Services/ContactPointService.cs @@ -80,7 +80,7 @@ private async Task> LookupContactPoints(List { List nins = recipients .Where(r => !string.IsNullOrEmpty(r.NationalIdentityNumber)) - .Select(r => r.NationalIdentityNumber!) + .Select(r => r.NationalIdentityNumber!) .ToList(); Task> ninLookupTask = nins.Count > 0 diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs new file mode 100644 index 00000000..45ddbca5 --- /dev/null +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +using Altinn.Notifications.Core.Integrations; +using Altinn.Notifications.Core.Models; +using Altinn.Notifications.Core.Models.Address; +using Altinn.Notifications.Core.Models.ContactPoints; +using Altinn.Notifications.Core.Services; + +using Moq; + +using Xunit; + +namespace Altinn.Notifications.Tests.Notifications.Core.TestingServices +{ + public class ContactPointServiceTests + { + [Fact] + public async Task GetSmsContactPoints_NationalIdentityNumberAvailable_ProfileServiceCalled() + { + // Arrange + List input = [ + new Recipient() + { + NationalIdentityNumber = "12345678901" + } + ]; + + List expectedOutput = [ + new Recipient() + { + NationalIdentityNumber = "12345678901", + IsReserved = true, + AddressInfo = [new SmsAddressPoint("+4799999999")] + } + ]; + + var profileClientMock = new Mock(); + profileClientMock + .Setup(p => p.GetUserContactPoints(It.Is>(s => s.Contains("12345678901")))) + .ReturnsAsync([new UserContactPoints() { NationalIdentityNumber = "12345678901", MobileNumber = "+4799999999", IsReserved = true }]); + + var service = GetTestService(profileClient: profileClientMock.Object); + + // Act + List actual = await service.GetSmsContactPoints(input); + + // Assert + Assert.Equivalent(expectedOutput, actual); + } + + [Fact] + public async Task GetEmailContactPoints_NationalIdentityNumberAvailable_ProfileServiceCalled() + { + // Arrange + List input = [ + new Recipient() + { + NationalIdentityNumber = "12345678901" + } + ]; + + List expectedOutput = [ + new Recipient() + { + NationalIdentityNumber = "12345678901", + IsReserved = true, + AddressInfo = [new EmailAddressPoint("email@domain.com")] + } + ]; + + var profileClientMock = new Mock(); + profileClientMock + .Setup(p => p.GetUserContactPoints(It.Is>(s => s.Contains("12345678901")))) + .ReturnsAsync([new UserContactPoints() { NationalIdentityNumber = "12345678901", Email = "email@domain.com", IsReserved = true }]); + + var service = GetTestService(profileClient: profileClientMock.Object); + + // Act + List actual = await service.GetEmailContactPoints(input); + + // Assert + Assert.Equivalent(expectedOutput, actual); + } + + private static ContactPointService GetTestService(IProfileClient? profileClient = null) + { + if (profileClient == null) + { + var profileClientMock = new Mock(); + profileClient = profileClientMock.Object; + } + + return new ContactPointService(profileClient); + } + } +} From 696b104c0dc9276d892ecc3117c3d6bbb1489ee0 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Wed, 13 Mar 2024 18:02:12 +0100 Subject: [PATCH 16/22] added availability logic --- .../Services/ContactPointService.cs | 26 +++++++++++++- .../Interfaces/IContactPointService.cs | 16 +++++---- .../ContactPointServiceTests.cs | 34 +++++++++++++++++++ 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/src/Altinn.Notifications.Core/Services/ContactPointService.cs b/src/Altinn.Notifications.Core/Services/ContactPointService.cs index 14b46f8d..ebb951a2 100644 --- a/src/Altinn.Notifications.Core/Services/ContactPointService.cs +++ b/src/Altinn.Notifications.Core/Services/ContactPointService.cs @@ -57,6 +57,12 @@ public async Task> GetSmsContactPoints(List recipient }); } + /// + public async Task> GetContactPointAvailability(List recipients) + { + return await LookupContactPointAvailability(recipients); + } + private async Task> AugmentRecipients(List recipients, Func createContactPoint) { List augmentedRecipients = []; @@ -80,7 +86,7 @@ private async Task> LookupContactPoints(List { List nins = recipients .Where(r => !string.IsNullOrEmpty(r.NationalIdentityNumber)) - .Select(r => r.NationalIdentityNumber!) + .Select(r => r.NationalIdentityNumber!) .ToList(); Task> ninLookupTask = nins.Count > 0 @@ -93,5 +99,23 @@ private async Task> LookupContactPoints(List return userContactPoints; } + + private async Task> LookupContactPointAvailability(List recipients) + { + List nins = recipients + .Where(r => !string.IsNullOrEmpty(r.NationalIdentityNumber)) + .Select(r => r.NationalIdentityNumber!) + .ToList(); + + Task> ninLookupTask = nins.Count > 0 + ? _profileClient.GetUserContactPointAvailabilities(nins) + : Task.FromResult(new List()); + + await Task.WhenAll(ninLookupTask); + + List contactPointAvailabilityList = ninLookupTask.Result; + + return contactPointAvailabilityList; + } } } diff --git a/src/Altinn.Notifications.Core/Services/Interfaces/IContactPointService.cs b/src/Altinn.Notifications.Core/Services/Interfaces/IContactPointService.cs index fe6eaff6..f50b3c38 100644 --- a/src/Altinn.Notifications.Core/Services/Interfaces/IContactPointService.cs +++ b/src/Altinn.Notifications.Core/Services/Interfaces/IContactPointService.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Altinn.Notifications.Core.Models; +using Altinn.Notifications.Core.Models; +using Altinn.Notifications.Core.Models.ContactPoints; namespace Altinn.Notifications.Core.Services.Interfaces { @@ -26,5 +21,12 @@ public interface IContactPointService /// List of recipients to retrieve contact points for /// The list of recipients augumented with SMS address points where available public Task> GetSmsContactPoints(List recipients); + + /// + /// Retrieves the availabililty of contact points for the provided recipient based on their national identity number or organisation number + /// + /// List of recipients to check contact point availability for + /// The list of recipients with contact point availability details + public Task> GetContactPointAvailability(List recipients); } } diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs index 45ddbca5..012e4b78 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs @@ -83,6 +83,40 @@ public async Task GetEmailContactPoints_NationalIdentityNumberAvailable_ProfileS Assert.Equivalent(expectedOutput, actual); } + [Fact] + public async Task GetContactPointAvailability_NationalIdentityNUmberAvailable_ProfileServiceCalled() + { + // Arrange + List input = [ + new Recipient() + { + NationalIdentityNumber = "12345678901" + } + ]; + + List expectedOutput = [ + new UserContactPointAvailability() + { + NationalIdentityNumber = "12345678901", + IsReserved = true, + EmailRegistered = true + } + ]; + + var profileClientMock = new Mock(); + profileClientMock + .Setup(p => p.GetUserContactPointAvailabilities(It.Is>(s => s.Contains("12345678901")))) + .ReturnsAsync([new UserContactPointAvailability() { NationalIdentityNumber = "12345678901", EmailRegistered = true, IsReserved = true }]); + + var service = GetTestService(profileClient: profileClientMock.Object); + + // Act + List actual = await service.GetContactPointAvailability(input); + + // Assert + Assert.Equivalent(expectedOutput, actual); + } + private static ContactPointService GetTestService(IProfileClient? profileClient = null) { if (profileClient == null) From 8ed835038309f8a72372f99b18ad89fdc20afc49 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Wed, 13 Mar 2024 18:14:13 +0100 Subject: [PATCH 17/22] changed syntax for adding to list --- .../Services/ContactPointService.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Altinn.Notifications.Core/Services/ContactPointService.cs b/src/Altinn.Notifications.Core/Services/ContactPointService.cs index ebb951a2..adc91a13 100644 --- a/src/Altinn.Notifications.Core/Services/ContactPointService.cs +++ b/src/Altinn.Notifications.Core/Services/ContactPointService.cs @@ -84,6 +84,7 @@ private async Task> AugmentRecipients(List recipients private async Task> LookupContactPoints(List recipients) { + List userContactPoints = new(); List nins = recipients .Where(r => !string.IsNullOrEmpty(r.NationalIdentityNumber)) .Select(r => r.NationalIdentityNumber!) @@ -95,13 +96,15 @@ private async Task> LookupContactPoints(List await Task.WhenAll(ninLookupTask); - List userContactPoints = ninLookupTask.Result; + userContactPoints.AddRange(ninLookupTask.Result); return userContactPoints; } private async Task> LookupContactPointAvailability(List recipients) { + List contactPointAvailabilityList = new(); + List nins = recipients .Where(r => !string.IsNullOrEmpty(r.NationalIdentityNumber)) .Select(r => r.NationalIdentityNumber!) @@ -113,7 +116,7 @@ private async Task> LookupContactPointAvailab await Task.WhenAll(ninLookupTask); - List contactPointAvailabilityList = ninLookupTask.Result; + contactPointAvailabilityList.AddRange(ninLookupTask.Result); return contactPointAvailabilityList; } From a55b59914a8036163521ac77bcfd0c5a216ab746 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Thu, 14 Mar 2024 13:53:59 +0100 Subject: [PATCH 18/22] removed unused reference --- src/Altinn.Notifications.Core/Services/OrderRequestService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Altinn.Notifications.Core/Services/OrderRequestService.cs b/src/Altinn.Notifications.Core/Services/OrderRequestService.cs index 702b6e47..6ffc0a59 100644 --- a/src/Altinn.Notifications.Core/Services/OrderRequestService.cs +++ b/src/Altinn.Notifications.Core/Services/OrderRequestService.cs @@ -3,7 +3,6 @@ using Altinn.Notifications.Core.Models.Orders; using Altinn.Notifications.Core.Persistence; using Altinn.Notifications.Core.Services.Interfaces; -using Altinn.Notifications.Core.Shared; using Microsoft.Extensions.Options; From 7cf84cd8813fec50e17a907b7b76964ff30ac922 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Thu, 14 Mar 2024 14:53:23 +0100 Subject: [PATCH 19/22] added implementation for order processing retry --- .../Services/EmailOrderProcessingService.cs | 11 +++++++++++ .../Services/SmsOrderProcessingService.cs | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs b/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs index 9942d39e..29b28f68 100644 --- a/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs +++ b/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs @@ -54,10 +54,21 @@ public async Task ProcessOrder(NotificationOrder order) /// public async Task ProcessOrderRetry(NotificationOrder order) { + var recipients = order.Recipients; + var recipientsWithoutEmail = recipients.Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Email)).ToList(); + + List agumentedRecipients = await _contactPointService.GetEmailContactPoints(recipientsWithoutEmail); + var augmentedRecipientDictionary = agumentedRecipients.ToDictionary(ar => $"{ar.NationalIdentityNumber}-{ar.OrganisationNumber}"); + List emailRecipients = await _emailNotificationRepository.GetRecipients(order.Id); foreach (Recipient recipient in order.Recipients) { + if (augmentedRecipientDictionary.TryGetValue($"{recipient.NationalIdentityNumber}-{recipient.OrganisationNumber}", out Recipient? augmentedRecipient)) + { + recipient.AddressInfo.AddRange(augmentedRecipient!.AddressInfo); + } + EmailAddressPoint? addressPoint = recipient.AddressInfo.Find(a => a.AddressType == AddressType.Email) as EmailAddressPoint; if (!emailRecipients.Exists(er => diff --git a/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs b/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs index 457757c2..c4dbe278 100644 --- a/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs +++ b/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs @@ -56,10 +56,22 @@ public async Task ProcessOrder(NotificationOrder order) /// public async Task ProcessOrderRetry(NotificationOrder order) { + var recipients = order.Recipients; + var recipientsWithoutMobileNumber = recipients.Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Sms)).ToList(); + + List agumentedRecipients = await _contactPointService.GetSmsContactPoints(recipientsWithoutMobileNumber); + var augmentedRecipientDictionary = agumentedRecipients.ToDictionary(ar => $"{ar.NationalIdentityNumber}-{ar.OrganisationNumber}"); + int smsCount = GetSmsCountForOrder(order); List smsRecipients = await _smsNotificationRepository.GetRecipients(order.Id); + foreach (Recipient recipient in order.Recipients) { + if (augmentedRecipientDictionary.TryGetValue($"{recipient.NationalIdentityNumber}-{recipient.OrganisationNumber}", out Recipient? augmentedRecipient)) + { + recipient.AddressInfo.AddRange(augmentedRecipient!.AddressInfo); + } + SmsAddressPoint? addressPoint = recipient.AddressInfo.Find(a => a.AddressType == AddressType.Sms) as SmsAddressPoint; if (!smsRecipients.Exists(sr => From b5053a63edd4c264b752095b4a073359b810c9a2 Mon Sep 17 00:00:00 2001 From: Stephanie Buadu <47737608+acn-sbuad@users.noreply.github.com> Date: Mon, 18 Mar 2024 13:14:20 +0100 Subject: [PATCH 20/22] Update src/Altinn.Notifications.Core/Services/ContactPointService.cs Co-authored-by: Terje Holene --- .../Services/ContactPointService.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Altinn.Notifications.Core/Services/ContactPointService.cs b/src/Altinn.Notifications.Core/Services/ContactPointService.cs index adc91a13..815fe90e 100644 --- a/src/Altinn.Notifications.Core/Services/ContactPointService.cs +++ b/src/Altinn.Notifications.Core/Services/ContactPointService.cs @@ -28,10 +28,7 @@ public async Task> GetEmailContactPoints(List recipie recipients, (recipient, userContactPoints) => { - if (userContactPoints.IsReserved) - { - recipient.IsReserved = userContactPoints.IsReserved; - } + recipient.IsReserved = userContactPoints.IsReserved; recipient.AddressInfo.Add(new EmailAddressPoint(userContactPoints.Email)); From c60e19b15bcb94e875122179a3d71f0df886aff3 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Mon, 18 Mar 2024 14:04:10 +0100 Subject: [PATCH 21/22] Ensuring reference type is only updated with contact point once --- .../Services/ContactPointService.cs | 23 ++++++++----------- .../Services/EmailOrderProcessingService.cs | 21 ++++------------- .../Interfaces/IContactPointService.cs | 10 ++++---- .../Services/SmsOrderProcessingService.cs | 23 ++++--------------- .../ContactPointServiceTests.cs | 8 +++---- .../EmailOrderProcessingServiceTests.cs | 15 ++++-------- .../SmsOrderProcessingServiceTests.cs | 16 ++++++------- 7 files changed, 39 insertions(+), 77 deletions(-) diff --git a/src/Altinn.Notifications.Core/Services/ContactPointService.cs b/src/Altinn.Notifications.Core/Services/ContactPointService.cs index 815fe90e..dfb19be9 100644 --- a/src/Altinn.Notifications.Core/Services/ContactPointService.cs +++ b/src/Altinn.Notifications.Core/Services/ContactPointService.cs @@ -22,34 +22,25 @@ public ContactPointService(IProfileClient profile) } /// - public async Task> GetEmailContactPoints(List recipients) + public async Task AddEmailContactPoints(List recipients) { - return await AugmentRecipients( + await AugmentRecipients( recipients, (recipient, userContactPoints) => { - recipient.IsReserved = userContactPoints.IsReserved; - recipient.AddressInfo.Add(new EmailAddressPoint(userContactPoints.Email)); - return recipient; }); } /// - public async Task> GetSmsContactPoints(List recipients) + public async Task AddSmsContactPoints(List recipients) { - return await AugmentRecipients( + await AugmentRecipients( recipients, (recipient, userContactPoints) => { - if (userContactPoints.IsReserved) - { - recipient.IsReserved = userContactPoints.IsReserved; - } - recipient.AddressInfo.Add(new SmsAddressPoint(userContactPoints.MobileNumber)); - return recipient; }); } @@ -72,7 +63,11 @@ private async Task> AugmentRecipients(List recipients UserContactPoints? userContactPoints = userContactPointsList! .Find(u => u.NationalIdentityNumber == recipient.NationalIdentityNumber); - augmentedRecipients.Add(createContactPoint(recipient, userContactPoints!)); + if (userContactPoints != null) + { + recipient.IsReserved = userContactPoints.IsReserved; + augmentedRecipients.Add(createContactPoint(recipient, userContactPoints)); + } } } diff --git a/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs b/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs index 29b28f68..2ef27d34 100644 --- a/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs +++ b/src/Altinn.Notifications.Core/Services/EmailOrderProcessingService.cs @@ -36,18 +36,11 @@ public async Task ProcessOrder(NotificationOrder order) var recipients = order.Recipients; var recipientsWithoutEmail = recipients.Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Email)).ToList(); - List agumentedRecipients = await _contactPointService.GetEmailContactPoints(recipientsWithoutEmail); + await _contactPointService.AddEmailContactPoints(recipientsWithoutEmail); - var augmentedRecipientDictionary = agumentedRecipients.ToDictionary(ar => $"{ar.NationalIdentityNumber}-{ar.OrganisationNumber}"); - - foreach (Recipient originalRecipient in recipients) + foreach (Recipient recipient in recipients) { - if (augmentedRecipientDictionary.TryGetValue($"{originalRecipient.NationalIdentityNumber}-{originalRecipient.OrganisationNumber}", out Recipient? augmentedRecipient)) - { - originalRecipient.AddressInfo.AddRange(augmentedRecipient!.AddressInfo); - } - - await _emailService.CreateNotification(order.Id, order.RequestedSendTime, originalRecipient, order.IgnoreReservation); + await _emailService.CreateNotification(order.Id, order.RequestedSendTime, recipient, order.IgnoreReservation); } } @@ -57,18 +50,12 @@ public async Task ProcessOrderRetry(NotificationOrder order) var recipients = order.Recipients; var recipientsWithoutEmail = recipients.Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Email)).ToList(); - List agumentedRecipients = await _contactPointService.GetEmailContactPoints(recipientsWithoutEmail); - var augmentedRecipientDictionary = agumentedRecipients.ToDictionary(ar => $"{ar.NationalIdentityNumber}-{ar.OrganisationNumber}"); + await _contactPointService.AddEmailContactPoints(recipientsWithoutEmail); List emailRecipients = await _emailNotificationRepository.GetRecipients(order.Id); foreach (Recipient recipient in order.Recipients) { - if (augmentedRecipientDictionary.TryGetValue($"{recipient.NationalIdentityNumber}-{recipient.OrganisationNumber}", out Recipient? augmentedRecipient)) - { - recipient.AddressInfo.AddRange(augmentedRecipient!.AddressInfo); - } - EmailAddressPoint? addressPoint = recipient.AddressInfo.Find(a => a.AddressType == AddressType.Email) as EmailAddressPoint; if (!emailRecipients.Exists(er => diff --git a/src/Altinn.Notifications.Core/Services/Interfaces/IContactPointService.cs b/src/Altinn.Notifications.Core/Services/Interfaces/IContactPointService.cs index f50b3c38..fa3e6701 100644 --- a/src/Altinn.Notifications.Core/Services/Interfaces/IContactPointService.cs +++ b/src/Altinn.Notifications.Core/Services/Interfaces/IContactPointService.cs @@ -9,18 +9,20 @@ namespace Altinn.Notifications.Core.Services.Interfaces public interface IContactPointService { /// - /// Retrieves email contact points for recipients based on their national identity number or organisation number + /// Looks up and adds the email contact points for recipients based on their national identity number or organisation number /// /// List of recipients to retrieve contact points for /// The list of recipients augumented with email address points where available - public Task> GetEmailContactPoints(List recipients); + /// Implementation alters the recipient reference object directly + public Task AddEmailContactPoints(List recipients); /// - /// Retrieves SMS contact points for recipients based on their national identity number or organisation number + /// Looks up and adds the SMS contact points for recipients based on their national identity number or organisation number /// /// List of recipients to retrieve contact points for /// The list of recipients augumented with SMS address points where available - public Task> GetSmsContactPoints(List recipients); + /// Implementation alters the recipient reference object directly + public Task AddSmsContactPoints(List recipients); /// /// Retrieves the availabililty of contact points for the provided recipient based on their national identity number or organisation number diff --git a/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs b/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs index c4dbe278..b6b21009 100644 --- a/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs +++ b/src/Altinn.Notifications.Core/Services/SmsOrderProcessingService.cs @@ -36,20 +36,13 @@ public async Task ProcessOrder(NotificationOrder order) { var recipients = order.Recipients; var recipientsWithoutMobileNumber = recipients.Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Sms)).ToList(); + await _contactPointService.AddSmsContactPoints(recipientsWithoutMobileNumber); - List agumentedRecipients = await _contactPointService.GetSmsContactPoints(recipientsWithoutMobileNumber); - - var augmentedRecipientDictionary = agumentedRecipients.ToDictionary(ar => $"{ar.NationalIdentityNumber}-{ar.OrganisationNumber}"); int smsCount = GetSmsCountForOrder(order); - foreach (Recipient originalRecipient in recipients) + foreach (Recipient recipient in recipients) { - if (augmentedRecipientDictionary.TryGetValue($"{originalRecipient.NationalIdentityNumber}-{originalRecipient.OrganisationNumber}", out Recipient? augmentedRecipient)) - { - originalRecipient.AddressInfo.AddRange(augmentedRecipient!.AddressInfo); - } - - await _smsService.CreateNotification(order.Id, order.RequestedSendTime, originalRecipient, smsCount, order.IgnoreReservation); + await _smsService.CreateNotification(order.Id, order.RequestedSendTime, recipient, smsCount, order.IgnoreReservation); } } @@ -59,19 +52,13 @@ public async Task ProcessOrderRetry(NotificationOrder order) var recipients = order.Recipients; var recipientsWithoutMobileNumber = recipients.Where(r => !r.AddressInfo.Exists(ap => ap.AddressType == AddressType.Sms)).ToList(); - List agumentedRecipients = await _contactPointService.GetSmsContactPoints(recipientsWithoutMobileNumber); - var augmentedRecipientDictionary = agumentedRecipients.ToDictionary(ar => $"{ar.NationalIdentityNumber}-{ar.OrganisationNumber}"); + await _contactPointService.AddSmsContactPoints(recipientsWithoutMobileNumber); int smsCount = GetSmsCountForOrder(order); List smsRecipients = await _smsNotificationRepository.GetRecipients(order.Id); - + foreach (Recipient recipient in order.Recipients) { - if (augmentedRecipientDictionary.TryGetValue($"{recipient.NationalIdentityNumber}-{recipient.OrganisationNumber}", out Recipient? augmentedRecipient)) - { - recipient.AddressInfo.AddRange(augmentedRecipient!.AddressInfo); - } - SmsAddressPoint? addressPoint = recipient.AddressInfo.Find(a => a.AddressType == AddressType.Sms) as SmsAddressPoint; if (!smsRecipients.Exists(sr => diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs index 012e4b78..1dbf04e4 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs @@ -43,10 +43,10 @@ public async Task GetSmsContactPoints_NationalIdentityNumberAvailable_ProfileSer var service = GetTestService(profileClient: profileClientMock.Object); // Act - List actual = await service.GetSmsContactPoints(input); + await service.AddSmsContactPoints(input); // Assert - Assert.Equivalent(expectedOutput, actual); + Assert.Equivalent(expectedOutput, input); } [Fact] @@ -77,10 +77,10 @@ public async Task GetEmailContactPoints_NationalIdentityNumberAvailable_ProfileS var service = GetTestService(profileClient: profileClientMock.Object); // Act - List actual = await service.GetEmailContactPoints(input); + await service.AddEmailContactPoints(input); // Assert - Assert.Equivalent(expectedOutput, actual); + Assert.Equivalent(expectedOutput, input); } [Fact] diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailOrderProcessingServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailOrderProcessingServiceTests.cs index 62c0d1e6..952ef016 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailOrderProcessingServiceTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailOrderProcessingServiceTests.cs @@ -140,16 +140,11 @@ public async Task ProcessOrder_RecipientMissingEmail_ContactPointServiceCalled() s => s.CreateNotification( It.IsAny(), It.IsAny(), - It.Is(r => (r.NationalIdentityNumber == "123456" && r.AddressInfo.Count == 1)), + It.Is(r => r.NationalIdentityNumber == "123456"), It.IsAny())); var contactPointServiceMock = new Mock(); - contactPointServiceMock.Setup(c => c.GetEmailContactPoints(It.Is>(r => r.Count == 1))) - .ReturnsAsync((List r) => - { - Recipient augumentedRecipient = new() { AddressInfo = [new EmailAddressPoint("test@test.com")], NationalIdentityNumber = r[0].NationalIdentityNumber }; - return new List() { augumentedRecipient }; - }); + contactPointServiceMock.Setup(c => c.AddEmailContactPoints(It.Is>(r => r.Count == 1))); var service = GetTestService(emailService: notificationServiceMock.Object, contactPointService: contactPointServiceMock.Object); @@ -157,7 +152,7 @@ public async Task ProcessOrder_RecipientMissingEmail_ContactPointServiceCalled() await service.ProcessOrder(order); // Assert - contactPointServiceMock.Verify(c => c.GetEmailContactPoints(It.Is>(r => r.Count == 1)), Times.Once); + contactPointServiceMock.Verify(c => c.AddEmailContactPoints(It.Is>(r => r.Count == 1)), Times.Once); notificationServiceMock.VerifyAll(); } @@ -219,9 +214,7 @@ private static EmailOrderProcessingService GetTestService( { var contactPointServiceMock = new Mock(); contactPointServiceMock - .Setup(e => e.GetEmailContactPoints(It.IsAny>())) - .ReturnsAsync( - (List recipients) => recipients); + .Setup(e => e.AddEmailContactPoints(It.IsAny>())); contactPointService = contactPointServiceMock.Object; } diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsOrderProcessingServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsOrderProcessingServiceTests.cs index 2fcef88b..4744870d 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsOrderProcessingServiceTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/SmsOrderProcessingServiceTests.cs @@ -114,16 +114,17 @@ public async Task ProcessOrder_RecipientMissingMobileNumber_ContactPointServiceC s => s.CreateNotification( It.IsAny(), It.IsAny(), - It.Is(r => (r.NationalIdentityNumber == "123456" && r.AddressInfo.Count == 1)), + It.Is(r => r.NationalIdentityNumber == "123456"), It.IsAny(), It.IsAny())); var contactPointServiceMock = new Mock(); - contactPointServiceMock.Setup(c => c.GetSmsContactPoints(It.Is>(r => r.Count == 1))) - .ReturnsAsync((List r) => + contactPointServiceMock.Setup(c => c.AddSmsContactPoints(It.Is>(r => r.Count == 1))) + .Callback>(r => { Recipient augumentedRecipient = new() { AddressInfo = [new SmsAddressPoint("+4712345678")], NationalIdentityNumber = r[0].NationalIdentityNumber }; - return new List() { augumentedRecipient }; + r.Clear(); + r.Add(augumentedRecipient); }); var service = GetTestService(smsService: notificationServiceMock.Object, contactPointService: contactPointServiceMock.Object); @@ -132,7 +133,7 @@ public async Task ProcessOrder_RecipientMissingMobileNumber_ContactPointServiceC await service.ProcessOrder(order); // Assert - contactPointServiceMock.Verify(c => c.GetSmsContactPoints(It.Is>(r => r.Count == 1)), Times.Once); + contactPointServiceMock.Verify(c => c.AddSmsContactPoints(It.Is>(r => r.Count == 1)), Times.Once); notificationServiceMock.VerifyAll(); } @@ -211,10 +212,7 @@ private static SmsOrderProcessingService GetTestService( if (contactPointService == null) { var contactPointServiceMock = new Mock(); - contactPointServiceMock - .Setup(e => e.GetSmsContactPoints(It.IsAny>())) - .ReturnsAsync( - (List recipients) => recipients); + contactPointServiceMock.Setup(e => e.AddSmsContactPoints(It.IsAny>())); contactPointService = contactPointServiceMock.Object; } From 98891dc42866a0de5ff65fe0967c36ad0b130b79 Mon Sep 17 00:00:00 2001 From: acn-sbuad Date: Mon, 18 Mar 2024 15:08:50 +0100 Subject: [PATCH 22/22] renamed tests --- .../TestingServices/ContactPointServiceTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs index 1dbf04e4..78055d0e 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/ContactPointServiceTests.cs @@ -16,7 +16,7 @@ namespace Altinn.Notifications.Tests.Notifications.Core.TestingServices public class ContactPointServiceTests { [Fact] - public async Task GetSmsContactPoints_NationalIdentityNumberAvailable_ProfileServiceCalled() + public async Task AddSmsContactPoints_NationalIdentityNumberAvailable_ProfileServiceCalled() { // Arrange List input = [ @@ -50,7 +50,7 @@ public async Task GetSmsContactPoints_NationalIdentityNumberAvailable_ProfileSer } [Fact] - public async Task GetEmailContactPoints_NationalIdentityNumberAvailable_ProfileServiceCalled() + public async Task AddEmailContactPoints_NationalIdentityNumberAvailable_ProfileServiceCalled() { // Arrange List input = [ @@ -84,7 +84,7 @@ public async Task GetEmailContactPoints_NationalIdentityNumberAvailable_ProfileS } [Fact] - public async Task GetContactPointAvailability_NationalIdentityNUmberAvailable_ProfileServiceCalled() + public async Task AddContactPointAvailability_NationalIdentityNUmberAvailable_ProfileServiceCalled() { // Arrange List input = [