From 9bed01279a75be1153147896148b7c88a4816bcd Mon Sep 17 00:00:00 2001 From: Hallgeir Garnes-Gutvik Date: Wed, 25 Sep 2024 15:02:07 +0200 Subject: [PATCH 01/98] Correct name of container and solution folder --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5d34067..3e98a0c 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ podman compose up -d --build - To stop the container running Altinn Profile run the command ```cmd -podman stop altinn-profile +podman stop altinn-platform-profile ``` @@ -71,10 +71,10 @@ podman stop altinn-profile The Profile components can be run locally when developing/debugging. Follow the install steps above if this has not already been done. -- Navigate to _src/Profile, and build and run the code from there, or run the solution using you selected code editor +- Navigate to _src/Altinn.Profile, and build and run the code from there, or run the solution using you selected code editor ```cmd -cd src/Profile +cd src/Altinn.Profile dotnet run ``` From 5e8c12c9b30c771a4b2004657a0c96aa23243836 Mon Sep 17 00:00:00 2001 From: Hallgeir Garnes-Gutvik Date: Wed, 25 Sep 2024 15:02:20 +0200 Subject: [PATCH 02/98] Fix comment typos --- src/Altinn.Profile/Health/HealthCheck.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Altinn.Profile/Health/HealthCheck.cs b/src/Altinn.Profile/Health/HealthCheck.cs index 3e2b55a..d7d5c9a 100644 --- a/src/Altinn.Profile/Health/HealthCheck.cs +++ b/src/Altinn.Profile/Health/HealthCheck.cs @@ -12,9 +12,9 @@ namespace Altinn.Profile.Health public class HealthCheck : IHealthCheck { /// - /// Verifies the healht status + /// Verifies the health status /// - /// The healtcheck context + /// The healthcheck context /// A cancellation token /// A health result public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) From d78a923958ed652d3966f4d1dc2321adec36ab40 Mon Sep 17 00:00:00 2001 From: Hallgeir Garnes-Gutvik Date: Wed, 25 Sep 2024 15:08:02 +0200 Subject: [PATCH 03/98] Use pattern matching for type check --- src/Altinn.Profile/Health/HealthTelemetryFilter.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Altinn.Profile/Health/HealthTelemetryFilter.cs b/src/Altinn.Profile/Health/HealthTelemetryFilter.cs index 85ef6f1..c58b1f1 100644 --- a/src/Altinn.Profile/Health/HealthTelemetryFilter.cs +++ b/src/Altinn.Profile/Health/HealthTelemetryFilter.cs @@ -35,9 +35,7 @@ public void Process(ITelemetry item) private bool ExcludeItemTelemetry(ITelemetry item) { - RequestTelemetry request = item as RequestTelemetry; - - if (request != null && request.Url.ToString().EndsWith("/health/")) + if (item is RequestTelemetry request && request.Url.ToString().EndsWith("/health/")) { return true; } From f7bcce5423c1383065cddc483043568f2b81dd31 Mon Sep 17 00:00:00 2001 From: Hallgeir Garnes-Gutvik Date: Wed, 25 Sep 2024 18:05:42 +0200 Subject: [PATCH 04/98] Refactoring GetUserListByUuid to better fit it with the other methods --- .../User/IUserProfileService.cs | 2 +- .../User/UserProfileCachingDecorator.cs | 12 ++-- .../User/UserProfileService.cs | 7 +-- .../UserProfileInternalController.cs | 8 ++- .../User/UserProfileCachingDecoratorTest.cs | 63 ++++++++++++------- 5 files changed, 58 insertions(+), 34 deletions(-) diff --git a/src/Altinn.Profile.Core/User/IUserProfileService.cs b/src/Altinn.Profile.Core/User/IUserProfileService.cs index 48c90c0..cfe227d 100644 --- a/src/Altinn.Profile.Core/User/IUserProfileService.cs +++ b/src/Altinn.Profile.Core/User/IUserProfileService.cs @@ -33,7 +33,7 @@ public interface IUserProfileService /// /// The list of user uuids /// List of User profiles with given user uuids or a boolean if failure. - Task> GetUserListByUuid(List userUuidList); + Task, bool>> GetUserListByUuid(List userUuidList); /// /// Method that fetches a user based on username. diff --git a/src/Altinn.Profile.Core/User/UserProfileCachingDecorator.cs b/src/Altinn.Profile.Core/User/UserProfileCachingDecorator.cs index d997f04..00c246f 100644 --- a/src/Altinn.Profile.Core/User/UserProfileCachingDecorator.cs +++ b/src/Altinn.Profile.Core/User/UserProfileCachingDecorator.cs @@ -91,10 +91,10 @@ public async Task> GetUserByUuid(Guid userUuid) } /// - public async Task> GetUserListByUuid(List userUuidList) + public async Task, bool>> GetUserListByUuid(List userUuidList) { - List userUuidListNotInCache = new List(); - List result = new List(); + List userUuidListNotInCache = []; + List result = []; foreach (Guid userUuid in userUuidList) { @@ -111,7 +111,11 @@ public async Task> GetUserListByUuid(List userUuidList) if (userUuidListNotInCache.Count > 0) { - List usersToCache = await _decoratedService.GetUserListByUuid(userUuidListNotInCache); + Result, bool> fetchedUserProfiles = await _decoratedService.GetUserListByUuid(userUuidListNotInCache); + List usersToCache = fetchedUserProfiles.Match( + userProfileList => userProfileList, + _ => []); + foreach (UserProfile user in usersToCache) { string uniqueCacheKey = $"User:UserUuid:{user.UserUuid}"; diff --git a/src/Altinn.Profile.Core/User/UserProfileService.cs b/src/Altinn.Profile.Core/User/UserProfileService.cs index f428d45..61efce2 100644 --- a/src/Altinn.Profile.Core/User/UserProfileService.cs +++ b/src/Altinn.Profile.Core/User/UserProfileService.cs @@ -44,11 +44,8 @@ public async Task> GetUserByUuid(Guid userUuid) } /// - public async Task> GetUserListByUuid(List userUuidList) + public async Task, bool>> GetUserListByUuid(List userUuidList) { - var result = await _userProfileClient.GetUserListByUuid(userUuidList); - return result.Match( - userProfileList => { return userProfileList; }, - _ => { return new List(); }); + return await _userProfileClient.GetUserListByUuid(userUuidList); } } diff --git a/src/Altinn.Profile/Controllers/UserProfileInternalController.cs b/src/Altinn.Profile/Controllers/UserProfileInternalController.cs index 5b9af2f..8b2f4fa 100644 --- a/src/Altinn.Profile/Controllers/UserProfileInternalController.cs +++ b/src/Altinn.Profile/Controllers/UserProfileInternalController.cs @@ -93,7 +93,11 @@ public async Task>> GetList([FromBody] List return BadRequest(); } - List result = await _userProfileService.GetUserListByUuid(userUuidList); - return Ok(result); + Result, bool> result = await _userProfileService.GetUserListByUuid(userUuidList); + List userProfiles = result.Match( + userProfileList => userProfileList, + _ => []); + + return Ok(userProfiles); } } diff --git a/test/Altinn.Profile.Tests/Profile.Core/User/UserProfileCachingDecoratorTest.cs b/test/Altinn.Profile.Tests/Profile.Core/User/UserProfileCachingDecoratorTest.cs index f7b12fd..24dd296 100644 --- a/test/Altinn.Profile.Tests/Profile.Core/User/UserProfileCachingDecoratorTest.cs +++ b/test/Altinn.Profile.Tests/Profile.Core/User/UserProfileCachingDecoratorTest.cs @@ -107,18 +107,24 @@ public async Task GetUserListUserUuid_UsersPartialInCache_decoratedServiceBothCa UserProfileCachingDecorator target = new UserProfileCachingDecorator(_decoratedServiceMock.Object, memoryCache, coreSettingsOptions.Object); // Act - List actual = await target.GetUserListByUuid(userUuids); + Result, bool> result = await target.GetUserListByUuid(userUuids); // Assert + Assert.True(result.IsSuccess, "Expected a success result"); _decoratedServiceMock.Verify(service => service.GetUserListByUuid(It.Is>(g => g.TrueForAll(g2 => g2 == userUuidNotInCache))), Times.Once); - Assert.NotNull(actual); - foreach (var userUuid in userUuids) - { - UserProfile currentProfileFromResult = actual.Find(p => p.UserUuid == userUuid); - UserProfile currentProfileFromCache = memoryCache.Get($"User:UserUuid:{userUuid}"); - Assert.NotNull(currentProfileFromResult); - Assert.NotNull(currentProfileFromCache); - } + result.Match( + actual => + { + Assert.NotNull(actual); + foreach (var userUuid in userUuids) + { + UserProfile currentProfileFromResult = actual.Find(p => p.UserUuid == userUuid); + UserProfile currentProfileFromCache = memoryCache.Get($"User:UserUuid:{userUuid}"); + Assert.NotNull(currentProfileFromResult); + Assert.NotNull(currentProfileFromCache); + } + }, + _ => { }); } /// @@ -140,16 +146,22 @@ public async Task GetUserListUserUuid_UsersInCache_decoratedServiceNotCalled() UserProfileCachingDecorator target = new UserProfileCachingDecorator(_decoratedServiceMock.Object, memoryCache, coreSettingsOptions.Object); // Act - List actual = await target.GetUserListByUuid(userUuids); + Result, bool> result = await target.GetUserListByUuid(userUuids); // Assert _decoratedServiceMock.Verify(service => service.GetUserListByUuid(It.IsAny>()), Times.Never()); - Assert.NotNull(actual); - foreach (var userUuid in userUuids) - { - UserProfile currentProfileFromResult = actual.Find(p => p.UserUuid == userUuid); - Assert.NotNull(currentProfileFromResult); - } + Assert.True(result.IsSuccess, "Expected a success result"); + result.Match( + actual => + { + Assert.NotNull(actual); + foreach (var userUuid in userUuids) + { + UserProfile currentProfileFromResult = actual.Find(p => p.UserUuid == userUuid); + Assert.NotNull(currentProfileFromResult); + } + }, + _ => { }); } /// @@ -234,14 +246,19 @@ public async Task GetUserListUserUuid_UserNotInCache_decoratedServiceCalledMockP var target = new UserProfileCachingDecorator(_decoratedServiceMock.Object, memoryCache, coreSettingsOptions.Object); // Act - List actual = await target.GetUserListByUuid(userUuids); + Result, bool> result = await target.GetUserListByUuid(userUuids); // Assert _decoratedServiceMock.Verify(service => service.GetUserListByUuid(It.IsAny>()), Times.Once()); + result.Match( + actual => + { + Assert.Equal(2, actual.Count); + Assert.Equal(userUuids[0], actual[0].UserUuid); + Assert.Equal(userUuids[1], actual[1].UserUuid); + }, + _ => { }); - Assert.Equal(2, actual.Count); - Assert.Equal(userUuids[0], actual[0].UserUuid); - Assert.Equal(userUuids[1], actual[1].UserUuid); Assert.True(memoryCache.TryGetValue($"User:UserUuid:{userUuids[0]}", out UserProfile _), "No data found in memory cache"); Assert.True(memoryCache.TryGetValue($"User:UserUuid:{userUuids[1]}", out UserProfile _), "No data found in memory cache"); } @@ -305,11 +322,13 @@ public async Task GetUserListUserUserUuid_EmptyListFromDecoratedService_CacheNot var target = new UserProfileCachingDecorator(_decoratedServiceMock.Object, memoryCache, coreSettingsOptions.Object); // Act - List actual = await target.GetUserListByUuid(userUuids); + Result, bool> result = await target.GetUserListByUuid(userUuids); // Assert _decoratedServiceMock.Verify(service => service.GetUserListByUuid(It.IsAny>()), Times.Once); - Assert.Empty(actual); + result.Match( + Assert.Empty, + _ => { }); Assert.False(memoryCache.TryGetValue($"User:UserUuid:{userUuids[0]}", out UserProfile _)); Assert.False(memoryCache.TryGetValue($"User:UserUuid:{userUuids[1]}", out UserProfile _)); } From 502872cb83454d7b2aefaff9971c70808c6c8814 Mon Sep 17 00:00:00 2001 From: Hallgeir Garnes-Gutvik Date: Thu, 26 Sep 2024 13:45:07 +0200 Subject: [PATCH 05/98] Correct typo in comment --- .../Unit.ContactPoints/IUnitContactPoints.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPoints.cs b/src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPoints.cs index 3aec836..93e7704 100644 --- a/src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPoints.cs +++ b/src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPoints.cs @@ -8,7 +8,7 @@ namespace Altinn.Profile.Core.User.ContactPoints; public interface IUnitContactPoints { /// - /// Method for retriveing user registered contact points for a unit + /// Method for retrieving user registered contact points for a unit /// /// A lookup object containing a list of organisation numbers and the resource to lookup contact points for /// The users' contact points and reservation status or a boolean if failure. From 9c1185ea9400929cf55d96de840e68204e439477 Mon Sep 17 00:00:00 2001 From: Hallgeir Garnes-Gutvik Date: Thu, 26 Sep 2024 13:49:01 +0200 Subject: [PATCH 06/98] Refactoring to arrow function for simplification --- .../Unit.ContactPoints/UnitContactPointService.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointService.cs b/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointService.cs index a16ca10..36516e1 100644 --- a/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointService.cs +++ b/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointService.cs @@ -19,10 +19,6 @@ public UnitContactPointService(IUnitProfileClient unitClient) } /// - public async Task> GetUserRegisteredContactPoints(UnitContactPointLookup lookup) - { - Result result = await _unitClient.GetUserRegisteredContactPoints(lookup); - return result; - } + public async Task> GetUserRegisteredContactPoints(UnitContactPointLookup lookup) => await _unitClient.GetUserRegisteredContactPoints(lookup); } } From 148c693a70c94f421cc6311cad527c7cffb2fe85 Mon Sep 17 00:00:00 2001 From: Hallgeir Garnes-Gutvik Date: Thu, 26 Sep 2024 14:29:32 +0200 Subject: [PATCH 07/98] Cleaning up namespace for Core's ServiceCollectionExtensions --- src/Altinn.Profile.Core/ServiceCollectionExtensions.cs | 5 ++--- src/Altinn.Profile/Program.cs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Altinn.Profile.Core/ServiceCollectionExtensions.cs b/src/Altinn.Profile.Core/ServiceCollectionExtensions.cs index da1f54d..7262b92 100644 --- a/src/Altinn.Profile.Core/ServiceCollectionExtensions.cs +++ b/src/Altinn.Profile.Core/ServiceCollectionExtensions.cs @@ -1,12 +1,11 @@ -using Altinn.Profile.Core; -using Altinn.Profile.Core.Unit.ContactPoints; +using Altinn.Profile.Core.Unit.ContactPoints; using Altinn.Profile.Core.User; using Altinn.Profile.Core.User.ContactPoints; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -namespace Altinn.Notifications.Core.Extensions; +namespace Altinn.Profile.Core; /// /// Extension class for diff --git a/src/Altinn.Profile/Program.cs b/src/Altinn.Profile/Program.cs index 8c84cd7..0ce41a7 100644 --- a/src/Altinn.Profile/Program.cs +++ b/src/Altinn.Profile/Program.cs @@ -6,8 +6,8 @@ using Altinn.Common.AccessToken; using Altinn.Common.AccessToken.Configuration; using Altinn.Common.AccessToken.Services; -using Altinn.Notifications.Core.Extensions; using Altinn.Profile.Configuration; +using Altinn.Profile.Core; using Altinn.Profile.Filters; using Altinn.Profile.Health; using Altinn.Profile.Integrations; From 463287d91d7b8ec4dbea2a6b793bbf95ea81a1e0 Mon Sep 17 00:00:00 2001 From: Hallgeir Garnes-Gutvik Date: Fri, 27 Sep 2024 09:58:51 +0200 Subject: [PATCH 08/98] Add suffix "Service" to IUnitContactPoints --- src/Altinn.Profile.Core/ServiceCollectionExtensions.cs | 2 +- .../{IUnitContactPoints.cs => IUnitContactPointsService.cs} | 2 +- .../Unit.ContactPoints/UnitContactPointService.cs | 4 ++-- src/Altinn.Profile/Controllers/UnitContactPointController.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename src/Altinn.Profile.Core/Unit.ContactPoints/{IUnitContactPoints.cs => IUnitContactPointsService.cs} (93%) diff --git a/src/Altinn.Profile.Core/ServiceCollectionExtensions.cs b/src/Altinn.Profile.Core/ServiceCollectionExtensions.cs index 7262b92..c72d26b 100644 --- a/src/Altinn.Profile.Core/ServiceCollectionExtensions.cs +++ b/src/Altinn.Profile.Core/ServiceCollectionExtensions.cs @@ -25,6 +25,6 @@ public static void AddCoreServices(this IServiceCollection services, IConfigurat .AddSingleton() .AddSingleton() .Decorate() - .AddSingleton(); + .AddSingleton(); } } diff --git a/src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPoints.cs b/src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPointsService.cs similarity index 93% rename from src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPoints.cs rename to src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPointsService.cs index 93e7704..b3591c9 100644 --- a/src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPoints.cs +++ b/src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPointsService.cs @@ -5,7 +5,7 @@ namespace Altinn.Profile.Core.User.ContactPoints; /// /// Class describing the methods required for user contact point service /// -public interface IUnitContactPoints +public interface IUnitContactPointsService { /// /// Method for retrieving user registered contact points for a unit diff --git a/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointService.cs b/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointService.cs index 36516e1..8d14e3d 100644 --- a/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointService.cs +++ b/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointService.cs @@ -4,9 +4,9 @@ namespace Altinn.Profile.Core.Unit.ContactPoints { /// - /// Implementation of the interface using a REST client to retrieve profile data "/> + /// Implementation of the interface using a REST client to retrieve profile data "/> /// - public class UnitContactPointService : IUnitContactPoints + public class UnitContactPointService : IUnitContactPointsService { private readonly IUnitProfileClient _unitClient; diff --git a/src/Altinn.Profile/Controllers/UnitContactPointController.cs b/src/Altinn.Profile/Controllers/UnitContactPointController.cs index 59887a2..760c31f 100644 --- a/src/Altinn.Profile/Controllers/UnitContactPointController.cs +++ b/src/Altinn.Profile/Controllers/UnitContactPointController.cs @@ -18,12 +18,12 @@ namespace Altinn.Profile.Controllers [Produces("application/json")] public class UnitContactPointController : ControllerBase { - private readonly IUnitContactPoints _contactPointService; + private readonly IUnitContactPointsService _contactPointService; /// /// Initializes a new instance of the class. /// - public UnitContactPointController(IUnitContactPoints contactPointsService) + public UnitContactPointController(IUnitContactPointsService contactPointsService) { _contactPointService = contactPointsService; } From 9da782d2fc45ab000eb19292bbc360103ad3fb14 Mon Sep 17 00:00:00 2001 From: Hallgeir Garnes-Gutvik Date: Fri, 27 Sep 2024 10:01:23 +0200 Subject: [PATCH 09/98] Correct namespace ..User.ContactPoints -> Unit.ContactPoints for IUnitContactPointsService --- .../Unit.ContactPoints/IUnitContactPointsService.cs | 4 +--- .../Unit.ContactPoints/UnitContactPointService.cs | 1 - src/Altinn.Profile/Controllers/UnitContactPointController.cs | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPointsService.cs b/src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPointsService.cs index b3591c9..a653275 100644 --- a/src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPointsService.cs +++ b/src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPointsService.cs @@ -1,6 +1,4 @@ -using Altinn.Profile.Core.Unit.ContactPoints; - -namespace Altinn.Profile.Core.User.ContactPoints; +namespace Altinn.Profile.Core.Unit.ContactPoints; /// /// Class describing the methods required for user contact point service diff --git a/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointService.cs b/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointService.cs index 8d14e3d..fc9762b 100644 --- a/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointService.cs +++ b/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointService.cs @@ -1,5 +1,4 @@ using Altinn.Profile.Core.Integrations; -using Altinn.Profile.Core.User.ContactPoints; namespace Altinn.Profile.Core.Unit.ContactPoints { diff --git a/src/Altinn.Profile/Controllers/UnitContactPointController.cs b/src/Altinn.Profile/Controllers/UnitContactPointController.cs index 760c31f..29aa614 100644 --- a/src/Altinn.Profile/Controllers/UnitContactPointController.cs +++ b/src/Altinn.Profile/Controllers/UnitContactPointController.cs @@ -2,7 +2,6 @@ using Altinn.Profile.Core; using Altinn.Profile.Core.Unit.ContactPoints; -using Altinn.Profile.Core.User.ContactPoints; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; From 6b321908413ac7a2e14d822f95edc6f45ca6e6d0 Mon Sep 17 00:00:00 2001 From: Hallgeir Garnes-Gutvik Date: Fri, 27 Sep 2024 10:36:13 +0200 Subject: [PATCH 10/98] Simplify list initialization --- .../SblBridge/PartyNotificationContactPointsTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/SblBridge/PartyNotificationContactPointsTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/SblBridge/PartyNotificationContactPointsTests.cs index e1216b1..7e7b522 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/SblBridge/PartyNotificationContactPointsTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/SblBridge/PartyNotificationContactPointsTests.cs @@ -25,24 +25,24 @@ public void MapToUnitContactPoints_EmptyListProvided_EmptyListReturned() public void MapToUnitContactPoints_LegacyPartyIdMappedToPartyId() { // Arrange - List input = new List - { + List input = + [ new PartyNotificationContactPoints { PartyId = Guid.NewGuid(), LegacyPartyId = 512345, OrganizationNumber = "123456789", - ContactPoints = new List - { + ContactPoints = + [ new UserRegisteredContactPoint { LegacyUserId = 212345, Email = "user@domain.com", MobileNumber = "12345678" } - } + ] } - }; + ]; UnitContactPointsList expected = new() { From 42def52403577b9f24aa26c2d4b78f1831607e60 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 30 Sep 2024 15:05:25 +0200 Subject: [PATCH 11/98] Generate database models --- src/Altinn.Profile/Altinn.Profile.csproj | 1 + src/Altinn.Profile/Models/MailboxSupplier.cs | 36 ++++++ src/Altinn.Profile/Models/Metadata.cs | 28 +++++ src/Altinn.Profile/Models/ProfiledbContext.cs | 79 ++++++++++++ src/Altinn.Profile/Models/Register.cs | 117 ++++++++++++++++++ src/Altinn.Profile/efpt.config.json | 54 ++++++++ 6 files changed, 315 insertions(+) create mode 100644 src/Altinn.Profile/Models/MailboxSupplier.cs create mode 100644 src/Altinn.Profile/Models/Metadata.cs create mode 100644 src/Altinn.Profile/Models/ProfiledbContext.cs create mode 100644 src/Altinn.Profile/Models/Register.cs create mode 100644 src/Altinn.Profile/efpt.config.json diff --git a/src/Altinn.Profile/Altinn.Profile.csproj b/src/Altinn.Profile/Altinn.Profile.csproj index 91e964e..bcc5e9b 100644 --- a/src/Altinn.Profile/Altinn.Profile.csproj +++ b/src/Altinn.Profile/Altinn.Profile.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Altinn.Profile/Models/MailboxSupplier.cs b/src/Altinn.Profile/Models/MailboxSupplier.cs new file mode 100644 index 0000000..b5db3eb --- /dev/null +++ b/src/Altinn.Profile/Models/MailboxSupplier.cs @@ -0,0 +1,36 @@ +// This file has been auto generated by EF Core Power Tools. +#nullable disable +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Altinn.Profile.Models +{ + /// + /// Represents a mailbox supplier entity. + /// + [Table("mailbox_supplier", Schema = "contact_and_reservation")] + public partial class MailboxSupplier + { + /// + /// Gets or sets the unique identifier for the mailbox supplier. + /// + [Key] + [Column("mailbox_supplier_id")] + public int MailboxSupplierId { get; set; } + + /// + /// Gets or sets the organization number. + /// + [Required] + [Column("org_number_ak")] + [StringLength(9)] + public string OrgNumberAk { get; set; } + + /// + /// Gets or sets the collection of registers associated with the mailbox supplier. + /// + [InverseProperty("MailboxSupplierIdFkNavigation")] + public virtual ICollection Registers { get; set; } = new List(); + } +} diff --git a/src/Altinn.Profile/Models/Metadata.cs b/src/Altinn.Profile/Models/Metadata.cs new file mode 100644 index 0000000..4c295a7 --- /dev/null +++ b/src/Altinn.Profile/Models/Metadata.cs @@ -0,0 +1,28 @@ +// This file has been auto generated by EF Core Power Tools. +#nullable disable +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Altinn.Profile.Models +{ + /// + /// Represents metadata information. + /// + [Table("metadata", Schema = "contact_and_reservation")] + public partial class Metadata + { + /// + /// Gets or sets the latest change number. + /// + [Key] + [Column("latest_change_number")] + public long LatestChangeNumber { get; set; } + + /// + /// Gets or sets the date and time when the metadata was exported. + /// + [Column("exported")] + public DateTime? Exported { get; set; } + } +} diff --git a/src/Altinn.Profile/Models/ProfiledbContext.cs b/src/Altinn.Profile/Models/ProfiledbContext.cs new file mode 100644 index 0000000..4b3c6a7 --- /dev/null +++ b/src/Altinn.Profile/Models/ProfiledbContext.cs @@ -0,0 +1,79 @@ +// This file has been auto generated by EF Core Power Tools. +#nullable disable +using Microsoft.EntityFrameworkCore; + +namespace Altinn.Profile.Models +{ + /// + /// Represents the database context for the profile database. + /// + public partial class ProfiledbContext : DbContext + { + /// + /// Initializes a new instance of the class. + /// + /// The options to be used by a . + public ProfiledbContext(DbContextOptions options) + : base(options) + { + } + + /// + /// Gets or sets the representing the mailbox suppliers. + /// + public virtual DbSet MailboxSuppliers { get; set; } + + /// + /// Gets or sets the representing the metadata. + /// + public virtual DbSet Metadata { get; set; } + + /// + /// Gets or sets the representing the registers. + /// + public virtual DbSet Registers { get; set; } + + /// + /// Configures the schema needed for the context. + /// + /// The builder being used to construct the model for this context. + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.MailboxSupplierId).HasName("mailbox_supplier_pkey"); + + entity.Property(e => e.MailboxSupplierId).UseIdentityAlwaysColumn(); + entity.Property(e => e.OrgNumberAk).IsFixedLength(); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.LatestChangeNumber).HasName("metadata_pkey"); + + entity.Property(e => e.LatestChangeNumber).ValueGeneratedNever(); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.ContactAndReservationUserId).HasName("register_pkey"); + + entity.Property(e => e.ContactAndReservationUserId).UseIdentityAlwaysColumn(); + entity.Property(e => e.FnumberAk).IsFixedLength(); + entity.Property(e => e.LanguageCode).IsFixedLength(); + + entity.HasOne(d => d.MailboxSupplierIdFkNavigation) + .WithMany(p => p.Registers) + .HasConstraintName("fk_mailbox_supplier"); + }); + + OnModelCreatingPartial(modelBuilder); + } + + /// + /// A partial method that can be used to configure the model further. + /// + /// The builder being used to construct the model for this context. + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); + } +} diff --git a/src/Altinn.Profile/Models/Register.cs b/src/Altinn.Profile/Models/Register.cs new file mode 100644 index 0000000..979e998 --- /dev/null +++ b/src/Altinn.Profile/Models/Register.cs @@ -0,0 +1,117 @@ +// This file has been auto generated by EF Core Power Tools. +#nullable disable +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +using Microsoft.EntityFrameworkCore; + +namespace Altinn.Profile.Models +{ + /// + /// Represents a register entity. + /// + [Table("register", Schema = "contact_and_reservation")] + [Index("FnumberAk", Name = "register_fnumber_ak_key", IsUnique = true)] + public partial class Register + { + /// + /// Gets or sets the unique identifier for the contact and reservation user. + /// + [Key] + [Column("contact_and_reservation_user_id")] + public int ContactAndReservationUserId { get; set; } + + /// + /// Gets or sets the F-number. + /// + [Required] + [Column("fnumber_ak")] + [StringLength(11)] + public string FnumberAk { get; set; } + + /// + /// Gets or sets the reservation status. + /// + [Column("reservation")] + public bool? Reservation { get; set; } + + /// + /// Gets or sets the description. + /// + [Column("description")] + [StringLength(20)] + public string Description { get; set; } + + /// + /// Gets or sets the mobile phone number. + /// + [Column("mobile_phone_number")] + [StringLength(20)] + public string MobilePhoneNumber { get; set; } + + /// + /// Gets or sets the date and time when the mobile phone number was last updated. + /// + [Column("mobile_phone_number_last_updated")] + public DateTime? MobilePhoneNumberLastUpdated { get; set; } + + /// + /// Gets or sets the date and time when the mobile phone number was last verified. + /// + [Column("mobile_phone_number_last_verified")] + public DateTime? MobilePhoneNumberLastVerified { get; set; } + + /// + /// Gets or sets the email address. + /// + [Column("email_address")] + [StringLength(400)] + public string EmailAddress { get; set; } + + /// + /// Gets or sets the date and time when the email address was last updated. + /// + [Column("email_address_last_updated")] + public DateTime? EmailAddressLastUpdated { get; set; } + + /// + /// Gets or sets the date and time when the email address was last verified. + /// + [Column("email_address_last_verified")] + public DateTime? EmailAddressLastVerified { get; set; } + + /// + /// Gets or sets the mailbox address. + /// + [Column("mailbox_address")] + [StringLength(50)] + public string MailboxAddress { get; set; } + + /// + /// Gets or sets the foreign key for the mailbox supplier. + /// + [Column("mailbox_supplier_id_fk")] + public int? MailboxSupplierIdFk { get; set; } + + /// + /// Gets or sets the X.509 certificate. + /// + [Column("x509_certificate")] + public string X509Certificate { get; set; } + + /// + /// Gets or sets the language code. + /// + [Column("language_code")] + [StringLength(2)] + public string LanguageCode { get; set; } + + /// + /// Gets or sets the navigation property for the associated mailbox supplier. + /// + [ForeignKey("MailboxSupplierIdFk")] + [InverseProperty("Registers")] + public virtual MailboxSupplier MailboxSupplierIdFkNavigation { get; set; } + } +} diff --git a/src/Altinn.Profile/efpt.config.json b/src/Altinn.Profile/efpt.config.json new file mode 100644 index 0000000..9369e58 --- /dev/null +++ b/src/Altinn.Profile/efpt.config.json @@ -0,0 +1,54 @@ +{ + "CodeGenerationMode": 4, + "ContextClassName": "ProfiledbContext", + "ContextNamespace": null, + "FilterSchemas": false, + "IncludeConnectionString": false, + "ModelNamespace": null, + "OutputContextPath": null, + "OutputPath": "Models", + "PreserveCasingWithRegex": true, + "ProjectRootNamespace": "Altinn.Profile", + "Schemas": null, + "SelectedHandlebarsLanguage": 2, + "SelectedToBeGenerated": 0, + "T4TemplatePath": null, + "Tables": [ + { + "Name": "contact_and_reservation.mailbox_supplier", + "ObjectType": 0 + }, + { + "Name": "contact_and_reservation.metadata", + "ObjectType": 0 + }, + { + "Name": "contact_and_reservation.register", + "ObjectType": 0 + } + ], + "UiHint": null, + "UncountableWords": null, + "UseAsyncStoredProcedureCalls": true, + "UseBoolPropertiesWithoutDefaultSql": false, + "UseDatabaseNames": false, + "UseDateOnlyTimeOnly": true, + "UseDbContextSplitting": false, + "UseDecimalDataAnnotationForSprocResult": true, + "UseFluentApiOnly": false, + "UseHandleBars": false, + "UseHierarchyId": false, + "UseInflector": true, + "UseLegacyPluralizer": false, + "UseManyToManyEntity": false, + "UseNoDefaultConstructor": false, + "UseNoNavigations": false, + "UseNoObjectFilter": false, + "UseNodaTime": false, + "UseNullableReferences": false, + "UsePrefixNavigationNaming": false, + "UseSchemaFolders": false, + "UseSchemaNamespaces": false, + "UseSpatial": false, + "UseT4": false +} \ No newline at end of file From 368af63592528696acd2092ba3b51c501bc879c6 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 30 Sep 2024 15:13:05 +0200 Subject: [PATCH 12/98] Move the database context class to its own folder --- .../Models/{ => DbContext}/ProfiledbContext.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename src/Altinn.Profile/Models/{ => DbContext}/ProfiledbContext.cs (95%) diff --git a/src/Altinn.Profile/Models/ProfiledbContext.cs b/src/Altinn.Profile/Models/DbContext/ProfiledbContext.cs similarity index 95% rename from src/Altinn.Profile/Models/ProfiledbContext.cs rename to src/Altinn.Profile/Models/DbContext/ProfiledbContext.cs index 4b3c6a7..7408e96 100644 --- a/src/Altinn.Profile/Models/ProfiledbContext.cs +++ b/src/Altinn.Profile/Models/DbContext/ProfiledbContext.cs @@ -7,13 +7,13 @@ namespace Altinn.Profile.Models /// /// Represents the database context for the profile database. /// - public partial class ProfiledbContext : DbContext + public partial class ProfileDbContext : DbContext { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The options to be used by a . - public ProfiledbContext(DbContextOptions options) + public ProfileDbContext(DbContextOptions options) : base(options) { } From 3d0631d2d3a83800d5be93260102710ea6614214 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 30 Sep 2024 15:21:36 +0200 Subject: [PATCH 13/98] Update the ContextClassName attribute --- src/Altinn.Profile/efpt.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Altinn.Profile/efpt.config.json b/src/Altinn.Profile/efpt.config.json index 9369e58..93e19b2 100644 --- a/src/Altinn.Profile/efpt.config.json +++ b/src/Altinn.Profile/efpt.config.json @@ -1,6 +1,6 @@ { "CodeGenerationMode": 4, - "ContextClassName": "ProfiledbContext", + "ContextClassName": "ProfileDbContext", "ContextNamespace": null, "FilterSchemas": false, "IncludeConnectionString": false, From 4a9e081c4df07a79c3e421deb78f35a2deef9e94 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 2 Oct 2024 11:17:07 +0200 Subject: [PATCH 14/98] Implemented an endpoint to retrieve user contact information efficiently. --- .../UserContactPointAvailability.cs | 4 +- src/Altinn.Profile/Altinn.Profile.csproj | 5 + .../Context/ProfiledbContext.cs | 80 +++++++ .../Controllers/RegisterController.cs | 62 ++++++ .../Models/DbContext/ProfiledbContext.cs | 79 ------- src/Altinn.Profile/Models/MailboxSupplier.cs | 48 ++-- src/Altinn.Profile/Models/Metadata.cs | 34 +-- src/Altinn.Profile/Models/Register.cs | 210 +++++++++--------- src/Altinn.Profile/Models/UserContactPoint.cs | 45 ++++ src/Altinn.Profile/Program.cs | 14 +- .../Repositories/IRegisterRepository.cs | 26 +++ .../Repositories/IRepository.cs | 17 ++ .../Repositories/RegisterRepository.cs | 48 ++++ src/Altinn.Profile/Repositories/Repository.cs | 35 +++ .../Services/IRegisterService.cs | 66 ++++++ 15 files changed, 545 insertions(+), 228 deletions(-) create mode 100644 src/Altinn.Profile/Context/ProfiledbContext.cs create mode 100644 src/Altinn.Profile/Controllers/RegisterController.cs delete mode 100644 src/Altinn.Profile/Models/DbContext/ProfiledbContext.cs create mode 100644 src/Altinn.Profile/Models/UserContactPoint.cs create mode 100644 src/Altinn.Profile/Repositories/IRegisterRepository.cs create mode 100644 src/Altinn.Profile/Repositories/IRepository.cs create mode 100644 src/Altinn.Profile/Repositories/RegisterRepository.cs create mode 100644 src/Altinn.Profile/Repositories/Repository.cs create mode 100644 src/Altinn.Profile/Services/IRegisterService.cs diff --git a/src/Altinn.Profile.Core/User.ContactPoints/UserContactPointAvailability.cs b/src/Altinn.Profile.Core/User.ContactPoints/UserContactPointAvailability.cs index 0f445e2..4267039 100644 --- a/src/Altinn.Profile.Core/User.ContactPoints/UserContactPointAvailability.cs +++ b/src/Altinn.Profile.Core/User.ContactPoints/UserContactPointAvailability.cs @@ -11,7 +11,7 @@ public class UserContactPointAvailability public int UserId { get; set; } /// - /// Gets or sets the national identityt number of the user + /// Gets or sets the national identity number of the user /// public string NationalIdentityNumber { get; set; } = string.Empty; @@ -37,7 +37,7 @@ public class UserContactPointAvailability public class UserContactPointAvailabilityList { /// - /// A list containing contact point availabiliy for users + /// A list containing contact point availability for users /// public List AvailabilityList { get; set; } = []; } diff --git a/src/Altinn.Profile/Altinn.Profile.csproj b/src/Altinn.Profile/Altinn.Profile.csproj index bcc5e9b..8024fd4 100644 --- a/src/Altinn.Profile/Altinn.Profile.csproj +++ b/src/Altinn.Profile/Altinn.Profile.csproj @@ -5,6 +5,7 @@ true {d32c4ee9-e827-467a-b116-8ef0ba08a11f} + bca64e38-bd2b-434e-96c0-e486dfed625d @@ -15,6 +16,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/src/Altinn.Profile/Context/ProfiledbContext.cs b/src/Altinn.Profile/Context/ProfiledbContext.cs new file mode 100644 index 0000000..55cf7d1 --- /dev/null +++ b/src/Altinn.Profile/Context/ProfiledbContext.cs @@ -0,0 +1,80 @@ +// This file has been auto generated by EF Core Power Tools. +#nullable disable +using Altinn.Profile.Models; + +using Microsoft.EntityFrameworkCore; + +namespace Altinn.Profile.Context; + +/// +/// Represents the database context for the profile database. +/// +public partial class ProfileDbContext : DbContext +{ + /// + /// Initializes a new instance of the class. + /// + /// The options to be used by a . + public ProfileDbContext(DbContextOptions options) + : base(options) + { + } + + /// + /// Gets or sets the representing the mailbox suppliers. + /// + public virtual DbSet MailboxSuppliers { get; set; } + + /// + /// Gets or sets the representing the metadata. + /// + public virtual DbSet Metadata { get; set; } + + /// + /// Gets or sets the representing the registers. + /// + public virtual DbSet Registers { get; set; } + + /// + /// Configures the schema needed for the context. + /// + /// The builder being used to construct the model for this context. + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.MailboxSupplierId).HasName("mailbox_supplier_pkey"); + + entity.Property(e => e.MailboxSupplierId).UseIdentityAlwaysColumn(); + entity.Property(e => e.OrgNumberAk).IsFixedLength(); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.LatestChangeNumber).HasName("metadata_pkey"); + + entity.Property(e => e.LatestChangeNumber).ValueGeneratedNever(); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.ContactAndReservationUserId).HasName("register_pkey"); + + entity.Property(e => e.ContactAndReservationUserId).UseIdentityAlwaysColumn(); + entity.Property(e => e.FnumberAk).IsFixedLength(); + entity.Property(e => e.LanguageCode).IsFixedLength(); + + entity.HasOne(d => d.MailboxSupplierIdFkNavigation) + .WithMany(p => p.Registers) + .HasConstraintName("fk_mailbox_supplier"); + }); + + OnModelCreatingPartial(modelBuilder); + } + + /// + /// A partial method that can be used to configure the model further. + /// + /// The builder being used to construct the model for this context. + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); +} diff --git a/src/Altinn.Profile/Controllers/RegisterController.cs b/src/Altinn.Profile/Controllers/RegisterController.cs new file mode 100644 index 0000000..1e7afa0 --- /dev/null +++ b/src/Altinn.Profile/Controllers/RegisterController.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Altinn.Profile.Models; +using Altinn.Profile.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Altinn.Profile.Controllers +{ + /// + /// Handles the presentation of unhandled exceptions during the execution of a request. + /// + [ApiController] + [Route("profile/api/v1/user")] + public class RegisterController : ControllerBase + { + private readonly IRegisterService _registerService; + + /// + /// Values the tuple. + /// + /// The type of the register service. + /// + public RegisterController(IRegisterService registerService) + { + _registerService = registerService; + } + + /// + /// Gets the user contact points by a collection of national identity numbers. + /// + /// A collection of national identity numbers. + /// A task that represents the asynchronous operation. The task result contains a collection of user contact points. + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> GetByNationalIdentityNumbersAsync([FromBody] IEnumerable nationalIdentityNumbers) + { + var data = await _registerService.GetUserContactPointAsync(nationalIdentityNumbers); + if (data == null) + { + return NotFound(); + } + + var result = new List(); + foreach (var item in data) + { + result.Add(new UserContactPoint + { + Reservation = item.Reservation, + EmailAddress = item.EmailAddress, + LanguageCode = item.LanguageCode, + NationalIdentityNumber = item.FnumberAk, + MobilePhoneNumber = item.MobilePhoneNumber + }); + } + + return Ok(result); + } + } +} diff --git a/src/Altinn.Profile/Models/DbContext/ProfiledbContext.cs b/src/Altinn.Profile/Models/DbContext/ProfiledbContext.cs deleted file mode 100644 index 7408e96..0000000 --- a/src/Altinn.Profile/Models/DbContext/ProfiledbContext.cs +++ /dev/null @@ -1,79 +0,0 @@ -// This file has been auto generated by EF Core Power Tools. -#nullable disable -using Microsoft.EntityFrameworkCore; - -namespace Altinn.Profile.Models -{ - /// - /// Represents the database context for the profile database. - /// - public partial class ProfileDbContext : DbContext - { - /// - /// Initializes a new instance of the class. - /// - /// The options to be used by a . - public ProfileDbContext(DbContextOptions options) - : base(options) - { - } - - /// - /// Gets or sets the representing the mailbox suppliers. - /// - public virtual DbSet MailboxSuppliers { get; set; } - - /// - /// Gets or sets the representing the metadata. - /// - public virtual DbSet Metadata { get; set; } - - /// - /// Gets or sets the representing the registers. - /// - public virtual DbSet Registers { get; set; } - - /// - /// Configures the schema needed for the context. - /// - /// The builder being used to construct the model for this context. - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.MailboxSupplierId).HasName("mailbox_supplier_pkey"); - - entity.Property(e => e.MailboxSupplierId).UseIdentityAlwaysColumn(); - entity.Property(e => e.OrgNumberAk).IsFixedLength(); - }); - - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.LatestChangeNumber).HasName("metadata_pkey"); - - entity.Property(e => e.LatestChangeNumber).ValueGeneratedNever(); - }); - - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.ContactAndReservationUserId).HasName("register_pkey"); - - entity.Property(e => e.ContactAndReservationUserId).UseIdentityAlwaysColumn(); - entity.Property(e => e.FnumberAk).IsFixedLength(); - entity.Property(e => e.LanguageCode).IsFixedLength(); - - entity.HasOne(d => d.MailboxSupplierIdFkNavigation) - .WithMany(p => p.Registers) - .HasConstraintName("fk_mailbox_supplier"); - }); - - OnModelCreatingPartial(modelBuilder); - } - - /// - /// A partial method that can be used to configure the model further. - /// - /// The builder being used to construct the model for this context. - partial void OnModelCreatingPartial(ModelBuilder modelBuilder); - } -} diff --git a/src/Altinn.Profile/Models/MailboxSupplier.cs b/src/Altinn.Profile/Models/MailboxSupplier.cs index b5db3eb..ef28851 100644 --- a/src/Altinn.Profile/Models/MailboxSupplier.cs +++ b/src/Altinn.Profile/Models/MailboxSupplier.cs @@ -1,36 +1,36 @@ // This file has been auto generated by EF Core Power Tools. #nullable disable + using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Altinn.Profile.Models +namespace Altinn.Profile.Models; + +/// +/// Represents a mailbox supplier entity. +/// +[Table("mailbox_supplier", Schema = "contact_and_reservation")] +public partial class MailboxSupplier { /// - /// Represents a mailbox supplier entity. + /// Gets or sets the unique identifier for the mailbox supplier. /// - [Table("mailbox_supplier", Schema = "contact_and_reservation")] - public partial class MailboxSupplier - { - /// - /// Gets or sets the unique identifier for the mailbox supplier. - /// - [Key] - [Column("mailbox_supplier_id")] - public int MailboxSupplierId { get; set; } + [Key] + [Column("mailbox_supplier_id")] + public int MailboxSupplierId { get; set; } - /// - /// Gets or sets the organization number. - /// - [Required] - [Column("org_number_ak")] - [StringLength(9)] - public string OrgNumberAk { get; set; } + /// + /// Gets or sets the organization number. + /// + [Required] + [Column("org_number_ak")] + [StringLength(9)] + public string OrgNumberAk { get; set; } - /// - /// Gets or sets the collection of registers associated with the mailbox supplier. - /// - [InverseProperty("MailboxSupplierIdFkNavigation")] - public virtual ICollection Registers { get; set; } = new List(); - } + /// + /// Gets or sets the collection of registers associated with the mailbox supplier. + /// + [InverseProperty("MailboxSupplierIdFkNavigation")] + public virtual ICollection Registers { get; set; } = new List(); } diff --git a/src/Altinn.Profile/Models/Metadata.cs b/src/Altinn.Profile/Models/Metadata.cs index 4c295a7..e18d8cd 100644 --- a/src/Altinn.Profile/Models/Metadata.cs +++ b/src/Altinn.Profile/Models/Metadata.cs @@ -1,28 +1,28 @@ // This file has been auto generated by EF Core Power Tools. #nullable disable + using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Altinn.Profile.Models +namespace Altinn.Profile.Models; + +/// +/// Represents a metadata entity. +/// +[Table("metadata", Schema = "contact_and_reservation")] +public partial class Metadata { /// - /// Represents metadata information. + /// Gets or sets the latest change number. /// - [Table("metadata", Schema = "contact_and_reservation")] - public partial class Metadata - { - /// - /// Gets or sets the latest change number. - /// - [Key] - [Column("latest_change_number")] - public long LatestChangeNumber { get; set; } + [Key] + [Column("latest_change_number")] + public long LatestChangeNumber { get; set; } - /// - /// Gets or sets the date and time when the metadata was exported. - /// - [Column("exported")] - public DateTime? Exported { get; set; } - } + /// + /// Gets or sets the date and time when the metadata was exported. + /// + [Column("exported")] + public DateTime? Exported { get; set; } } diff --git a/src/Altinn.Profile/Models/Register.cs b/src/Altinn.Profile/Models/Register.cs index 979e998..13566f7 100644 --- a/src/Altinn.Profile/Models/Register.cs +++ b/src/Altinn.Profile/Models/Register.cs @@ -1,117 +1,117 @@ // This file has been auto generated by EF Core Power Tools. #nullable disable + using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; -namespace Altinn.Profile.Models +namespace Altinn.Profile.Models; + +/// +/// Represents a register entity. +/// +[Table("register", Schema = "contact_and_reservation")] +[Index("FnumberAk", Name = "register_fnumber_ak_key", IsUnique = true)] +public partial class Register { /// - /// Represents a register entity. + /// Gets or sets the unique identifier for the user that holds contact and reservation information. + /// + [Key] + [Column("contact_and_reservation_user_id")] + public int ContactAndReservationUserId { get; set; } + + /// + /// Gets or sets the national identity number. + /// + [Required] + [Column("fnumber_ak")] + [StringLength(11)] + public string FnumberAk { get; set; } + + /// + /// Gets or sets the reservation status. + /// + [Column("reservation")] + public bool Reservation { get; set; } + + /// + /// Gets or sets the description. + /// + [Column("description")] + [StringLength(20)] + public string Description { get; set; } + + /// + /// Gets or sets the mobile phone number. + /// + [Column("mobile_phone_number")] + [StringLength(20)] + public string MobilePhoneNumber { get; set; } + + /// + /// Gets or sets the date and time in which the mobile phone number was last updated. + /// + [Column("mobile_phone_number_last_updated")] + public DateTime? MobilePhoneNumberLastUpdated { get; set; } + + /// + /// Gets or sets the date and time in which the mobile phone number was last verified. + /// + [Column("mobile_phone_number_last_verified")] + public DateTime? MobilePhoneNumberLastVerified { get; set; } + + /// + /// Gets or sets the email address. + /// + [Column("email_address")] + [StringLength(400)] + public string EmailAddress { get; set; } + + /// + /// Gets or sets the date and time in which the email address was last updated. + /// + [Column("email_address_last_updated")] + public DateTime? EmailAddressLastUpdated { get; set; } + + /// + /// Gets or sets the date and time in which the email address was last verified. + /// + [Column("email_address_last_verified")] + public DateTime? EmailAddressLastVerified { get; set; } + + /// + /// Gets or sets the mailbox address. + /// + [Column("mailbox_address")] + [StringLength(50)] + public string MailboxAddress { get; set; } + + /// + /// Gets or sets the foreign key for the mailbox supplier. + /// + [Column("mailbox_supplier_id_fk")] + public int? MailboxSupplierIdFk { get; set; } + + /// + /// Gets or sets the X.509 certificate. + /// + [Column("x509_certificate")] + public string X509Certificate { get; set; } + + /// + /// Gets or sets the language code. + /// + [Column("language_code")] + [StringLength(2)] + public string LanguageCode { get; set; } + + /// + /// Gets or sets the navigation property for the associated mailbox supplier. /// - [Table("register", Schema = "contact_and_reservation")] - [Index("FnumberAk", Name = "register_fnumber_ak_key", IsUnique = true)] - public partial class Register - { - /// - /// Gets or sets the unique identifier for the contact and reservation user. - /// - [Key] - [Column("contact_and_reservation_user_id")] - public int ContactAndReservationUserId { get; set; } - - /// - /// Gets or sets the F-number. - /// - [Required] - [Column("fnumber_ak")] - [StringLength(11)] - public string FnumberAk { get; set; } - - /// - /// Gets or sets the reservation status. - /// - [Column("reservation")] - public bool? Reservation { get; set; } - - /// - /// Gets or sets the description. - /// - [Column("description")] - [StringLength(20)] - public string Description { get; set; } - - /// - /// Gets or sets the mobile phone number. - /// - [Column("mobile_phone_number")] - [StringLength(20)] - public string MobilePhoneNumber { get; set; } - - /// - /// Gets or sets the date and time when the mobile phone number was last updated. - /// - [Column("mobile_phone_number_last_updated")] - public DateTime? MobilePhoneNumberLastUpdated { get; set; } - - /// - /// Gets or sets the date and time when the mobile phone number was last verified. - /// - [Column("mobile_phone_number_last_verified")] - public DateTime? MobilePhoneNumberLastVerified { get; set; } - - /// - /// Gets or sets the email address. - /// - [Column("email_address")] - [StringLength(400)] - public string EmailAddress { get; set; } - - /// - /// Gets or sets the date and time when the email address was last updated. - /// - [Column("email_address_last_updated")] - public DateTime? EmailAddressLastUpdated { get; set; } - - /// - /// Gets or sets the date and time when the email address was last verified. - /// - [Column("email_address_last_verified")] - public DateTime? EmailAddressLastVerified { get; set; } - - /// - /// Gets or sets the mailbox address. - /// - [Column("mailbox_address")] - [StringLength(50)] - public string MailboxAddress { get; set; } - - /// - /// Gets or sets the foreign key for the mailbox supplier. - /// - [Column("mailbox_supplier_id_fk")] - public int? MailboxSupplierIdFk { get; set; } - - /// - /// Gets or sets the X.509 certificate. - /// - [Column("x509_certificate")] - public string X509Certificate { get; set; } - - /// - /// Gets or sets the language code. - /// - [Column("language_code")] - [StringLength(2)] - public string LanguageCode { get; set; } - - /// - /// Gets or sets the navigation property for the associated mailbox supplier. - /// - [ForeignKey("MailboxSupplierIdFk")] - [InverseProperty("Registers")] - public virtual MailboxSupplier MailboxSupplierIdFkNavigation { get; set; } - } + [ForeignKey("MailboxSupplierIdFk")] + [InverseProperty("Registers")] + public virtual MailboxSupplier MailboxSupplierIdFkNavigation { get; set; } } diff --git a/src/Altinn.Profile/Models/UserContactPoint.cs b/src/Altinn.Profile/Models/UserContactPoint.cs new file mode 100644 index 0000000..98ed0c5 --- /dev/null +++ b/src/Altinn.Profile/Models/UserContactPoint.cs @@ -0,0 +1,45 @@ +#nullable enable + +using System.Text.Json.Serialization; + +namespace Altinn.Profile.Models; + +/// +/// Represents a user contact point. +/// +public record UserContactPoint +{ + /// + /// Gets or sets the national identity number of the user. + /// + [JsonPropertyName("nationalIdentityNumber")] + public required string NationalIdentityNumber { get; init; } + + /// + /// Gets or sets a value indicating whether the user opts out of being contacted. + /// + [JsonPropertyName("reservation")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? Reservation { get; init; } + + /// + /// Gets or sets the mobile phone number of the user. + /// + [JsonPropertyName("mobilePhoneNumber")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? MobilePhoneNumber { get; init; } + + /// + /// Gets or sets the email address of the user. + /// + [JsonPropertyName("emailAddress")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? EmailAddress { get; init; } + + /// + /// Gets or sets the language code of the user. + /// + [JsonPropertyName("languageCode")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? LanguageCode { get; init; } +} diff --git a/src/Altinn.Profile/Program.cs b/src/Altinn.Profile/Program.cs index 0ce41a7..fbfaedd 100644 --- a/src/Altinn.Profile/Program.cs +++ b/src/Altinn.Profile/Program.cs @@ -7,10 +7,13 @@ using Altinn.Common.AccessToken.Configuration; using Altinn.Common.AccessToken.Services; using Altinn.Profile.Configuration; +using Altinn.Profile.Context; using Altinn.Profile.Core; using Altinn.Profile.Filters; using Altinn.Profile.Health; using Altinn.Profile.Integrations; +using Altinn.Profile.Repositories; +using Altinn.Profile.Services; using AltinnCore.Authentication.JwtCookie; @@ -25,7 +28,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; - +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -39,7 +42,10 @@ ILogger logger; +const string ProfileDbAdminUserNameKey = "PostgreSqlSettings--ProfileDbAdminUserName"; +const string ProfileDbAdminPasswordKey = "PostgreSqlSettings--ProfileDbAdminPassword"; const string VaultApplicationInsightsKey = "ApplicationInsights--InstrumentationKey"; +const string ProfileDbConnectionStringKey = "PostgreSqlSettings--ProfileDbConnectionString"; string applicationInsightsConnectionString = string.Empty; @@ -171,6 +177,12 @@ void ConfigureServices(IServiceCollection services, IConfiguration config) services.AddSingleton(); services.AddSingleton(); + services.AddScoped(); + services.AddScoped(); + + string profileDbConnectionString = string.Format(config[ProfileDbConnectionStringKey], config[ProfileDbAdminUserNameKey], config[ProfileDbAdminPasswordKey]); + services.AddDbContext(options => options.UseNpgsql(profileDbConnectionString)); + services.AddAuthentication(JwtCookieDefaults.AuthenticationScheme) .AddJwtCookie(JwtCookieDefaults.AuthenticationScheme, options => { diff --git a/src/Altinn.Profile/Repositories/IRegisterRepository.cs b/src/Altinn.Profile/Repositories/IRegisterRepository.cs new file mode 100644 index 0000000..881eda8 --- /dev/null +++ b/src/Altinn.Profile/Repositories/IRegisterRepository.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +using Altinn.Profile.Models; + +namespace Altinn.Profile.Repositories; + +/// +/// Repository for handling register data +/// +public interface IRegisterRepository : IRepository +{ + /// + /// Gets the by national identity number asynchronous. + /// + /// The national identity number. + /// + Task GetUserContactPointAsync(string nationalIdentityNumber); + + /// + /// Gets the by national identity number asynchronous. + /// + /// The national identity number. + /// + Task> GetUserContactPointAsync(IEnumerable nationalIdentityNumber); +} diff --git a/src/Altinn.Profile/Repositories/IRepository.cs b/src/Altinn.Profile/Repositories/IRepository.cs new file mode 100644 index 0000000..7c0100f --- /dev/null +++ b/src/Altinn.Profile/Repositories/IRepository.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; + +namespace Altinn.Profile.Repositories; + +/// +/// Defines methods for handling entities in a repository. +/// +/// The type of the entity. +public interface IRepository + where T : class +{ + /// + /// Asynchronously retrieves entities. + /// + /// A task that represents the asynchronous operation. The task result contains the entity that matches the social security number. + Task GetAsync(); +} diff --git a/src/Altinn.Profile/Repositories/RegisterRepository.cs b/src/Altinn.Profile/Repositories/RegisterRepository.cs new file mode 100644 index 0000000..676d96e --- /dev/null +++ b/src/Altinn.Profile/Repositories/RegisterRepository.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using Altinn.Profile.Context; +using Altinn.Profile.Models; + +using Microsoft.EntityFrameworkCore; + +namespace Altinn.Profile.Repositories; + +/// +/// Register Repository for handling register data +/// +/// +public class RegisterRepository : Repository, IRegisterRepository +{ + private ProfileDbContext _context; + + /// + /// Initializes a new instance of the class. + /// + /// The context. + public RegisterRepository(ProfileDbContext context) : base(context) + { + _context = context; + } + + /// + /// Asynchronously retrieves a collection of user contact points based on the provided social security numbers. + /// + /// A collection of social security numbers to filter the user contact points. + /// A task that represents the asynchronous operation. The task result contains a collection of user contact points. + public async Task GetUserContactPointAsync(string nationalIdentityNumber) + { + return await _context.Registers.SingleOrDefaultAsync(k => k.FnumberAk == nationalIdentityNumber); + } + + /// + /// Asynchronously retrieves a collection of user contact points based on the provided social security numbers. + /// + /// A collection of social security numbers to filter the user contact points. + /// A task that represents the asynchronous operation. The task result contains a collection of user contact points. + public async Task> GetUserContactPointAsync(IEnumerable socialSecurityNumbers) + { + return await _context.Registers.Where(k => socialSecurityNumbers.Contains(k.FnumberAk)).ToListAsync(); + } +} diff --git a/src/Altinn.Profile/Repositories/Repository.cs b/src/Altinn.Profile/Repositories/Repository.cs new file mode 100644 index 0000000..ce224bc --- /dev/null +++ b/src/Altinn.Profile/Repositories/Repository.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; + +using Altinn.Profile.Context; + +using Microsoft.EntityFrameworkCore; + +namespace Altinn.Profile.Repositories; + +/// +/// Generic repository for handling data access operations. +/// +/// The type of the entity. +/// +public class Repository : IRepository + where T : class +{ + private readonly ProfileDbContext _context; + private readonly DbSet _dbSet; + + /// + /// Initializes a new instance of the class. + /// + /// The database context. + public Repository(ProfileDbContext context) + { + _context = context; + _dbSet = _context.Set(); + } + + /// + public async Task GetAsync() + { + return await _dbSet.FirstAsync(); + } +} diff --git a/src/Altinn.Profile/Services/IRegisterService.cs b/src/Altinn.Profile/Services/IRegisterService.cs new file mode 100644 index 0000000..170c7e2 --- /dev/null +++ b/src/Altinn.Profile/Services/IRegisterService.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +using Altinn.Profile.Models; +using Altinn.Profile.Repositories; + +namespace Altinn.Profile.Services +{ + /// + /// Register service for handling register data + /// + public interface IRegisterService + { + /// + /// Gets the by national identity number asynchronous. + /// + /// The national identity number. + /// + Task GetUserContactPointAsync(string nationalIdentityNumber); + + /// + /// Gets the by national identity number asynchronous. + /// + /// The national identity number. + /// + Task> GetUserContactPointAsync(IEnumerable nationalIdentityNumber); + } + + /// + /// Register service for handling register data + /// + /// + public class RegisterService : IRegisterService + { + private readonly IRegisterRepository _registerRepository; + + /// + /// Initializes a new instance of the class. + /// + /// The register repository. + public RegisterService(IRegisterRepository registerRepository) + { + _registerRepository = registerRepository; + } + + /// + /// Gets the by national identity number asynchronous. + /// + /// The national identity number. + /// + public async Task GetUserContactPointAsync(string nationalIdentityNumber) + { + return await _registerRepository.GetUserContactPointAsync(nationalIdentityNumber); + } + + /// + /// Gets the by national identity number asynchronous. + /// + /// The national identity number. + /// + public async Task> GetUserContactPointAsync(IEnumerable nationalIdentityNumber) + { + return await _registerRepository.GetUserContactPointAsync(nationalIdentityNumber); + } + } +} From d45930826ea01c05aa365a67756517f690d34227 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 2 Oct 2024 14:36:48 +0200 Subject: [PATCH 15/98] Organize each component into its appropriate namespace --- src/Altinn.Profile.Core/Domain/IRepository.cs | 93 +++++++++++ .../Altinn.Profile.Integrations.csproj | 2 + .../Entities}/MailboxSupplier.cs | 3 +- .../Entities}/Metadata.cs | 3 +- .../Entities}/Register.cs | 3 +- .../Persistence}/ProfiledbContext.cs | 5 +- .../Repositories/IRegisterRepository.cs | 26 ++++ .../Repositories/RegisterRepository.cs | 58 +++++++ .../Repositories/Repository.cs | 147 ++++++++++++++++++ .../Services/IRegisterService.cs} | 13 +- .../Services/RegisterService.cs | 43 +++++ src/Altinn.Profile/Altinn.Profile.csproj | 74 ++++----- .../Controllers/RegisterController.cs | 4 +- src/Altinn.Profile/Program.cs | 6 +- .../Repositories/IRepository.cs | 17 -- .../Repositories/RegisterRepository.cs | 48 ------ src/Altinn.Profile/Repositories/Repository.cs | 35 ----- .../Services/IRegisterService.cs | 66 -------- 18 files changed, 424 insertions(+), 222 deletions(-) create mode 100644 src/Altinn.Profile.Core/Domain/IRepository.cs rename src/{Altinn.Profile/Models => Altinn.Profile.Integrations/Entities}/MailboxSupplier.cs (94%) rename src/{Altinn.Profile/Models => Altinn.Profile.Integrations/Entities}/Metadata.cs (93%) rename src/{Altinn.Profile/Models => Altinn.Profile.Integrations/Entities}/Register.cs (98%) rename src/{Altinn.Profile/Context => Altinn.Profile.Integrations/Persistence}/ProfiledbContext.cs (96%) create mode 100644 src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs create mode 100644 src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs create mode 100644 src/Altinn.Profile.Integrations/Repositories/Repository.cs rename src/{Altinn.Profile/Repositories/IRegisterRepository.cs => Altinn.Profile.Integrations/Services/IRegisterService.cs} (63%) create mode 100644 src/Altinn.Profile.Integrations/Services/RegisterService.cs delete mode 100644 src/Altinn.Profile/Repositories/IRepository.cs delete mode 100644 src/Altinn.Profile/Repositories/RegisterRepository.cs delete mode 100644 src/Altinn.Profile/Repositories/Repository.cs delete mode 100644 src/Altinn.Profile/Services/IRegisterService.cs diff --git a/src/Altinn.Profile.Core/Domain/IRepository.cs b/src/Altinn.Profile.Core/Domain/IRepository.cs new file mode 100644 index 0000000..6a76054 --- /dev/null +++ b/src/Altinn.Profile.Core/Domain/IRepository.cs @@ -0,0 +1,93 @@ +#nullable enable + +namespace Altinn.Profile.Core.Domain; + +/// +/// Defines generic methods for handling entities in a repository. +/// +/// The type of the entity. +public interface IRepository + where T : class +{ + /// + /// Asynchronously retrieves all entities. + /// + /// A task that represents the asynchronous operation. The task result contains a collection of entities. + Task> GetAllAsync(); + + /// + /// Asynchronously retrieves entities with optional filtering, sorting, and pagination. + /// + /// Optional filter criteria. + /// Optional ordering criteria. + /// The number of entities to skip. + /// The number of entities to take. + /// A task that represents the asynchronous operation. The task result contains a collection of filtered, sorted, and paginated entities. + Task> GetAsync( + Func? filter = null, + Func, IOrderedEnumerable>? orderBy = null, + int? skip = null, + int? take = null); + + /// + /// Asynchronously retrieves an entity by its identifier. + /// + /// The identifier of the entity. + /// A task that represents the asynchronous operation. The task result contains the entity that matches the identifier. + Task GetByIdAsync(string id); + + /// + /// Asynchronously checks if an entity exists by its identifier. + /// + /// The identifier of the entity. + /// A task that represents the asynchronous operation. The task result contains a boolean indicating the existence of the entity. + Task ExistsAsync(string id); + + /// + /// Asynchronously adds a new entity. + /// + /// The entity to add. + /// A task that represents the asynchronous operation. The task result contains the added entity. + Task AddAsync(T entity); + + /// + /// Asynchronously adds multiple entities. + /// + /// The entities to add. + /// A task that represents the asynchronous operation. + Task AddRangeAsync(IEnumerable entities); + + /// + /// Asynchronously updates an existing entity. + /// + /// The entity to update. + /// A task that represents the asynchronous operation. + Task UpdateAsync(T entity); + + /// + /// Asynchronously updates multiple entities. + /// + /// The entities to update. + /// A task that represents the asynchronous operation. + Task UpdateRangeAsync(IEnumerable entities); + + /// + /// Asynchronously deletes an entity by its identifier. + /// + /// The identifier of the entity to delete. + /// A task that represents the asynchronous operation. + Task DeleteAsync(string id); + + /// + /// Asynchronously deletes multiple entities by their identifiers. + /// + /// The identifiers of the entities to delete. + /// A task that represents the asynchronous operation. + Task DeleteRangeAsync(IEnumerable ids); + + /// + /// Saves changes to the data source asynchronously. + /// + /// A task that represents the asynchronous save operation. The task result contains the number of state entries written to the data source. + Task SaveChangesAsync(); +} diff --git a/src/Altinn.Profile.Integrations/Altinn.Profile.Integrations.csproj b/src/Altinn.Profile.Integrations/Altinn.Profile.Integrations.csproj index 1744f74..2ec3ca1 100644 --- a/src/Altinn.Profile.Integrations/Altinn.Profile.Integrations.csproj +++ b/src/Altinn.Profile.Integrations/Altinn.Profile.Integrations.csproj @@ -8,10 +8,12 @@ + + diff --git a/src/Altinn.Profile/Models/MailboxSupplier.cs b/src/Altinn.Profile.Integrations/Entities/MailboxSupplier.cs similarity index 94% rename from src/Altinn.Profile/Models/MailboxSupplier.cs rename to src/Altinn.Profile.Integrations/Entities/MailboxSupplier.cs index ef28851..a19a87e 100644 --- a/src/Altinn.Profile/Models/MailboxSupplier.cs +++ b/src/Altinn.Profile.Integrations/Entities/MailboxSupplier.cs @@ -1,11 +1,10 @@ // This file has been auto generated by EF Core Power Tools. #nullable disable -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Altinn.Profile.Models; +namespace Altinn.Profile.Integrations.Entities; /// /// Represents a mailbox supplier entity. diff --git a/src/Altinn.Profile/Models/Metadata.cs b/src/Altinn.Profile.Integrations/Entities/Metadata.cs similarity index 93% rename from src/Altinn.Profile/Models/Metadata.cs rename to src/Altinn.Profile.Integrations/Entities/Metadata.cs index e18d8cd..039641d 100644 --- a/src/Altinn.Profile/Models/Metadata.cs +++ b/src/Altinn.Profile.Integrations/Entities/Metadata.cs @@ -1,11 +1,10 @@ // This file has been auto generated by EF Core Power Tools. #nullable disable -using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Altinn.Profile.Models; +namespace Altinn.Profile.Integrations.Entities; /// /// Represents a metadata entity. diff --git a/src/Altinn.Profile/Models/Register.cs b/src/Altinn.Profile.Integrations/Entities/Register.cs similarity index 98% rename from src/Altinn.Profile/Models/Register.cs rename to src/Altinn.Profile.Integrations/Entities/Register.cs index 13566f7..f90905c 100644 --- a/src/Altinn.Profile/Models/Register.cs +++ b/src/Altinn.Profile.Integrations/Entities/Register.cs @@ -1,13 +1,12 @@ // This file has been auto generated by EF Core Power Tools. #nullable disable -using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; -namespace Altinn.Profile.Models; +namespace Altinn.Profile.Integrations.Entities; /// /// Represents a register entity. diff --git a/src/Altinn.Profile/Context/ProfiledbContext.cs b/src/Altinn.Profile.Integrations/Persistence/ProfiledbContext.cs similarity index 96% rename from src/Altinn.Profile/Context/ProfiledbContext.cs rename to src/Altinn.Profile.Integrations/Persistence/ProfiledbContext.cs index 55cf7d1..93c7ed7 100644 --- a/src/Altinn.Profile/Context/ProfiledbContext.cs +++ b/src/Altinn.Profile.Integrations/Persistence/ProfiledbContext.cs @@ -1,10 +1,11 @@ // This file has been auto generated by EF Core Power Tools. #nullable disable -using Altinn.Profile.Models; + +using Altinn.Profile.Integrations.Entities; using Microsoft.EntityFrameworkCore; -namespace Altinn.Profile.Context; +namespace Altinn.Profile.Integrations.Persistence; /// /// Represents the database context for the profile database. diff --git a/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs new file mode 100644 index 0000000..284cf78 --- /dev/null +++ b/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs @@ -0,0 +1,26 @@ +#nullable enable + +using Altinn.Profile.Core.Domain; +using Altinn.Profile.Integrations.Entities; + +namespace Altinn.Profile.Integrations.Repositories; + +/// +/// Repository for handling register data. +/// +public interface IRegisterRepository : IRepository +{ + /// + /// Gets the contact info for a single user by the national identity number asynchronously. + /// + /// The national identity number. + /// A task that represents the asynchronous operation. The task result contains the register data for the user. + Task GetUserContactPointAsync(string nationalIdentityNumber); + + /// + /// Gets the contact info for multiple users by their national identity numbers asynchronously. + /// + /// The collection of national identity numbers. + /// A task that represents the asynchronous operation. The task result contains a collection of register data for the users. + Task> GetUserContactPointAsync(IEnumerable nationalIdentityNumbers); +} diff --git a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs new file mode 100644 index 0000000..cb70969 --- /dev/null +++ b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs @@ -0,0 +1,58 @@ +#nullable enable + +using Altinn.Profile.Integrations.Entities; +using Altinn.Profile.Integrations.Persistence; + +using Microsoft.EntityFrameworkCore; + +namespace Altinn.Profile.Integrations.Repositories; + +/// +/// Repository for handling register data. +/// +/// +public class RegisterRepository : Repository, IRegisterRepository +{ + private readonly ProfileDbContext _context; + + /// + /// Initializes a new instance of the class. + /// + /// The context. + public RegisterRepository(ProfileDbContext context) : base(context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + /// + /// Asynchronously retrieves the contact info for a single user based on the provided national identity number. + /// + /// The national identity number to filter the user contact point. + /// A task that represents the asynchronous operation. The task result contains the user contact point, or null if not found. + /// Thrown when the provided national identity number is null or empty. + public async Task GetUserContactPointAsync(string nationalIdentityNumber) + { + if (string.IsNullOrWhiteSpace(nationalIdentityNumber)) + { + throw new ArgumentException("National identity number cannot be null or empty.", nameof(nationalIdentityNumber)); + } + + // Asynchronously retrieve the user contact point + return await _context.Registers.SingleOrDefaultAsync(k => k.FnumberAk == nationalIdentityNumber); + } + + /// + /// Asynchronously retrieves the contact info for multiple users based on the provided national identity numbers. + /// + /// A collection of national identity numbers to filter the user contact points. + /// A task that represents the asynchronous operation. The task result contains a collection of user contact points. + public async Task> GetUserContactPointAsync(IEnumerable nationalIdentityNumbers) + { + if (nationalIdentityNumbers == null || nationalIdentityNumbers.Count() == 0) + { + throw new ArgumentException("National identity numbers collection cannot be null or empty.", nameof(nationalIdentityNumbers)); + } + + return await _context.Registers.Where(k => nationalIdentityNumbers.Contains(k.FnumberAk)).ToListAsync(); + } +} diff --git a/src/Altinn.Profile.Integrations/Repositories/Repository.cs b/src/Altinn.Profile.Integrations/Repositories/Repository.cs new file mode 100644 index 0000000..c9f0f3e --- /dev/null +++ b/src/Altinn.Profile.Integrations/Repositories/Repository.cs @@ -0,0 +1,147 @@ +#nullable enable + +using Altinn.Profile.Core.Domain; +using Altinn.Profile.Integrations.Persistence; + +using Microsoft.EntityFrameworkCore; + +namespace Altinn.Profile.Integrations.Repositories; + +/// +/// Generic repository for handling data access operations. +/// +/// The type of the entity. +/// +public class Repository : IRepository + where T : class +{ + private readonly ProfileDbContext _context; + private readonly DbSet _dbSet; + + /// + /// Initializes a new instance of the class. + /// + /// The database context. + public Repository(ProfileDbContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + _dbSet = _context.Set(); + } + + /// + /// Asynchronously adds a new entity to the database. + /// + /// The entity to add. + /// + /// A task that represents the asynchronous operation. The task result contains the added entity. + /// + public Task AddAsync(T entity) + { + throw new NotImplementedException(); + } + + /// + /// Adds multiple entities to the database asynchronously. + /// + /// The entities to add. + /// A task that represents the asynchronous operation. + public Task AddRangeAsync(IEnumerable entities) + { + throw new NotImplementedException(); + } + + /// + /// Deletes an entity from the database asynchronously based on its identifier. + /// + /// The identifier of the entity to delete. + /// A task that represents the asynchronous operation. + public Task DeleteAsync(string id) + { + throw new NotImplementedException(); + } + + /// + /// Deletes multiple entities from the database asynchronously based on their identifiers. + /// + /// The identifiers of the entities to delete. + /// A task that represents the asynchronous operation. + public Task DeleteRangeAsync(IEnumerable ids) + { + throw new NotImplementedException(); + } + + /// + /// Checks whether an entity exists asynchronously based on its identifier. + /// + /// The identifier of the entity. + /// A task that represents the asynchronous operation. The task result contains true if the entity exists, otherwise false. + public Task ExistsAsync(string id) + { + throw new NotImplementedException(); + } + + /// + /// Retrieves all entities from the database asynchronously. + /// + /// A task that represents the asynchronous operation. The task result contains a collection of all entities. + public Task> GetAllAsync() + { + throw new NotImplementedException(); + } + + /// + /// Retrieves entities based on a filter, with optional sorting, pagination, and filtering. + /// + /// A function to filter the entities. + /// A function to order the entities. + /// Number of entities to skip for pagination. + /// Number of entities to take for pagination. + /// A task that represents the asynchronous operation. The task result contains a collection of entities matching the criteria. + public Task> GetAsync( + Func? filter = null, + Func, IOrderedEnumerable>? orderBy = null, + int? skip = null, + int? take = null) + { + throw new NotImplementedException(); + } + + /// + /// Retrieves an entity asynchronously based on its identifier. + /// + /// The identifier of the entity to retrieve. + /// A task that represents the asynchronous operation. The task result contains the entity if found, otherwise null. + public Task GetByIdAsync(string id) + { + throw new NotImplementedException(); + } + + /// + /// Saves the changes made in the context asynchronously. + /// + /// A task that represents the asynchronous operation. The task result contains the number of state entries written to the database. + public Task SaveChangesAsync() + { + throw new NotImplementedException(); + } + + /// + /// Updates an entity in the database asynchronously. + /// + /// The entity to update. + /// A task that represents the asynchronous operation. + public Task UpdateAsync(T entity) + { + throw new NotImplementedException(); + } + + /// + /// Updates multiple entities in the database asynchronously. + /// + /// The entities to update. + /// A task that represents the asynchronous operation. + public Task UpdateRangeAsync(IEnumerable entities) + { + throw new NotImplementedException(); + } +} diff --git a/src/Altinn.Profile/Repositories/IRegisterRepository.cs b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs similarity index 63% rename from src/Altinn.Profile/Repositories/IRegisterRepository.cs rename to src/Altinn.Profile.Integrations/Services/IRegisterService.cs index 881eda8..4e5e599 100644 --- a/src/Altinn.Profile/Repositories/IRegisterRepository.cs +++ b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs @@ -1,21 +1,20 @@ -using System.Collections.Generic; -using System.Threading.Tasks; +#nullable enable -using Altinn.Profile.Models; +using Altinn.Profile.Integrations.Entities; -namespace Altinn.Profile.Repositories; +namespace Altinn.Profile.Integrations.Services; /// -/// Repository for handling register data +/// Register service for handling register data /// -public interface IRegisterRepository : IRepository +public interface IRegisterService { /// /// Gets the by national identity number asynchronous. /// /// The national identity number. /// - Task GetUserContactPointAsync(string nationalIdentityNumber); + Task GetUserContactPointAsync(string nationalIdentityNumber); /// /// Gets the by national identity number asynchronous. diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs new file mode 100644 index 0000000..c76e867 --- /dev/null +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -0,0 +1,43 @@ +#nullable enable + +using Altinn.Profile.Integrations.Entities; +using Altinn.Profile.Integrations.Repositories; + +namespace Altinn.Profile.Integrations.Services; + +/// +/// Service for handling operations related to user registration and contact points. +/// +public class RegisterService : IRegisterService +{ + private readonly IRegisterRepository _registerRepository; + + /// + /// Initializes a new instance of the class. + /// + /// The repository used for accessing register data. + public RegisterService(IRegisterRepository registerRepository) + { + _registerRepository = registerRepository; + } + + /// + /// Asynchronously retrieves the user contact point by national identity number. + /// + /// The national identity number of the user. + /// A task that represents the asynchronous operation. The task result contains the user contact point if found; otherwise, null. + public async Task GetUserContactPointAsync(string nationalIdentityNumber) + { + return await _registerRepository.GetUserContactPointAsync(nationalIdentityNumber); + } + + /// + /// Asynchronously retrieves user contact points by a collection of national identity numbers. + /// + /// A collection of national identity numbers. + /// A task that represents the asynchronous operation. The task result contains a collection of user contact points. + public async Task> GetUserContactPointAsync(IEnumerable nationalIdentityNumbers) + { + return await _registerRepository.GetUserContactPointAsync(nationalIdentityNumbers); + } +} diff --git a/src/Altinn.Profile/Altinn.Profile.csproj b/src/Altinn.Profile/Altinn.Profile.csproj index 8024fd4..a03cf6f 100644 --- a/src/Altinn.Profile/Altinn.Profile.csproj +++ b/src/Altinn.Profile/Altinn.Profile.csproj @@ -1,44 +1,44 @@  - - net8.0 - true - - {d32c4ee9-e827-467a-b116-8ef0ba08a11f} - bca64e38-bd2b-434e-96c0-e486dfed625d - + + net8.0 + true + + {d32c4ee9-e827-467a-b116-8ef0ba08a11f} + bca64e38-bd2b-434e-96c0-e486dfed625d + - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + - - - - + + + + - - - all - runtime; build; native; contentfiles; analyzers - - - stylecop.json - - + + + all + runtime; build; native; contentfiles; analyzers + + + stylecop.json + + diff --git a/src/Altinn.Profile/Controllers/RegisterController.cs b/src/Altinn.Profile/Controllers/RegisterController.cs index 1e7afa0..e2186f4 100644 --- a/src/Altinn.Profile/Controllers/RegisterController.cs +++ b/src/Altinn.Profile/Controllers/RegisterController.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using System.Threading.Tasks; + +using Altinn.Profile.Integrations.Services; using Altinn.Profile.Models; -using Altinn.Profile.Services; + using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; diff --git a/src/Altinn.Profile/Program.cs b/src/Altinn.Profile/Program.cs index fbfaedd..384c383 100644 --- a/src/Altinn.Profile/Program.cs +++ b/src/Altinn.Profile/Program.cs @@ -7,13 +7,13 @@ using Altinn.Common.AccessToken.Configuration; using Altinn.Common.AccessToken.Services; using Altinn.Profile.Configuration; -using Altinn.Profile.Context; using Altinn.Profile.Core; using Altinn.Profile.Filters; using Altinn.Profile.Health; using Altinn.Profile.Integrations; -using Altinn.Profile.Repositories; -using Altinn.Profile.Services; +using Altinn.Profile.Integrations.Persistence; +using Altinn.Profile.Integrations.Repositories; +using Altinn.Profile.Integrations.Services; using AltinnCore.Authentication.JwtCookie; diff --git a/src/Altinn.Profile/Repositories/IRepository.cs b/src/Altinn.Profile/Repositories/IRepository.cs deleted file mode 100644 index 7c0100f..0000000 --- a/src/Altinn.Profile/Repositories/IRepository.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Threading.Tasks; - -namespace Altinn.Profile.Repositories; - -/// -/// Defines methods for handling entities in a repository. -/// -/// The type of the entity. -public interface IRepository - where T : class -{ - /// - /// Asynchronously retrieves entities. - /// - /// A task that represents the asynchronous operation. The task result contains the entity that matches the social security number. - Task GetAsync(); -} diff --git a/src/Altinn.Profile/Repositories/RegisterRepository.cs b/src/Altinn.Profile/Repositories/RegisterRepository.cs deleted file mode 100644 index 676d96e..0000000 --- a/src/Altinn.Profile/Repositories/RegisterRepository.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -using Altinn.Profile.Context; -using Altinn.Profile.Models; - -using Microsoft.EntityFrameworkCore; - -namespace Altinn.Profile.Repositories; - -/// -/// Register Repository for handling register data -/// -/// -public class RegisterRepository : Repository, IRegisterRepository -{ - private ProfileDbContext _context; - - /// - /// Initializes a new instance of the class. - /// - /// The context. - public RegisterRepository(ProfileDbContext context) : base(context) - { - _context = context; - } - - /// - /// Asynchronously retrieves a collection of user contact points based on the provided social security numbers. - /// - /// A collection of social security numbers to filter the user contact points. - /// A task that represents the asynchronous operation. The task result contains a collection of user contact points. - public async Task GetUserContactPointAsync(string nationalIdentityNumber) - { - return await _context.Registers.SingleOrDefaultAsync(k => k.FnumberAk == nationalIdentityNumber); - } - - /// - /// Asynchronously retrieves a collection of user contact points based on the provided social security numbers. - /// - /// A collection of social security numbers to filter the user contact points. - /// A task that represents the asynchronous operation. The task result contains a collection of user contact points. - public async Task> GetUserContactPointAsync(IEnumerable socialSecurityNumbers) - { - return await _context.Registers.Where(k => socialSecurityNumbers.Contains(k.FnumberAk)).ToListAsync(); - } -} diff --git a/src/Altinn.Profile/Repositories/Repository.cs b/src/Altinn.Profile/Repositories/Repository.cs deleted file mode 100644 index ce224bc..0000000 --- a/src/Altinn.Profile/Repositories/Repository.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Threading.Tasks; - -using Altinn.Profile.Context; - -using Microsoft.EntityFrameworkCore; - -namespace Altinn.Profile.Repositories; - -/// -/// Generic repository for handling data access operations. -/// -/// The type of the entity. -/// -public class Repository : IRepository - where T : class -{ - private readonly ProfileDbContext _context; - private readonly DbSet _dbSet; - - /// - /// Initializes a new instance of the class. - /// - /// The database context. - public Repository(ProfileDbContext context) - { - _context = context; - _dbSet = _context.Set(); - } - - /// - public async Task GetAsync() - { - return await _dbSet.FirstAsync(); - } -} diff --git a/src/Altinn.Profile/Services/IRegisterService.cs b/src/Altinn.Profile/Services/IRegisterService.cs deleted file mode 100644 index 170c7e2..0000000 --- a/src/Altinn.Profile/Services/IRegisterService.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -using Altinn.Profile.Models; -using Altinn.Profile.Repositories; - -namespace Altinn.Profile.Services -{ - /// - /// Register service for handling register data - /// - public interface IRegisterService - { - /// - /// Gets the by national identity number asynchronous. - /// - /// The national identity number. - /// - Task GetUserContactPointAsync(string nationalIdentityNumber); - - /// - /// Gets the by national identity number asynchronous. - /// - /// The national identity number. - /// - Task> GetUserContactPointAsync(IEnumerable nationalIdentityNumber); - } - - /// - /// Register service for handling register data - /// - /// - public class RegisterService : IRegisterService - { - private readonly IRegisterRepository _registerRepository; - - /// - /// Initializes a new instance of the class. - /// - /// The register repository. - public RegisterService(IRegisterRepository registerRepository) - { - _registerRepository = registerRepository; - } - - /// - /// Gets the by national identity number asynchronous. - /// - /// The national identity number. - /// - public async Task GetUserContactPointAsync(string nationalIdentityNumber) - { - return await _registerRepository.GetUserContactPointAsync(nationalIdentityNumber); - } - - /// - /// Gets the by national identity number asynchronous. - /// - /// The national identity number. - /// - public async Task> GetUserContactPointAsync(IEnumerable nationalIdentityNumber) - { - return await _registerRepository.GetUserContactPointAsync(nationalIdentityNumber); - } - } -} From 6b9c2f37ec50769de745092fc6ae3944c76da7dc Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 2 Oct 2024 15:10:40 +0200 Subject: [PATCH 16/98] Remove the unused dependencies and improve the service collection extension methods --- .../Altinn.Profile.Integrations.csproj | 4 - .../ServiceCollectionExtensions.cs | 80 +++++++++++++++++-- src/Altinn.Profile/Program.cs | 8 +- 3 files changed, 74 insertions(+), 18 deletions(-) diff --git a/src/Altinn.Profile.Integrations/Altinn.Profile.Integrations.csproj b/src/Altinn.Profile.Integrations/Altinn.Profile.Integrations.csproj index 2ec3ca1..530af99 100644 --- a/src/Altinn.Profile.Integrations/Altinn.Profile.Integrations.csproj +++ b/src/Altinn.Profile.Integrations/Altinn.Profile.Integrations.csproj @@ -8,11 +8,7 @@ - - - - diff --git a/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs b/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs index 703128c..883b1f1 100644 --- a/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs +++ b/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs @@ -1,29 +1,95 @@ using Altinn.Profile.Core.Integrations; +using Altinn.Profile.Integrations.Persistence; using Altinn.Profile.Integrations.SblBridge; +using Altinn.Profile.Integrations.Services; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Altinn.Profile.Integrations; /// -/// Extension class for +/// Extension class for to add services and configurations. /// public static class ServiceCollectionExtensions { + private const string _profileDbAdminUserNameKey = "PostgreSqlSettings--ProfileDbAdminUserName"; + private const string _profileDbAdminPasswordKey = "PostgreSqlSettings--ProfileDbAdminPassword"; + private const string _profileDbConnectionStringKey = "PostgreSqlSettings--ProfileDbConnectionString"; + /// - /// Adds Altinn clients and configurations to DI container. + /// Adds SBL Bridge clients and configurations to the DI container. /// - /// service collection. - /// the configuration collection + /// The service collection. + /// The configuration collection. + /// Thrown when the required SblBridgeSettings are missing from the configuration. public static void AddSblBridgeClients(this IServiceCollection services, IConfiguration config) { - _ = config.GetSection(nameof(SblBridgeSettings)) - .Get() - ?? throw new ArgumentNullException(nameof(config), "Required SblBridgeSettings is missing from application configuration"); + var sblBridgeSettings = config.GetSection(nameof(SblBridgeSettings)).Get(); + if (sblBridgeSettings == null) + { + throw new ArgumentNullException(nameof(config), "Required SblBridgeSettings is missing from application configuration"); + } services.Configure(config.GetSection(nameof(SblBridgeSettings))); + services.AddHttpClient(); services.AddHttpClient(); } + + /// + /// Adds the register service and database context to the DI container. + /// + /// The service collection. + /// The configuration collection. + /// Thrown when the configuration is null. + /// Thrown when any of the required configuration values are missing or empty. + public static void AddRegisterService(this IServiceCollection services, IConfiguration config) + { + if (config == null) + { + throw new ArgumentNullException(nameof(config), "Configuration cannot be null."); + } + + services.AddScoped(); + + var connectionString = config.GetDatabaseConnectionString(); + if (string.IsNullOrEmpty(connectionString)) + { + throw new InvalidOperationException("Database connection string is not properly configured."); + } + + services.AddDbContext(options => options.UseNpgsql(connectionString)); + } + + /// + /// Retrieves the database connection string from the configuration. + /// + /// The configuration instance containing the connection settings. + /// The formatted database connection string if all required settings are present; otherwise, an empty string. + /// + /// This method expects the configuration to contain the following keys: + /// + /// PostgreSqlSettings--ProfileDbAdminUserName + /// PostgreSqlSettings--ProfileDbAdminPassword + /// PostgreSqlSettings--ProfileDbConnectionString + /// + /// The connection string is formatted using the administrator user name and password. + /// + public static string GetDatabaseConnectionString(this IConfiguration config) + { + var adminUserName = config[_profileDbAdminUserNameKey]; + var adminPassword = config[_profileDbAdminPasswordKey]; + var connectionString = config[_profileDbConnectionStringKey]; + + if (string.IsNullOrWhiteSpace(adminUserName) || + string.IsNullOrWhiteSpace(adminPassword) || + string.IsNullOrWhiteSpace(connectionString)) + { + return string.Empty; + } + + return string.Format(connectionString, adminUserName, adminPassword); + } } diff --git a/src/Altinn.Profile/Program.cs b/src/Altinn.Profile/Program.cs index 384c383..8f44629 100644 --- a/src/Altinn.Profile/Program.cs +++ b/src/Altinn.Profile/Program.cs @@ -42,10 +42,7 @@ ILogger logger; -const string ProfileDbAdminUserNameKey = "PostgreSqlSettings--ProfileDbAdminUserName"; -const string ProfileDbAdminPasswordKey = "PostgreSqlSettings--ProfileDbAdminPassword"; const string VaultApplicationInsightsKey = "ApplicationInsights--InstrumentationKey"; -const string ProfileDbConnectionStringKey = "PostgreSqlSettings--ProfileDbConnectionString"; string applicationInsightsConnectionString = string.Empty; @@ -177,12 +174,8 @@ void ConfigureServices(IServiceCollection services, IConfiguration config) services.AddSingleton(); services.AddSingleton(); - services.AddScoped(); services.AddScoped(); - string profileDbConnectionString = string.Format(config[ProfileDbConnectionStringKey], config[ProfileDbAdminUserNameKey], config[ProfileDbAdminPasswordKey]); - services.AddDbContext(options => options.UseNpgsql(profileDbConnectionString)); - services.AddAuthentication(JwtCookieDefaults.AuthenticationScheme) .AddJwtCookie(JwtCookieDefaults.AuthenticationScheme, options => { @@ -211,6 +204,7 @@ void ConfigureServices(IServiceCollection services, IConfiguration config) }); services.AddCoreServices(config); + services.AddRegisterService(config); services.AddSblBridgeClients(config); if (!string.IsNullOrEmpty(applicationInsightsConnectionString)) From da1535bdf15701eed4eb5057a8f9b456b635ee99 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 2 Oct 2024 15:44:22 +0200 Subject: [PATCH 17/98] Implement a simple validation logic --- .../Extensions/ConfigurationExtensions.cs | 43 ++++++++++++ .../ServiceCollectionExtensions.cs | 39 +---------- .../Extensions/StringExtensions.cs | 66 +++++++++++++++++++ .../Services/IRegisterService.cs | 16 ++--- .../Services/RegisterService.cs | 40 ++++++++--- src/Altinn.Profile/Program.cs | 5 +- 6 files changed, 152 insertions(+), 57 deletions(-) create mode 100644 src/Altinn.Profile.Integrations/Extensions/ConfigurationExtensions.cs rename src/Altinn.Profile.Integrations/{ => Extensions}/ServiceCollectionExtensions.cs (60%) create mode 100644 src/Altinn.Profile.Integrations/Extensions/StringExtensions.cs diff --git a/src/Altinn.Profile.Integrations/Extensions/ConfigurationExtensions.cs b/src/Altinn.Profile.Integrations/Extensions/ConfigurationExtensions.cs new file mode 100644 index 0000000..df8b1ab --- /dev/null +++ b/src/Altinn.Profile.Integrations/Extensions/ConfigurationExtensions.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.Configuration; + +namespace Altinn.Profile.Integrations.Extensions; + +/// +/// Extension class for to add more members. +/// +public static class ConfigurationExtensions +{ + private const string ProfileDbAdminUserNameKey = "PostgreSqlSettings--ProfileDbAdminUserName"; + private const string ProfileDbAdminPasswordKey = "PostgreSqlSettings--ProfileDbAdminPassword"; + private const string ProfileDbConnectionStringKey = "PostgreSqlSettings--ProfileDbConnectionString"; + + /// + /// Retrieves the database connection string from the configuration. + /// + /// The configuration instance containing the connection settings. + /// The formatted database connection string if all required settings are present; otherwise, an empty string. + /// + /// This method expects the configuration to contain the following keys: + /// + /// PostgreSqlSettings--ProfileDbAdminUserName + /// PostgreSqlSettings--ProfileDbAdminPassword + /// PostgreSqlSettings--ProfileDbConnectionString + /// + /// The connection string is formatted using the administrator user name and password. + /// + public static string GetDatabaseConnectionString(this IConfiguration config) + { + var adminUserName = config[ProfileDbAdminUserNameKey]; + var adminPassword = config[ProfileDbAdminPasswordKey]; + var connectionString = config[ProfileDbConnectionStringKey]; + + if (string.IsNullOrWhiteSpace(adminUserName) || + string.IsNullOrWhiteSpace(adminPassword) || + string.IsNullOrWhiteSpace(connectionString)) + { + return string.Empty; + } + + return string.Format(connectionString, adminUserName, adminPassword); + } +} diff --git a/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs b/src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs similarity index 60% rename from src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs rename to src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs index 883b1f1..6dfbc02 100644 --- a/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs +++ b/src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -namespace Altinn.Profile.Integrations; +namespace Altinn.Profile.Integrations.Extensions; /// /// Extension class for to add services and configurations. @@ -26,12 +26,7 @@ public static class ServiceCollectionExtensions /// Thrown when the required SblBridgeSettings are missing from the configuration. public static void AddSblBridgeClients(this IServiceCollection services, IConfiguration config) { - var sblBridgeSettings = config.GetSection(nameof(SblBridgeSettings)).Get(); - if (sblBridgeSettings == null) - { - throw new ArgumentNullException(nameof(config), "Required SblBridgeSettings is missing from application configuration"); - } - + var sblBridgeSettings = config.GetSection(nameof(SblBridgeSettings)).Get() ?? throw new ArgumentNullException(nameof(config), "Required SblBridgeSettings is missing from application configuration"); services.Configure(config.GetSection(nameof(SblBridgeSettings))); services.AddHttpClient(); @@ -62,34 +57,4 @@ public static void AddRegisterService(this IServiceCollection services, IConfigu services.AddDbContext(options => options.UseNpgsql(connectionString)); } - - /// - /// Retrieves the database connection string from the configuration. - /// - /// The configuration instance containing the connection settings. - /// The formatted database connection string if all required settings are present; otherwise, an empty string. - /// - /// This method expects the configuration to contain the following keys: - /// - /// PostgreSqlSettings--ProfileDbAdminUserName - /// PostgreSqlSettings--ProfileDbAdminPassword - /// PostgreSqlSettings--ProfileDbConnectionString - /// - /// The connection string is formatted using the administrator user name and password. - /// - public static string GetDatabaseConnectionString(this IConfiguration config) - { - var adminUserName = config[_profileDbAdminUserNameKey]; - var adminPassword = config[_profileDbAdminPasswordKey]; - var connectionString = config[_profileDbConnectionStringKey]; - - if (string.IsNullOrWhiteSpace(adminUserName) || - string.IsNullOrWhiteSpace(adminPassword) || - string.IsNullOrWhiteSpace(connectionString)) - { - return string.Empty; - } - - return string.Format(connectionString, adminUserName, adminPassword); - } } diff --git a/src/Altinn.Profile.Integrations/Extensions/StringExtensions.cs b/src/Altinn.Profile.Integrations/Extensions/StringExtensions.cs new file mode 100644 index 0000000..733642b --- /dev/null +++ b/src/Altinn.Profile.Integrations/Extensions/StringExtensions.cs @@ -0,0 +1,66 @@ +using System.Text.RegularExpressions; + +namespace Altinn.Profile.Integrations.Extensions; + +/// +/// Extension class for to add more members. +/// +public static partial class StringExtensions +{ + /// + /// Determines whether a given string consists of only digits. + /// + /// The string to check. + /// + /// true if the given string consists of only digits; otherwise, false. + /// + /// + /// This method checks if the provided string is not null or whitespace and matches the regex pattern for digits. + /// The regex pattern ensures that the string contains only numeric characters (0-9). + /// + public static bool IsDigitsOnly(this string input) + { + if (string.IsNullOrWhiteSpace(input)) + { + return false; + } + + return DigitsOnlyRegex().IsMatch(input); + } + + /// + /// Removes all whitespace characters from the given string. + /// + /// The string from which to remove whitespace characters. + /// + /// A new string with all whitespace characters removed. + /// If the input is null, empty, or consists only of whitespace, the original input is returned. + /// + public static string? RemoveWhitespace(this string stringToClean) + { + if (string.IsNullOrWhiteSpace(stringToClean)) + { + return stringToClean?.Trim(); + } + + return WhitespaceRegex().Replace(stringToClean, string.Empty); + } + + /// + /// Generates a compiled regular expression for matching all whitespace characters in a string. + /// + /// + /// A object that can be used to match all whitespace characters in a string. + /// + [GeneratedRegex(@"\s+", RegexOptions.Compiled)] + private static partial Regex WhitespaceRegex(); + + /// + /// Generates a compiled regular expression for validating that a string consists of only digits. + /// + /// + /// A object that can be used to validate that a string contains only digits. + /// + [GeneratedRegex(@"^\d+$", RegexOptions.Compiled)] + private static partial Regex DigitsOnlyRegex(); +} diff --git a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs index 4e5e599..ef8b389 100644 --- a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs @@ -5,21 +5,21 @@ namespace Altinn.Profile.Integrations.Services; /// -/// Register service for handling register data +/// Interface for a service that handles operations related to user register data. /// public interface IRegisterService { /// - /// Gets the by national identity number asynchronous. + /// Asynchronously retrieves the contact point information for a user based on their national identity number. /// - /// The national identity number. - /// + /// The national identity number of the user. + /// A task that represents the asynchronous operation. The task result contains the user's register information, or null if not found. Task GetUserContactPointAsync(string nationalIdentityNumber); /// - /// Gets the by national identity number asynchronous. + /// Asynchronously retrieves the contact point information for multiple users based on their national identity numbers. /// - /// The national identity number. - /// - Task> GetUserContactPointAsync(IEnumerable nationalIdentityNumber); + /// A collection of national identity numbers. + /// A task that represents the asynchronous operation. The task result contains a collection of user register information, or null if none are found. + Task> GetUserContactPointAsync(IEnumerable nationalIdentityNumbers); } diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs index c76e867..48545a8 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -1,6 +1,5 @@ -#nullable enable - -using Altinn.Profile.Integrations.Entities; +using Altinn.Profile.Integrations.Entities; +using Altinn.Profile.Integrations.Extensions; using Altinn.Profile.Integrations.Repositories; namespace Altinn.Profile.Integrations.Services; @@ -18,26 +17,51 @@ public class RegisterService : IRegisterService /// The repository used for accessing register data. public RegisterService(IRegisterRepository registerRepository) { - _registerRepository = registerRepository; + _registerRepository = registerRepository ?? throw new ArgumentNullException(nameof(registerRepository)); } /// - /// Asynchronously retrieves the user contact point by national identity number. + /// Asynchronously retrieves the contact point information for a user based on their national identity number. /// /// The national identity number of the user. - /// A task that represents the asynchronous operation. The task result contains the user contact point if found; otherwise, null. + /// A task that represents the asynchronous operation. The task result contains the user's register information, or null if not found. public async Task GetUserContactPointAsync(string nationalIdentityNumber) { + if (!IsValidNationalIdentityNumber(nationalIdentityNumber)) + { + return null; + } + return await _registerRepository.GetUserContactPointAsync(nationalIdentityNumber); } /// - /// Asynchronously retrieves user contact points by a collection of national identity numbers. + /// Asynchronously retrieves the contact point information for multiple users based on their national identity numbers. /// /// A collection of national identity numbers. - /// A task that represents the asynchronous operation. The task result contains a collection of user contact points. + /// A task that represents the asynchronous operation. The task result contains a collection of user register information, or an empty collection if none are found. public async Task> GetUserContactPointAsync(IEnumerable nationalIdentityNumbers) { + if (nationalIdentityNumbers == null || !nationalIdentityNumbers.Any()) + { + return []; + } + + if (!nationalIdentityNumbers.All(IsValidNationalIdentityNumber)) + { + return []; + } + return await _registerRepository.GetUserContactPointAsync(nationalIdentityNumbers); } + + /// + /// Validates the national identity number. + /// + /// The national identity number to validate. + /// True if the national identity number is valid; otherwise, false. + private bool IsValidNationalIdentityNumber(string? nationalIdentityNumber) + { + return !string.IsNullOrWhiteSpace(nationalIdentityNumber) && nationalIdentityNumber.IsDigitsOnly(); + } } diff --git a/src/Altinn.Profile/Program.cs b/src/Altinn.Profile/Program.cs index 8f44629..ed5d029 100644 --- a/src/Altinn.Profile/Program.cs +++ b/src/Altinn.Profile/Program.cs @@ -10,10 +10,8 @@ using Altinn.Profile.Core; using Altinn.Profile.Filters; using Altinn.Profile.Health; -using Altinn.Profile.Integrations; -using Altinn.Profile.Integrations.Persistence; +using Altinn.Profile.Integrations.Extensions; using Altinn.Profile.Integrations.Repositories; -using Altinn.Profile.Integrations.Services; using AltinnCore.Authentication.JwtCookie; @@ -28,7 +26,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; From 4be6d04db955e8ad83e339c152b330112feee92f Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Thu, 3 Oct 2024 08:12:17 +0200 Subject: [PATCH 18/98] Specify the expected request and response content types --- .../Repositories/RegisterRepository.cs | 1 - src/Altinn.Profile/Controllers/RegisterController.cs | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs index cb70969..900a127 100644 --- a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs @@ -37,7 +37,6 @@ public RegisterRepository(ProfileDbContext context) : base(context) throw new ArgumentException("National identity number cannot be null or empty.", nameof(nationalIdentityNumber)); } - // Asynchronously retrieve the user contact point return await _context.Registers.SingleOrDefaultAsync(k => k.FnumberAk == nationalIdentityNumber); } diff --git a/src/Altinn.Profile/Controllers/RegisterController.cs b/src/Altinn.Profile/Controllers/RegisterController.cs index e2186f4..8c6f17a 100644 --- a/src/Altinn.Profile/Controllers/RegisterController.cs +++ b/src/Altinn.Profile/Controllers/RegisterController.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Altinn.Profile.Integrations.Services; @@ -14,6 +15,8 @@ namespace Altinn.Profile.Controllers /// [ApiController] [Route("profile/api/v1/user")] + [Consumes("application/json")] + [Produces("application/json")] public class RegisterController : ControllerBase { private readonly IRegisterService _registerService; @@ -40,7 +43,7 @@ public RegisterController(IRegisterService registerService) public async Task>> GetByNationalIdentityNumbersAsync([FromBody] IEnumerable nationalIdentityNumbers) { var data = await _registerService.GetUserContactPointAsync(nationalIdentityNumbers); - if (data == null) + if (data == null || data.Count() == 0) { return NotFound(); } From 50f9afda03e612d0a166f85e32e6f03e7ca65ddd Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Thu, 3 Oct 2024 11:03:53 +0200 Subject: [PATCH 19/98] Code refactoring to achieve a clean separation of concerns, enhance flexibility, and improve the maintainability and testability --- .../StringExtensions.cs | 2 +- .../Entities/IUserContactInfo.cs | 49 +++++++++++++ .../Entities/UserContactInfo.cs | 49 +++++++++++++ .../Extensions/ServiceCollectionExtensions.cs | 5 +- .../Repositories/IRegisterRepository.cs | 4 +- .../Repositories/RegisterRepository.cs | 8 +-- .../Repositories/Repository.cs | 10 +-- .../Services/IRegisterService.cs | 12 ++-- .../Services/RegisterService.cs | 70 ++++++++++++++++--- .../Controllers/RegisterController.cs | 8 +-- src/Altinn.Profile/Program.cs | 2 - 11 files changed, 185 insertions(+), 34 deletions(-) rename src/{Altinn.Profile.Integrations/Extensions => Altinn.Profile.Core}/StringExtensions.cs (97%) create mode 100644 src/Altinn.Profile.Integrations/Entities/IUserContactInfo.cs create mode 100644 src/Altinn.Profile.Integrations/Entities/UserContactInfo.cs diff --git a/src/Altinn.Profile.Integrations/Extensions/StringExtensions.cs b/src/Altinn.Profile.Core/StringExtensions.cs similarity index 97% rename from src/Altinn.Profile.Integrations/Extensions/StringExtensions.cs rename to src/Altinn.Profile.Core/StringExtensions.cs index 733642b..864e047 100644 --- a/src/Altinn.Profile.Integrations/Extensions/StringExtensions.cs +++ b/src/Altinn.Profile.Core/StringExtensions.cs @@ -1,6 +1,6 @@ using System.Text.RegularExpressions; -namespace Altinn.Profile.Integrations.Extensions; +namespace Altinn.Profile.Core; /// /// Extension class for to add more members. diff --git a/src/Altinn.Profile.Integrations/Entities/IUserContactInfo.cs b/src/Altinn.Profile.Integrations/Entities/IUserContactInfo.cs new file mode 100644 index 0000000..42ddd45 --- /dev/null +++ b/src/Altinn.Profile.Integrations/Entities/IUserContactInfo.cs @@ -0,0 +1,49 @@ +#nullable enable + +namespace Altinn.Profile.Integrations.Entities; + +/// +/// Represents a user's contact information. +/// +public interface IUserContactInfo +{ + /// + /// Gets the national identity number of the user. + /// + /// + /// This is a unique identifier for the user. + /// + string NationalIdentityNumber { get; } + + /// + /// Gets a value indicating whether the user opts out of being contacted. + /// + /// + /// If true, the user has opted out of being contacted. If false, the user has not opted out. + /// + bool? IsReserved { get; } + + /// + /// Gets the mobile phone number of the user. + /// + /// + /// This is the user's primary contact number. + /// + string? MobilePhoneNumber { get; } + + /// + /// Gets the email address of the user. + /// + /// + /// This is the user's primary email address. + /// + string? EmailAddress { get; } + + /// + /// Gets the language code of the user. + /// + /// + /// This is the preferred language of the user, represented as an ISO 639-1 code. + /// + string? LanguageCode { get; } +} diff --git a/src/Altinn.Profile.Integrations/Entities/UserContactInfo.cs b/src/Altinn.Profile.Integrations/Entities/UserContactInfo.cs new file mode 100644 index 0000000..d9d7359 --- /dev/null +++ b/src/Altinn.Profile.Integrations/Entities/UserContactInfo.cs @@ -0,0 +1,49 @@ +#nullable enable + +namespace Altinn.Profile.Integrations.Entities; + +/// +/// Represents a user's contact information. +/// +public class UserContactInfo : IUserContactInfo +{ + /// + /// Gets the national identity number of the user. + /// + /// + /// This is a unique identifier for the user. + /// + public required string NationalIdentityNumber { get; init; } + + /// + /// Gets a value indicating whether the user opts out of being contacted. + /// + /// + /// If true, the user has opted out of being contacted. If false, the user has not opted out. + /// + public bool? IsReserved { get; init; } + + /// + /// Gets the mobile phone number of the user. + /// + /// + /// This is the user's primary contact number. + /// + public string? MobilePhoneNumber { get; init; } + + /// + /// Gets the email address of the user. + /// + /// + /// This is the user's primary email address. + /// + public string? EmailAddress { get; init; } + + /// + /// Gets the language code of the user. + /// + /// + /// This is the preferred language of the user, represented as an ISO 639-1 code. + /// + public string? LanguageCode { get; init; } +} diff --git a/src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs index 6dfbc02..fc77419 100644 --- a/src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ using Altinn.Profile.Core.Integrations; using Altinn.Profile.Integrations.Persistence; +using Altinn.Profile.Integrations.Repositories; using Altinn.Profile.Integrations.SblBridge; using Altinn.Profile.Integrations.Services; @@ -47,8 +48,6 @@ public static void AddRegisterService(this IServiceCollection services, IConfigu throw new ArgumentNullException(nameof(config), "Configuration cannot be null."); } - services.AddScoped(); - var connectionString = config.GetDatabaseConnectionString(); if (string.IsNullOrEmpty(connectionString)) { @@ -56,5 +55,7 @@ public static void AddRegisterService(this IServiceCollection services, IConfigu } services.AddDbContext(options => options.UseNpgsql(connectionString)); + services.AddScoped(); + services.AddScoped(); } } diff --git a/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs index 284cf78..aaa7080 100644 --- a/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs @@ -15,12 +15,12 @@ public interface IRegisterRepository : IRepository /// /// The national identity number. /// A task that represents the asynchronous operation. The task result contains the register data for the user. - Task GetUserContactPointAsync(string nationalIdentityNumber); + Task GetUserContactInfoAsync(string nationalIdentityNumber); /// /// Gets the contact info for multiple users by their national identity numbers asynchronously. /// /// The collection of national identity numbers. /// A task that represents the asynchronous operation. The task result contains a collection of register data for the users. - Task> GetUserContactPointAsync(IEnumerable nationalIdentityNumbers); + Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers); } diff --git a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs index 900a127..5ea407f 100644 --- a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs @@ -11,7 +11,7 @@ namespace Altinn.Profile.Integrations.Repositories; /// Repository for handling register data. /// /// -public class RegisterRepository : Repository, IRegisterRepository +internal class RegisterRepository : Repository, IRegisterRepository { private readonly ProfileDbContext _context; @@ -30,7 +30,7 @@ public RegisterRepository(ProfileDbContext context) : base(context) /// The national identity number to filter the user contact point. /// A task that represents the asynchronous operation. The task result contains the user contact point, or null if not found. /// Thrown when the provided national identity number is null or empty. - public async Task GetUserContactPointAsync(string nationalIdentityNumber) + public async Task GetUserContactInfoAsync(string nationalIdentityNumber) { if (string.IsNullOrWhiteSpace(nationalIdentityNumber)) { @@ -45,9 +45,9 @@ public RegisterRepository(ProfileDbContext context) : base(context) /// /// A collection of national identity numbers to filter the user contact points. /// A task that represents the asynchronous operation. The task result contains a collection of user contact points. - public async Task> GetUserContactPointAsync(IEnumerable nationalIdentityNumbers) + public async Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers) { - if (nationalIdentityNumbers == null || nationalIdentityNumbers.Count() == 0) + if (nationalIdentityNumbers == null || !nationalIdentityNumbers.Any()) { throw new ArgumentException("National identity numbers collection cannot be null or empty.", nameof(nationalIdentityNumbers)); } diff --git a/src/Altinn.Profile.Integrations/Repositories/Repository.cs b/src/Altinn.Profile.Integrations/Repositories/Repository.cs index c9f0f3e..d9c1eab 100644 --- a/src/Altinn.Profile.Integrations/Repositories/Repository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/Repository.cs @@ -12,7 +12,7 @@ namespace Altinn.Profile.Integrations.Repositories; /// /// The type of the entity. /// -public class Repository : IRepository +internal class Repository : IRepository where T : class { private readonly ProfileDbContext _context; @@ -22,7 +22,7 @@ public class Repository : IRepository /// Initializes a new instance of the class. /// /// The database context. - public Repository(ProfileDbContext context) + internal Repository(ProfileDbContext context) { _context = context ?? throw new ArgumentNullException(nameof(context)); _dbSet = _context.Set(); @@ -97,11 +97,7 @@ public Task> GetAllAsync() /// Number of entities to skip for pagination. /// Number of entities to take for pagination. /// A task that represents the asynchronous operation. The task result contains a collection of entities matching the criteria. - public Task> GetAsync( - Func? filter = null, - Func, IOrderedEnumerable>? orderBy = null, - int? skip = null, - int? take = null) + public Task> GetAsync(Func? filter, Func, IOrderedEnumerable>? orderBy, int? skip, int? take) { throw new NotImplementedException(); } diff --git a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs index ef8b389..42f350f 100644 --- a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs @@ -13,13 +13,17 @@ public interface IRegisterService /// Asynchronously retrieves the contact point information for a user based on their national identity number. /// /// The national identity number of the user. - /// A task that represents the asynchronous operation. The task result contains the user's register information, or null if not found. - Task GetUserContactPointAsync(string nationalIdentityNumber); + /// + /// A task that represents the asynchronous operation. The task result contains the user's register information, or null if not found. + /// + Task GetUserContactInfoAsync(string nationalIdentityNumber); /// /// Asynchronously retrieves the contact point information for multiple users based on their national identity numbers. /// /// A collection of national identity numbers. - /// A task that represents the asynchronous operation. The task result contains a collection of user register information, or null if none are found. - Task> GetUserContactPointAsync(IEnumerable nationalIdentityNumbers); + /// + /// A task that represents the asynchronous operation. The task result contains a collection of user register information, or an empty collection if none are found. + /// + Task>? GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers); } diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs index 48545a8..cd7c68c 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -1,5 +1,5 @@ -using Altinn.Profile.Integrations.Entities; -using Altinn.Profile.Integrations.Extensions; +using Altinn.Profile.Core; +using Altinn.Profile.Integrations.Entities; using Altinn.Profile.Integrations.Repositories; namespace Altinn.Profile.Integrations.Services; @@ -24,23 +24,28 @@ public RegisterService(IRegisterRepository registerRepository) /// Asynchronously retrieves the contact point information for a user based on their national identity number. /// /// The national identity number of the user. - /// A task that represents the asynchronous operation. The task result contains the user's register information, or null if not found. - public async Task GetUserContactPointAsync(string nationalIdentityNumber) + /// + /// A task that represents the asynchronous operation. The task result contains the user's register information, or null if not found. + /// + public async Task GetUserContactInfoAsync(string nationalIdentityNumber) { if (!IsValidNationalIdentityNumber(nationalIdentityNumber)) { return null; } - return await _registerRepository.GetUserContactPointAsync(nationalIdentityNumber); + var userContactInfo = await _registerRepository.GetUserContactInfoAsync(nationalIdentityNumber); + return MapToUserContactInfo(userContactInfo); } /// /// Asynchronously retrieves the contact point information for multiple users based on their national identity numbers. /// /// A collection of national identity numbers. - /// A task that represents the asynchronous operation. The task result contains a collection of user register information, or an empty collection if none are found. - public async Task> GetUserContactPointAsync(IEnumerable nationalIdentityNumbers) + /// + /// A task that represents the asynchronous operation. The task result contains a collection of user register information, or an empty collection if none are found. + /// + public async Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers) { if (nationalIdentityNumbers == null || !nationalIdentityNumbers.Any()) { @@ -52,7 +57,56 @@ public async Task> GetUserContactPointAsync(IEnumerable + /// Maps a entity to a object. + /// + /// The entity containing user contact information. + /// + /// A object containing the mapped contact information, or null if the input is null. + /// + private static UserContactInfo? MapToUserContactInfo(Register? userContactInfo) + { + if (userContactInfo is null) + { + return null; + } + + return new UserContactInfo() + { + IsReserved = userContactInfo.Reservation, + EmailAddress = userContactInfo.EmailAddress, + LanguageCode = userContactInfo.LanguageCode, + NationalIdentityNumber = userContactInfo.FnumberAk, + MobilePhoneNumber = userContactInfo.MobilePhoneNumber, + }; + } + + /// + /// Maps a collection of entities to a collection of objects. + /// + /// The collection of entities containing user contact information. + /// + /// A collection of objects containing the mapped contact information, or an empty collection if the input is null. + /// + private static IEnumerable MapToUserContactInfo(IEnumerable? userContactInfos) + { + if (userContactInfos is null) + { + return []; + } + + return userContactInfos.Select(userContactInfo => new UserContactInfo() + { + IsReserved = userContactInfo.Reservation, + EmailAddress = userContactInfo.EmailAddress, + LanguageCode = userContactInfo.LanguageCode, + NationalIdentityNumber = userContactInfo.FnumberAk, + MobilePhoneNumber = userContactInfo.MobilePhoneNumber, + }); } /// diff --git a/src/Altinn.Profile/Controllers/RegisterController.cs b/src/Altinn.Profile/Controllers/RegisterController.cs index 8c6f17a..6b15a38 100644 --- a/src/Altinn.Profile/Controllers/RegisterController.cs +++ b/src/Altinn.Profile/Controllers/RegisterController.cs @@ -42,7 +42,7 @@ public RegisterController(IRegisterService registerService) [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task>> GetByNationalIdentityNumbersAsync([FromBody] IEnumerable nationalIdentityNumbers) { - var data = await _registerService.GetUserContactPointAsync(nationalIdentityNumbers); + var data = await _registerService.GetUserContactInfoAsync(nationalIdentityNumbers); if (data == null || data.Count() == 0) { return NotFound(); @@ -53,11 +53,11 @@ public async Task>> GetByNationalIden { result.Add(new UserContactPoint { - Reservation = item.Reservation, + Reservation = item.IsReserved, EmailAddress = item.EmailAddress, LanguageCode = item.LanguageCode, - NationalIdentityNumber = item.FnumberAk, - MobilePhoneNumber = item.MobilePhoneNumber + MobilePhoneNumber = item.MobilePhoneNumber, + NationalIdentityNumber = item.NationalIdentityNumber, }); } diff --git a/src/Altinn.Profile/Program.cs b/src/Altinn.Profile/Program.cs index ed5d029..4b7928e 100644 --- a/src/Altinn.Profile/Program.cs +++ b/src/Altinn.Profile/Program.cs @@ -171,8 +171,6 @@ void ConfigureServices(IServiceCollection services, IConfiguration config) services.AddSingleton(); services.AddSingleton(); - services.AddScoped(); - services.AddAuthentication(JwtCookieDefaults.AuthenticationScheme) .AddJwtCookie(JwtCookieDefaults.AuthenticationScheme, options => { From f5187230aa6f2a1a916025a72fda94b6afe2f450 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Thu, 3 Oct 2024 11:18:58 +0200 Subject: [PATCH 20/98] Code refactoring --- .../Repositories/{Repository.cs => ProfileRepository.cs} | 7 ++++--- .../Repositories/RegisterRepository.cs | 3 ++- .../Services/IRegisterService.cs | 2 +- .../Services/RegisterService.cs | 1 + 4 files changed, 8 insertions(+), 5 deletions(-) rename src/Altinn.Profile.Integrations/Repositories/{Repository.cs => ProfileRepository.cs} (94%) diff --git a/src/Altinn.Profile.Integrations/Repositories/Repository.cs b/src/Altinn.Profile.Integrations/Repositories/ProfileRepository.cs similarity index 94% rename from src/Altinn.Profile.Integrations/Repositories/Repository.cs rename to src/Altinn.Profile.Integrations/Repositories/ProfileRepository.cs index d9c1eab..6477900 100644 --- a/src/Altinn.Profile.Integrations/Repositories/Repository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/ProfileRepository.cs @@ -12,17 +12,18 @@ namespace Altinn.Profile.Integrations.Repositories; /// /// The type of the entity. /// -internal class Repository : IRepository +internal class ProfileRepository : IRepository where T : class { private readonly ProfileDbContext _context; private readonly DbSet _dbSet; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The database context. - internal Repository(ProfileDbContext context) + /// Thrown when the object is null. + internal ProfileRepository(ProfileDbContext context) { _context = context ?? throw new ArgumentNullException(nameof(context)); _dbSet = _context.Set(); diff --git a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs index 5ea407f..45a5a34 100644 --- a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs @@ -11,7 +11,7 @@ namespace Altinn.Profile.Integrations.Repositories; /// Repository for handling register data. /// /// -internal class RegisterRepository : Repository, IRegisterRepository +internal class RegisterRepository : ProfileRepository, IRegisterRepository { private readonly ProfileDbContext _context; @@ -19,6 +19,7 @@ internal class RegisterRepository : Repository, IRegisterRepository /// Initializes a new instance of the class. /// /// The context. + /// Thrown when the object is null. public RegisterRepository(ProfileDbContext context) : base(context) { _context = context ?? throw new ArgumentNullException(nameof(context)); diff --git a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs index 42f350f..be466ff 100644 --- a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs @@ -25,5 +25,5 @@ public interface IRegisterService /// /// A task that represents the asynchronous operation. The task result contains a collection of user register information, or an empty collection if none are found. /// - Task>? GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers); + Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers); } diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs index cd7c68c..2fa66ab 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -15,6 +15,7 @@ public class RegisterService : IRegisterService /// Initializes a new instance of the class. /// /// The repository used for accessing register data. + /// Thrown when the object is null. public RegisterService(IRegisterRepository registerRepository) { _registerRepository = registerRepository ?? throw new ArgumentNullException(nameof(registerRepository)); From 220239bdf973deb2745a8dd855a1c589d20e597b Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Thu, 3 Oct 2024 12:01:05 +0200 Subject: [PATCH 21/98] Keep the validation in the service layer and use the AutoMapper to move mapping logic out of service. --- .../Altinn.Profile.Integrations.csproj | 1 + .../Entities/UserContactInfoProfile.cs | 27 +++++++ .../Extensions/ServiceCollectionExtensions.cs | 1 + .../Repositories/RegisterRepository.cs | 10 --- .../Services/RegisterService.cs | 70 +++++-------------- 5 files changed, 45 insertions(+), 64 deletions(-) create mode 100644 src/Altinn.Profile.Integrations/Entities/UserContactInfoProfile.cs diff --git a/src/Altinn.Profile.Integrations/Altinn.Profile.Integrations.csproj b/src/Altinn.Profile.Integrations/Altinn.Profile.Integrations.csproj index 530af99..9fb9053 100644 --- a/src/Altinn.Profile.Integrations/Altinn.Profile.Integrations.csproj +++ b/src/Altinn.Profile.Integrations/Altinn.Profile.Integrations.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Altinn.Profile.Integrations/Entities/UserContactInfoProfile.cs b/src/Altinn.Profile.Integrations/Entities/UserContactInfoProfile.cs new file mode 100644 index 0000000..3e9dc6b --- /dev/null +++ b/src/Altinn.Profile.Integrations/Entities/UserContactInfoProfile.cs @@ -0,0 +1,27 @@ +namespace Altinn.Profile.Integrations.Entities; + +/// +/// AutoMapper profile for configuring mappings between and . +/// +/// +/// This profile defines the mapping rules required to transform a object into a object. +/// It is used by AutoMapper to facilitate the conversion of data between these two models. +/// +public class UserContactInfoProfile : AutoMapper.Profile +{ + /// + /// Initializes a new instance of the class. + /// + /// + /// The constructor configures the mappings between the and classes. + /// + public UserContactInfoProfile() + { + CreateMap() + .ForMember(dest => dest.IsReserved, opt => opt.MapFrom(src => src.Reservation)) + .ForMember(dest => dest.EmailAddress, opt => opt.MapFrom(src => src.EmailAddress)) + .ForMember(dest => dest.LanguageCode, opt => opt.MapFrom(src => src.LanguageCode)) + .ForMember(dest => dest.NationalIdentityNumber, opt => opt.MapFrom(src => src.FnumberAk)) + .ForMember(dest => dest.MobilePhoneNumber, opt => opt.MapFrom(src => src.MobilePhoneNumber)); + } +} diff --git a/src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs index fc77419..ff9442b 100644 --- a/src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs @@ -55,6 +55,7 @@ public static void AddRegisterService(this IServiceCollection services, IConfigu } services.AddDbContext(options => options.UseNpgsql(connectionString)); + services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); services.AddScoped(); services.AddScoped(); } diff --git a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs index 45a5a34..8ce0f8d 100644 --- a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs @@ -33,11 +33,6 @@ public RegisterRepository(ProfileDbContext context) : base(context) /// Thrown when the provided national identity number is null or empty. public async Task GetUserContactInfoAsync(string nationalIdentityNumber) { - if (string.IsNullOrWhiteSpace(nationalIdentityNumber)) - { - throw new ArgumentException("National identity number cannot be null or empty.", nameof(nationalIdentityNumber)); - } - return await _context.Registers.SingleOrDefaultAsync(k => k.FnumberAk == nationalIdentityNumber); } @@ -48,11 +43,6 @@ public RegisterRepository(ProfileDbContext context) : base(context) /// A task that represents the asynchronous operation. The task result contains a collection of user contact points. public async Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers) { - if (nationalIdentityNumbers == null || !nationalIdentityNumbers.Any()) - { - throw new ArgumentException("National identity numbers collection cannot be null or empty.", nameof(nationalIdentityNumbers)); - } - return await _context.Registers.Where(k => nationalIdentityNumbers.Contains(k.FnumberAk)).ToListAsync(); } } diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs index 2fa66ab..6dba8bb 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -2,6 +2,8 @@ using Altinn.Profile.Integrations.Entities; using Altinn.Profile.Integrations.Repositories; +using AutoMapper; + namespace Altinn.Profile.Integrations.Services; /// @@ -9,15 +11,18 @@ namespace Altinn.Profile.Integrations.Services; /// public class RegisterService : IRegisterService { + private readonly IMapper _mapper; private readonly IRegisterRepository _registerRepository; /// /// Initializes a new instance of the class. /// + /// The repository used for. /// The repository used for accessing register data. - /// Thrown when the object is null. - public RegisterService(IRegisterRepository registerRepository) + /// Thrown when the or object is null. + public RegisterService(IMapper mapper, IRegisterRepository registerRepository) { + _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); _registerRepository = registerRepository ?? throw new ArgumentNullException(nameof(registerRepository)); } @@ -36,7 +41,7 @@ public RegisterService(IRegisterRepository registerRepository) } var userContactInfo = await _registerRepository.GetUserContactInfoAsync(nationalIdentityNumber); - return MapToUserContactInfo(userContactInfo); + return _mapper.Map(userContactInfo); } /// @@ -44,70 +49,27 @@ public RegisterService(IRegisterRepository registerRepository) /// /// A collection of national identity numbers. /// - /// A task that represents the asynchronous operation. The task result contains a collection of user register information, or an empty collection if none are found. + /// A task that represents the asynchronous operation. The task result contains a collection of user contact information, or an empty collection if none are found. /// public async Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers) { + // Check if the input collection is null or empty if (nationalIdentityNumbers == null || !nationalIdentityNumbers.Any()) { - return []; + return Enumerable.Empty(); // Return an empty collection } + // Validate that all national identity numbers are valid if (!nationalIdentityNumbers.All(IsValidNationalIdentityNumber)) { - return []; + return Enumerable.Empty(); // Return an empty collection for invalid numbers } + // Retrieve user contact information from the repository var userContactInfo = await _registerRepository.GetUserContactInfoAsync(nationalIdentityNumbers); - return MapToUserContactInfo(userContactInfo); - } - - /// - /// Maps a entity to a object. - /// - /// The entity containing user contact information. - /// - /// A object containing the mapped contact information, or null if the input is null. - /// - private static UserContactInfo? MapToUserContactInfo(Register? userContactInfo) - { - if (userContactInfo is null) - { - return null; - } - return new UserContactInfo() - { - IsReserved = userContactInfo.Reservation, - EmailAddress = userContactInfo.EmailAddress, - LanguageCode = userContactInfo.LanguageCode, - NationalIdentityNumber = userContactInfo.FnumberAk, - MobilePhoneNumber = userContactInfo.MobilePhoneNumber, - }; - } - - /// - /// Maps a collection of entities to a collection of objects. - /// - /// The collection of entities containing user contact information. - /// - /// A collection of objects containing the mapped contact information, or an empty collection if the input is null. - /// - private static IEnumerable MapToUserContactInfo(IEnumerable? userContactInfos) - { - if (userContactInfos is null) - { - return []; - } - - return userContactInfos.Select(userContactInfo => new UserContactInfo() - { - IsReserved = userContactInfo.Reservation, - EmailAddress = userContactInfo.EmailAddress, - LanguageCode = userContactInfo.LanguageCode, - NationalIdentityNumber = userContactInfo.FnumberAk, - MobilePhoneNumber = userContactInfo.MobilePhoneNumber, - }); + // Map the retrieved data to the desired interface type + return _mapper.Map>(userContactInfo); } /// From 393fa8b5720e36b28a097d6f771a340dee8e0a8d Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Thu, 3 Oct 2024 12:52:52 +0200 Subject: [PATCH 22/98] Add a number of unit tests --- .../Altinn.Profile.Integrations.csproj | 1 + .../Altinn.Profile.Tests.csproj | 1 + .../RegisterRepositoryTests.cs | 157 ++++++++++++++++++ .../RegisterServiceTests.cs | 103 ++++++++++++ 4 files changed, 262 insertions(+) create mode 100644 test/Altinn.Profile.Tests/Profile.Integrations/RegisterRepositoryTests.cs create mode 100644 test/Altinn.Profile.Tests/Profile.Integrations/RegisterServiceTests.cs diff --git a/src/Altinn.Profile.Integrations/Altinn.Profile.Integrations.csproj b/src/Altinn.Profile.Integrations/Altinn.Profile.Integrations.csproj index 9fb9053..85c1889 100644 --- a/src/Altinn.Profile.Integrations/Altinn.Profile.Integrations.csproj +++ b/src/Altinn.Profile.Integrations/Altinn.Profile.Integrations.csproj @@ -15,6 +15,7 @@ + diff --git a/test/Altinn.Profile.Tests/Altinn.Profile.Tests.csproj b/test/Altinn.Profile.Tests/Altinn.Profile.Tests.csproj index 69dcf15..7be8790 100644 --- a/test/Altinn.Profile.Tests/Altinn.Profile.Tests.csproj +++ b/test/Altinn.Profile.Tests/Altinn.Profile.Tests.csproj @@ -7,6 +7,7 @@ + diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/RegisterRepositoryTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/RegisterRepositoryTests.cs new file mode 100644 index 0000000..62dcb34 --- /dev/null +++ b/test/Altinn.Profile.Tests/Profile.Integrations/RegisterRepositoryTests.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using Altinn.Profile.Integrations.Entities; +using Altinn.Profile.Integrations.Persistence; +using Altinn.Profile.Integrations.Repositories; +using Microsoft.EntityFrameworkCore; + +using Xunit; + +namespace Altinn.Profile.Tests.Profile.Integrations; + +/// +/// Contains unit tests for the class. +/// +public class RegisterRepositoryTests : IDisposable +{ + private readonly ProfileDbContext _context; + private readonly RegisterRepository _registerRepository; + + public RegisterRepositoryTests() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + _context = new ProfileDbContext(options); + _registerRepository = new RegisterRepository(_context); + } + + public void Dispose() + { + _context.Database.EnsureDeleted(); + _context.Dispose(); + } + + [Fact] + public async Task GetUserContactInfoAsync_ShouldReturnContactInfo_WhenFound() + { + var register = new Register + { + Reservation = true, + LanguageCode = "EN", + FnumberAk = "21102709516", + MobilePhoneNumber = "1234567890", + Description = "Test Description", + EmailAddress = "test@example.com", + MailboxAddress = "Test Mailbox Address" + }; + + await _context.Registers.AddAsync(register); + + await _context.SaveChangesAsync(); + + var result = await _registerRepository.GetUserContactInfoAsync("21102709516"); + + Assert.NotNull(result); + Assert.Equal(register.FnumberAk, result.FnumberAk); + Assert.Equal(register.Description, result.Description); + Assert.Equal(register.Reservation, result.Reservation); + Assert.Equal(register.LanguageCode, result.LanguageCode); + Assert.Equal(register.EmailAddress, result.EmailAddress); + Assert.Equal(register.MailboxAddress, result.MailboxAddress); + Assert.Equal(register.MobilePhoneNumber, result.MobilePhoneNumber); + } + + [Fact] + public async Task GetUserContactInfoAsync_ShouldReturnNull_WhenNotFound() + { + var result = await _registerRepository.GetUserContactInfoAsync("nonexistent"); + + Assert.Null(result); + } + + [Fact] + public async Task GetUserContactInfoAsync_ShouldReturnMultipleContacts_WhenFound() + { + var registers = new List + { + new() + { + Reservation = false, + LanguageCode = "NO", + FnumberAk = "03062701187", + MobilePhoneNumber = "1234567891", + Description = "Test Description 1", + EmailAddress = "test1@example.com", + MailboxAddress = "Test Mailbox Address 1" + }, + new() + { + Reservation = true, + LanguageCode = "EN", + FnumberAk = "02024333593", + MobilePhoneNumber = "1234567892", + Description = "Test Description 2", + EmailAddress = "test2@example.com", + MailboxAddress = "Test Mailbox Address 2" + } + }; + + await _context.Registers.AddRangeAsync(registers); + await _context.SaveChangesAsync(); + + var result = await _registerRepository.GetUserContactInfoAsync(["03062701187", "02024333593"]); + + Assert.Equal(2, result.Count()); + + var resultList = result.ToList(); + Assert.Contains(resultList, r => r.FnumberAk == "02024333593" && r.EmailAddress == "test2@example.com" && r.MobilePhoneNumber == "1234567892" && r.Description == "Test Description 2" && r.Reservation == true && r.MailboxAddress == "Test Mailbox Address 2" && r.LanguageCode == "EN"); + Assert.Contains(resultList, r => r.FnumberAk == "03062701187" && r.EmailAddress == "test1@example.com" && r.MobilePhoneNumber == "1234567891" && r.Description == "Test Description 1" && r.Reservation == false && r.MailboxAddress == "Test Mailbox Address 1" && r.LanguageCode == "NO"); + } + + [Fact] + public async Task GetUserContactInfoAsync_ShouldReturnEmpty_WhenNoneFound() + { + var result = await _registerRepository.GetUserContactInfoAsync(new[] { "nonexistent1", "nonexistent2" }); + + Assert.Empty(result); + } + + [Fact] + public async Task GetUserContactInfoAsync_ShouldReturnCorrectResults_ForValidAndInvalidNumbers() + { + var validRegister = new Register + { + Reservation = true, + LanguageCode = "EN", + FnumberAk = "21102709516", + MobilePhoneNumber = "1234567890", + EmailAddress = "valid@example.com", + Description = "Valid Test Description", + MailboxAddress = "Valid Mailbox Address" + }; + + await _context.Registers.AddAsync(validRegister); + await _context.SaveChangesAsync(); + + var invalidResult = await _registerRepository.GetUserContactInfoAsync("invalid"); + var validResult = await _registerRepository.GetUserContactInfoAsync("21102709516"); + + // Assert valid result + Assert.NotNull(validResult); + Assert.Equal(validRegister.FnumberAk, validResult.FnumberAk); + Assert.Equal(validRegister.Description, validResult.Description); + Assert.Equal(validRegister.Reservation, validResult.Reservation); + Assert.Equal(validRegister.LanguageCode, validResult.LanguageCode); + Assert.Equal(validRegister.EmailAddress, validResult.EmailAddress); + Assert.Equal(validRegister.MailboxAddress, validResult.MailboxAddress); + Assert.Equal(validRegister.MobilePhoneNumber, validResult.MobilePhoneNumber); + + // Assert invalid result + Assert.Null(invalidResult); + } +} diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/RegisterServiceTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/RegisterServiceTests.cs new file mode 100644 index 0000000..cbcfdec --- /dev/null +++ b/test/Altinn.Profile.Tests/Profile.Integrations/RegisterServiceTests.cs @@ -0,0 +1,103 @@ +#nullable enable + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using Altinn.Profile.Integrations.Entities; +using Altinn.Profile.Integrations.Repositories; +using Altinn.Profile.Integrations.Services; + +using AutoMapper; + +using Moq; + +using Xunit; + +namespace Altinn.Profile.Tests.Profile.Integrations; + +public class RegisterServiceTests +{ + private readonly Mock _mockRegisterRepository; + private readonly Mock _mockMapper; + private readonly RegisterService _registerService; + + public RegisterServiceTests() + { + _mockMapper = new Mock(); + _mockRegisterRepository = new Mock(); + _registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object); + } + + [Fact] + public async Task GetUserContactInfoAsync_ShouldReturnContactInfo_WhenValidNationalIdentityNumber() + { + var nationalIdentityNumber = "123"; + var register = new Register { EmailAddress = "test@example.com" }; + var mockUserContactInfo = new Mock(); + mockUserContactInfo.SetupGet(u => u.EmailAddress).Returns("test@example.com"); + + _mockRegisterRepository.Setup(repo => repo.GetUserContactInfoAsync(nationalIdentityNumber)) + .ReturnsAsync(register); + _mockMapper.Setup(m => m.Map(register)).Returns(mockUserContactInfo.Object); + + var result = await _registerService.GetUserContactInfoAsync(nationalIdentityNumber); + + Assert.NotNull(result); + Assert.Equal("test@example.com", result?.EmailAddress); + } + + [Fact] + public async Task GetUserContactInfoAsync_ShouldReturnNull_WhenInvalidNationalIdentityNumber() + { + _mockRegisterRepository.Setup(repo => repo.GetUserContactInfoAsync("invalid")) + .ReturnsAsync((Register?)null); + + var result = await _registerService.GetUserContactInfoAsync("invalid"); + + Assert.Null(result); + } + + [Fact] + public async Task GetUserContactInfoAsync_ShouldReturnEmpty_WhenNoNationalIdentityNumbersProvided() + { + var result = await _registerService.GetUserContactInfoAsync(new List()); + + Assert.Empty(result); + } + + [Fact] + public async Task GetUserContactInfoAsync_ShouldReturnCorrectResults_ForValidAndInvalidNumbers() + { + var validNationalIdentityNumber = "123"; + var invalidNationalIdentityNumber = "invalid"; + var register = new Register { EmailAddress = "test@example.com" }; + var mockUserContactInfo = new Mock(); + mockUserContactInfo.SetupGet(u => u.EmailAddress).Returns("test@example.com"); + + _mockRegisterRepository.Setup(repo => repo.GetUserContactInfoAsync(validNationalIdentityNumber)) + .ReturnsAsync(register); + _mockRegisterRepository.Setup(repo => repo.GetUserContactInfoAsync(invalidNationalIdentityNumber)) + .ReturnsAsync((Register?)null); + _mockMapper.Setup(m => m.Map(register)).Returns(mockUserContactInfo.Object); + + var validResult = await _registerService.GetUserContactInfoAsync(validNationalIdentityNumber); + var invalidResult = await _registerService.GetUserContactInfoAsync(invalidNationalIdentityNumber); + + // Assert valid result + Assert.NotNull(validResult); + Assert.Equal("test@example.com", validResult?.EmailAddress); + + // Assert invalid result + Assert.Null(invalidResult); + } + + [Fact] + public async Task GetUserContactInfoAsync_ShouldReturnNull_WhenNationalIdentityNumberIsNull() + { + var result = await _registerService.GetUserContactInfoAsync(string.Empty); + + Assert.Null(result); + } + +} From 635fe8917b57cc519902e70ded96255c37fdf087 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Thu, 3 Oct 2024 15:44:35 +0200 Subject: [PATCH 23/98] Improve the unit tests --- .../Entities/UserContactInfoProfile.cs | 6 +- .../Repositories/IRegisterRepository.cs | 7 - .../Repositories/RegisterRepository.cs | 11 -- .../Services/RegisterService.cs | 23 ++- .../Register/RegisterRepositoryTests.cs | 154 +++++++++++++++++ .../Register/RegisterServiceTests.cs | 161 ++++++++++++++++++ .../RegisterRepositoryTests.cs | 157 ----------------- .../RegisterServiceTests.cs | 103 ----------- 8 files changed, 329 insertions(+), 293 deletions(-) create mode 100644 test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs create mode 100644 test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs delete mode 100644 test/Altinn.Profile.Tests/Profile.Integrations/RegisterRepositoryTests.cs delete mode 100644 test/Altinn.Profile.Tests/Profile.Integrations/RegisterServiceTests.cs diff --git a/src/Altinn.Profile.Integrations/Entities/UserContactInfoProfile.cs b/src/Altinn.Profile.Integrations/Entities/UserContactInfoProfile.cs index 3e9dc6b..c707ce1 100644 --- a/src/Altinn.Profile.Integrations/Entities/UserContactInfoProfile.cs +++ b/src/Altinn.Profile.Integrations/Entities/UserContactInfoProfile.cs @@ -1,10 +1,10 @@ namespace Altinn.Profile.Integrations.Entities; /// -/// AutoMapper profile for configuring mappings between and . +/// AutoMapper profile for configuring mappings between and a class implementing the interface. /// /// -/// This profile defines the mapping rules required to transform a object into a object. +/// This profile defines the mapping rules required to transform a object into a class implementing the interface. /// It is used by AutoMapper to facilitate the conversion of data between these two models. /// public class UserContactInfoProfile : AutoMapper.Profile @@ -13,7 +13,7 @@ public class UserContactInfoProfile : AutoMapper.Profile /// Initializes a new instance of the class. /// /// - /// The constructor configures the mappings between the and classes. + /// The constructor configures the mappings between the class and a class implementing the interface. /// public UserContactInfoProfile() { diff --git a/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs index aaa7080..3118b8f 100644 --- a/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs @@ -10,13 +10,6 @@ namespace Altinn.Profile.Integrations.Repositories; /// public interface IRegisterRepository : IRepository { - /// - /// Gets the contact info for a single user by the national identity number asynchronously. - /// - /// The national identity number. - /// A task that represents the asynchronous operation. The task result contains the register data for the user. - Task GetUserContactInfoAsync(string nationalIdentityNumber); - /// /// Gets the contact info for multiple users by their national identity numbers asynchronously. /// diff --git a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs index 8ce0f8d..cb47dda 100644 --- a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs @@ -25,17 +25,6 @@ public RegisterRepository(ProfileDbContext context) : base(context) _context = context ?? throw new ArgumentNullException(nameof(context)); } - /// - /// Asynchronously retrieves the contact info for a single user based on the provided national identity number. - /// - /// The national identity number to filter the user contact point. - /// A task that represents the asynchronous operation. The task result contains the user contact point, or null if not found. - /// Thrown when the provided national identity number is null or empty. - public async Task GetUserContactInfoAsync(string nationalIdentityNumber) - { - return await _context.Registers.SingleOrDefaultAsync(k => k.FnumberAk == nationalIdentityNumber); - } - /// /// Asynchronously retrieves the contact info for multiple users based on the provided national identity numbers. /// diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs index 6dba8bb..6228d23 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -17,9 +17,9 @@ public class RegisterService : IRegisterService /// /// Initializes a new instance of the class. /// - /// The repository used for. + /// The mapper used for object mapping. /// The repository used for accessing register data. - /// Thrown when the or object is null. + /// Thrown when the or object is null. public RegisterService(IMapper mapper, IRegisterRepository registerRepository) { _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); @@ -40,7 +40,7 @@ public RegisterService(IMapper mapper, IRegisterRepository registerRepository) return null; } - var userContactInfo = await _registerRepository.GetUserContactInfoAsync(nationalIdentityNumber); + var userContactInfo = await _registerRepository.GetUserContactInfoAsync([nationalIdentityNumber]); return _mapper.Map(userContactInfo); } @@ -53,23 +53,22 @@ public RegisterService(IMapper mapper, IRegisterRepository registerRepository) /// public async Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers) { - // Check if the input collection is null or empty if (nationalIdentityNumbers == null || !nationalIdentityNumbers.Any()) { - return Enumerable.Empty(); // Return an empty collection + return []; } - // Validate that all national identity numbers are valid - if (!nationalIdentityNumbers.All(IsValidNationalIdentityNumber)) + // Filter out invalid national identity numbers + var validNationalIdentityNumbers = nationalIdentityNumbers.Where(IsValidNationalIdentityNumber).ToList(); + + if (!validNationalIdentityNumbers.Any()) { - return Enumerable.Empty(); // Return an empty collection for invalid numbers + return []; } - // Retrieve user contact information from the repository - var userContactInfo = await _registerRepository.GetUserContactInfoAsync(nationalIdentityNumbers); + var userContactInfo = await _registerRepository.GetUserContactInfoAsync(validNationalIdentityNumbers); - // Map the retrieved data to the desired interface type - return _mapper.Map>(userContactInfo); + return _mapper.Map>(userContactInfo); } /// diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs new file mode 100644 index 0000000..b1f2832 --- /dev/null +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using Altinn.Profile.Integrations.Entities; +using Altinn.Profile.Integrations.Persistence; +using Altinn.Profile.Integrations.Repositories; + +using Microsoft.EntityFrameworkCore; + +using Xunit; + +namespace Altinn.Profile.Tests.Profile.Integrations; + +/// +/// Contains unit tests for the class. +/// +public class RegisterRepositoryTests : IDisposable +{ + private readonly ProfileDbContext _context; + private readonly RegisterRepository _registerRepository; + + public RegisterRepositoryTests() + { + var options = new DbContextOptionsBuilder().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options; + _context = new ProfileDbContext(options); + _registerRepository = new RegisterRepository(_context); + } + + public void Dispose() + { + _context.Database.EnsureDeleted(); + _context.Dispose(); + GC.SuppressFinalize(this); + } + + [Fact] + public async Task GetUserContactInfoAsync_ReturnsContactInfo_WhenFound() + { + // Arrange + var register = CreateRegister("21102709516", "Test Description", "test@example.com", "Test Mailbox Address"); + + await AddRegisterToContext(register); + + // Act + var result = await _registerRepository.GetUserContactInfoAsync(["21102709516"]); + + // Assert + AssertSingleRegister(register, result.First()); + } + + [Fact] + public async Task GetUserContactInfoAsync_ReturnsCorrectResults_WhenValidAndInvalidNumbers() + { + // Arrange + var validRegister = CreateRegister("21102709516", "Valid Test Description", "valid@example.com", "Valid Mailbox Address"); + + await AddRegisterToContext(validRegister); + + // Act + var result = await _registerRepository.GetUserContactInfoAsync(["21102709516", "nonexistent2"]); + + // Assert valid result + AssertSingleRegister(validRegister, result.FirstOrDefault(r => r.FnumberAk == "21102709516")); + + // Assert invalid result + Assert.Null(result.FirstOrDefault(r => r.FnumberAk == "nonexistent2")); + } + + [Fact] + public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenNoneFound() + { + // Act + var result = await _registerRepository.GetUserContactInfoAsync(["nonexistent1", "nonexistent2"]); + + // Assert + Assert.Empty(result); + } + + [Fact] + public async Task GetUserContactInfoAsync_ReturnsMultipleContacts_WhenFound() + { + // Arrange + var registers = new List + { + CreateRegister("03062701187", "Test Description 1", "test1@example.com", "Test Mailbox Address 1", false), + CreateRegister("02024333593", "Test Description 2", "test2@example.com", "Test Mailbox Address 2", true) + }; + + await _context.Registers.AddRangeAsync(registers); + await _context.SaveChangesAsync(); + + // Act + var result = await _registerRepository.GetUserContactInfoAsync(["03062701187", "02024333593"]); + + // Assert + Assert.Equal(2, result.Count()); + + foreach (var register in registers) + { + var foundRegister = result.FirstOrDefault(r => r.FnumberAk == register.FnumberAk); + Assert.NotNull(foundRegister); + AssertRegisterProperties(register, foundRegister); + } + } + + [Fact] + public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenNotFound() + { + // Act + var result = await _registerRepository.GetUserContactInfoAsync(["nonexistent"]); + + // Assert + Assert.Empty(result); + } + + private async Task AddRegisterToContext(Register register) + { + await _context.Registers.AddAsync(register); + await _context.SaveChangesAsync(); + } + + private static void AssertSingleRegister(Register expected, Register actual) + { + Assert.NotNull(actual); + AssertRegisterProperties(expected, actual); + } + + private static void AssertRegisterProperties(Register expected, Register actual) + { + Assert.Equal(expected.FnumberAk, actual.FnumberAk); + Assert.Equal(expected.Description, actual.Description); + Assert.Equal(expected.Reservation, actual.Reservation); + Assert.Equal(expected.LanguageCode, actual.LanguageCode); + Assert.Equal(expected.EmailAddress, actual.EmailAddress); + Assert.Equal(expected.MailboxAddress, actual.MailboxAddress); + Assert.Equal(expected.MobilePhoneNumber, actual.MobilePhoneNumber); + } + + private static Register CreateRegister(string fnumberAk, string description, string emailAddress, string mailboxAddress, bool reservation = true) + { + return new Register + { + LanguageCode = "EN", + FnumberAk = fnumberAk, + Reservation = reservation, + Description = description, + EmailAddress = emailAddress, + MailboxAddress = mailboxAddress, + MobilePhoneNumber = "1234567890", + }; + } +} diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs new file mode 100644 index 0000000..61cd028 --- /dev/null +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs @@ -0,0 +1,161 @@ +#nullable enable + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using Altinn.Profile.Integrations.Entities; +using Altinn.Profile.Integrations.Repositories; +using Altinn.Profile.Integrations.Services; + +using AutoMapper; + +using Moq; + +using Xunit; + +namespace Altinn.Profile.Tests.Profile.Integrations; + +public class RegisterServiceTests +{ + private readonly Mock _mockRegisterRepository; + private readonly Mock _mockMapper; + + public RegisterServiceTests() + { + _mockMapper = new Mock(); + _mockRegisterRepository = new Mock(); + } + + [Fact] + public async Task GetUserContactInfoAsync_ReturnsMappedInfo_WhenValidIds() + { + // Arrange + var ids = new List { "11103048704", "30039302787" }; + + var firstRegister = new Register + { + Reservation = false, + LanguageCode = "NO", + FnumberAk = "11103048704", + MobilePhoneNumber = "1234567890", + EmailAddress = "test1@example.com", + Description = "Test Description 1", + MailboxAddress = "Test Mailbox Address 1" + }; + + var secondRegister = new Register + { + Reservation = false, + LanguageCode = "NO", + FnumberAk = "30039302787", + MobilePhoneNumber = "0987654321", + EmailAddress = "test2@example.com", + Description = "Test Description 2", + MailboxAddress = "Test Mailbox Address 2" + }; + + SetupRegisterRepository(firstRegister, secondRegister); + + var firstUserContactInfo = SetupUserContactInfo(firstRegister); + var secondUserContactInfo = SetupUserContactInfo(secondRegister); + + SetupMapper((firstRegister, firstUserContactInfo), (secondRegister, secondUserContactInfo)); + + // Act + var registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object); + var result = await registerService.GetUserContactInfoAsync(ids); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count()); + + AssertUserContactInfoMatches(firstRegister, result.ElementAt(0)); + AssertUserContactInfoMatches(secondRegister, result.ElementAt(1)); + } + + [Fact] + public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenInvalidNationalIds() + { + // Arrange + var nationalIdentityNumbers = new List { "invalid1", "invalid2" }; + + SetupRegisterRepository(); // Return empty + + // Act + var registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object); + var result = await registerService.GetUserContactInfoAsync(nationalIdentityNumbers); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + } + + [Fact] + public async Task GetUserContactInfoAsync_ReturnsMapped_WhenOneValidAndOneInvalidId() + { + // Arrange + var nationalIdentityNumbers = new List { "11103048704", "invalid" }; + + var validRegister = new Register + { + Reservation = false, + LanguageCode = "NO", + FnumberAk = "11103048704", + MobilePhoneNumber = "1234567890", + EmailAddress = "test@example.com", + Description = "Test Description", + MailboxAddress = "Test Mailbox Address" + }; + + SetupRegisterRepository(validRegister); + + var mockUserContactInfo = SetupUserContactInfo(validRegister); + + SetupMapper((validRegister, mockUserContactInfo)); + + // Act + var registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object); + var result = await registerService.GetUserContactInfoAsync(nationalIdentityNumbers); + + // Assert + Assert.NotNull(result); + Assert.Single(result); + AssertUserContactInfoMatches(validRegister, result.First()); + } + + private void SetupRegisterRepository(params Register[] registers) + { + _mockRegisterRepository.Setup(repo => repo.GetUserContactInfoAsync(It.IsAny>())).ReturnsAsync(registers.AsEnumerable()); + } + + private static Mock SetupUserContactInfo(Register register) + { + var mockUserContactInfo = new Mock(); + mockUserContactInfo.SetupGet(u => u.IsReserved).Returns(register.Reservation); + mockUserContactInfo.SetupGet(u => u.LanguageCode).Returns(register.LanguageCode); + mockUserContactInfo.SetupGet(u => u.EmailAddress).Returns(register.EmailAddress); + mockUserContactInfo.SetupGet(u => u.NationalIdentityNumber).Returns(register.FnumberAk); + mockUserContactInfo.SetupGet(u => u.MobilePhoneNumber).Returns(register.MobilePhoneNumber); + return mockUserContactInfo; + } + + private void SetupMapper(params (Register Register, Mock UserContactInfo)[] mappings) + { + _mockMapper.Setup(m => m.Map>(It.IsAny>())) + .Returns((IEnumerable registers) => + registers.Select(r => + mappings.FirstOrDefault(m => m.Register.FnumberAk == r.FnumberAk).UserContactInfo.Object) + .Where(u => u != null) + .Cast()); + } + + private static void AssertUserContactInfoMatches(Register expectedRegister, IUserContactInfo actualContactInfo) + { + Assert.Equal(expectedRegister.Reservation, actualContactInfo.IsReserved); + Assert.Equal(expectedRegister.EmailAddress, actualContactInfo.EmailAddress); + Assert.Equal(expectedRegister.LanguageCode, actualContactInfo.LanguageCode); + Assert.Equal(expectedRegister.FnumberAk, actualContactInfo.NationalIdentityNumber); + Assert.Equal(expectedRegister.MobilePhoneNumber, actualContactInfo.MobilePhoneNumber); + } +} diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/RegisterRepositoryTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/RegisterRepositoryTests.cs deleted file mode 100644 index 62dcb34..0000000 --- a/test/Altinn.Profile.Tests/Profile.Integrations/RegisterRepositoryTests.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -using Altinn.Profile.Integrations.Entities; -using Altinn.Profile.Integrations.Persistence; -using Altinn.Profile.Integrations.Repositories; -using Microsoft.EntityFrameworkCore; - -using Xunit; - -namespace Altinn.Profile.Tests.Profile.Integrations; - -/// -/// Contains unit tests for the class. -/// -public class RegisterRepositoryTests : IDisposable -{ - private readonly ProfileDbContext _context; - private readonly RegisterRepository _registerRepository; - - public RegisterRepositoryTests() - { - var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase(Guid.NewGuid().ToString()) - .Options; - - _context = new ProfileDbContext(options); - _registerRepository = new RegisterRepository(_context); - } - - public void Dispose() - { - _context.Database.EnsureDeleted(); - _context.Dispose(); - } - - [Fact] - public async Task GetUserContactInfoAsync_ShouldReturnContactInfo_WhenFound() - { - var register = new Register - { - Reservation = true, - LanguageCode = "EN", - FnumberAk = "21102709516", - MobilePhoneNumber = "1234567890", - Description = "Test Description", - EmailAddress = "test@example.com", - MailboxAddress = "Test Mailbox Address" - }; - - await _context.Registers.AddAsync(register); - - await _context.SaveChangesAsync(); - - var result = await _registerRepository.GetUserContactInfoAsync("21102709516"); - - Assert.NotNull(result); - Assert.Equal(register.FnumberAk, result.FnumberAk); - Assert.Equal(register.Description, result.Description); - Assert.Equal(register.Reservation, result.Reservation); - Assert.Equal(register.LanguageCode, result.LanguageCode); - Assert.Equal(register.EmailAddress, result.EmailAddress); - Assert.Equal(register.MailboxAddress, result.MailboxAddress); - Assert.Equal(register.MobilePhoneNumber, result.MobilePhoneNumber); - } - - [Fact] - public async Task GetUserContactInfoAsync_ShouldReturnNull_WhenNotFound() - { - var result = await _registerRepository.GetUserContactInfoAsync("nonexistent"); - - Assert.Null(result); - } - - [Fact] - public async Task GetUserContactInfoAsync_ShouldReturnMultipleContacts_WhenFound() - { - var registers = new List - { - new() - { - Reservation = false, - LanguageCode = "NO", - FnumberAk = "03062701187", - MobilePhoneNumber = "1234567891", - Description = "Test Description 1", - EmailAddress = "test1@example.com", - MailboxAddress = "Test Mailbox Address 1" - }, - new() - { - Reservation = true, - LanguageCode = "EN", - FnumberAk = "02024333593", - MobilePhoneNumber = "1234567892", - Description = "Test Description 2", - EmailAddress = "test2@example.com", - MailboxAddress = "Test Mailbox Address 2" - } - }; - - await _context.Registers.AddRangeAsync(registers); - await _context.SaveChangesAsync(); - - var result = await _registerRepository.GetUserContactInfoAsync(["03062701187", "02024333593"]); - - Assert.Equal(2, result.Count()); - - var resultList = result.ToList(); - Assert.Contains(resultList, r => r.FnumberAk == "02024333593" && r.EmailAddress == "test2@example.com" && r.MobilePhoneNumber == "1234567892" && r.Description == "Test Description 2" && r.Reservation == true && r.MailboxAddress == "Test Mailbox Address 2" && r.LanguageCode == "EN"); - Assert.Contains(resultList, r => r.FnumberAk == "03062701187" && r.EmailAddress == "test1@example.com" && r.MobilePhoneNumber == "1234567891" && r.Description == "Test Description 1" && r.Reservation == false && r.MailboxAddress == "Test Mailbox Address 1" && r.LanguageCode == "NO"); - } - - [Fact] - public async Task GetUserContactInfoAsync_ShouldReturnEmpty_WhenNoneFound() - { - var result = await _registerRepository.GetUserContactInfoAsync(new[] { "nonexistent1", "nonexistent2" }); - - Assert.Empty(result); - } - - [Fact] - public async Task GetUserContactInfoAsync_ShouldReturnCorrectResults_ForValidAndInvalidNumbers() - { - var validRegister = new Register - { - Reservation = true, - LanguageCode = "EN", - FnumberAk = "21102709516", - MobilePhoneNumber = "1234567890", - EmailAddress = "valid@example.com", - Description = "Valid Test Description", - MailboxAddress = "Valid Mailbox Address" - }; - - await _context.Registers.AddAsync(validRegister); - await _context.SaveChangesAsync(); - - var invalidResult = await _registerRepository.GetUserContactInfoAsync("invalid"); - var validResult = await _registerRepository.GetUserContactInfoAsync("21102709516"); - - // Assert valid result - Assert.NotNull(validResult); - Assert.Equal(validRegister.FnumberAk, validResult.FnumberAk); - Assert.Equal(validRegister.Description, validResult.Description); - Assert.Equal(validRegister.Reservation, validResult.Reservation); - Assert.Equal(validRegister.LanguageCode, validResult.LanguageCode); - Assert.Equal(validRegister.EmailAddress, validResult.EmailAddress); - Assert.Equal(validRegister.MailboxAddress, validResult.MailboxAddress); - Assert.Equal(validRegister.MobilePhoneNumber, validResult.MobilePhoneNumber); - - // Assert invalid result - Assert.Null(invalidResult); - } -} diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/RegisterServiceTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/RegisterServiceTests.cs deleted file mode 100644 index cbcfdec..0000000 --- a/test/Altinn.Profile.Tests/Profile.Integrations/RegisterServiceTests.cs +++ /dev/null @@ -1,103 +0,0 @@ -#nullable enable - -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -using Altinn.Profile.Integrations.Entities; -using Altinn.Profile.Integrations.Repositories; -using Altinn.Profile.Integrations.Services; - -using AutoMapper; - -using Moq; - -using Xunit; - -namespace Altinn.Profile.Tests.Profile.Integrations; - -public class RegisterServiceTests -{ - private readonly Mock _mockRegisterRepository; - private readonly Mock _mockMapper; - private readonly RegisterService _registerService; - - public RegisterServiceTests() - { - _mockMapper = new Mock(); - _mockRegisterRepository = new Mock(); - _registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object); - } - - [Fact] - public async Task GetUserContactInfoAsync_ShouldReturnContactInfo_WhenValidNationalIdentityNumber() - { - var nationalIdentityNumber = "123"; - var register = new Register { EmailAddress = "test@example.com" }; - var mockUserContactInfo = new Mock(); - mockUserContactInfo.SetupGet(u => u.EmailAddress).Returns("test@example.com"); - - _mockRegisterRepository.Setup(repo => repo.GetUserContactInfoAsync(nationalIdentityNumber)) - .ReturnsAsync(register); - _mockMapper.Setup(m => m.Map(register)).Returns(mockUserContactInfo.Object); - - var result = await _registerService.GetUserContactInfoAsync(nationalIdentityNumber); - - Assert.NotNull(result); - Assert.Equal("test@example.com", result?.EmailAddress); - } - - [Fact] - public async Task GetUserContactInfoAsync_ShouldReturnNull_WhenInvalidNationalIdentityNumber() - { - _mockRegisterRepository.Setup(repo => repo.GetUserContactInfoAsync("invalid")) - .ReturnsAsync((Register?)null); - - var result = await _registerService.GetUserContactInfoAsync("invalid"); - - Assert.Null(result); - } - - [Fact] - public async Task GetUserContactInfoAsync_ShouldReturnEmpty_WhenNoNationalIdentityNumbersProvided() - { - var result = await _registerService.GetUserContactInfoAsync(new List()); - - Assert.Empty(result); - } - - [Fact] - public async Task GetUserContactInfoAsync_ShouldReturnCorrectResults_ForValidAndInvalidNumbers() - { - var validNationalIdentityNumber = "123"; - var invalidNationalIdentityNumber = "invalid"; - var register = new Register { EmailAddress = "test@example.com" }; - var mockUserContactInfo = new Mock(); - mockUserContactInfo.SetupGet(u => u.EmailAddress).Returns("test@example.com"); - - _mockRegisterRepository.Setup(repo => repo.GetUserContactInfoAsync(validNationalIdentityNumber)) - .ReturnsAsync(register); - _mockRegisterRepository.Setup(repo => repo.GetUserContactInfoAsync(invalidNationalIdentityNumber)) - .ReturnsAsync((Register?)null); - _mockMapper.Setup(m => m.Map(register)).Returns(mockUserContactInfo.Object); - - var validResult = await _registerService.GetUserContactInfoAsync(validNationalIdentityNumber); - var invalidResult = await _registerService.GetUserContactInfoAsync(invalidNationalIdentityNumber); - - // Assert valid result - Assert.NotNull(validResult); - Assert.Equal("test@example.com", validResult?.EmailAddress); - - // Assert invalid result - Assert.Null(invalidResult); - } - - [Fact] - public async Task GetUserContactInfoAsync_ShouldReturnNull_WhenNationalIdentityNumberIsNull() - { - var result = await _registerService.GetUserContactInfoAsync(string.Empty); - - Assert.Null(result); - } - -} From e4b42b1dbba684e955ac75d0122e4eada19e7eb0 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Fri, 4 Oct 2024 08:08:47 +0200 Subject: [PATCH 24/98] Code refactoring --- .../RegisterToUserContactInfoProfile.cs} | 18 ++++++++++-------- .../Repositories/IRegisterRepository.cs | 6 ++++-- .../Repositories/RegisterRepository.cs | 8 +++++--- .../Services/RegisterService.cs | 2 +- .../Controllers/RegisterController.cs | 4 ++-- 5 files changed, 22 insertions(+), 16 deletions(-) rename src/Altinn.Profile.Integrations/{Entities/UserContactInfoProfile.cs => Mappings/RegisterToUserContactInfoProfile.cs} (67%) diff --git a/src/Altinn.Profile.Integrations/Entities/UserContactInfoProfile.cs b/src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactInfoProfile.cs similarity index 67% rename from src/Altinn.Profile.Integrations/Entities/UserContactInfoProfile.cs rename to src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactInfoProfile.cs index c707ce1..e447b1e 100644 --- a/src/Altinn.Profile.Integrations/Entities/UserContactInfoProfile.cs +++ b/src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactInfoProfile.cs @@ -1,23 +1,25 @@ -namespace Altinn.Profile.Integrations.Entities; +using Altinn.Profile.Integrations.Entities; + +namespace Altinn.Profile.Integrations.Mappings; /// -/// AutoMapper profile for configuring mappings between and a class implementing the interface. +/// AutoMapper profile for configuring mappings between and a class implementing the interface. /// /// -/// This profile defines the mapping rules required to transform a object into a class implementing the interface. +/// This profile defines the mapping rules required to transform a object into a class implementing the interface. /// It is used by AutoMapper to facilitate the conversion of data between these two models. /// -public class UserContactInfoProfile : AutoMapper.Profile +public class RegisterToUserContactInfoProfile : AutoMapper.Profile { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The constructor configures the mappings between the class and a class implementing the interface. + /// The constructor configures the mappings between the class and a class implementing the interface. /// - public UserContactInfoProfile() + public RegisterToUserContactInfoProfile() { - CreateMap() + CreateMap() .ForMember(dest => dest.IsReserved, opt => opt.MapFrom(src => src.Reservation)) .ForMember(dest => dest.EmailAddress, opt => opt.MapFrom(src => src.EmailAddress)) .ForMember(dest => dest.LanguageCode, opt => opt.MapFrom(src => src.LanguageCode)) diff --git a/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs index 3118b8f..a3e9ad0 100644 --- a/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs @@ -11,9 +11,11 @@ namespace Altinn.Profile.Integrations.Repositories; public interface IRegisterRepository : IRepository { /// - /// Gets the contact info for multiple users by their national identity numbers asynchronously. + /// Asynchronously retrieves the register data for multiple users by their national identity numbers. /// /// The collection of national identity numbers. - /// A task that represents the asynchronous operation. The task result contains a collection of register data for the users. + /// + /// A task that represents the asynchronous operation. The task result contains a collection of register data for the users. + /// Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers); } diff --git a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs index cb47dda..532a049 100644 --- a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs @@ -26,10 +26,12 @@ public RegisterRepository(ProfileDbContext context) : base(context) } /// - /// Asynchronously retrieves the contact info for multiple users based on the provided national identity numbers. + /// Asynchronously retrieves the register data for multiple users by their national identity numbers. /// - /// A collection of national identity numbers to filter the user contact points. - /// A task that represents the asynchronous operation. The task result contains a collection of user contact points. + /// The collection of national identity numbers. + /// + /// A task that represents the asynchronous operation. The task result contains a collection of register data for the users. + /// public async Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers) { return await _context.Registers.Where(k => nationalIdentityNumbers.Contains(k.FnumberAk)).ToListAsync(); diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs index 6228d23..d1726ba 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -68,7 +68,7 @@ public async Task> GetUserContactInfoAsync(IEnumer var userContactInfo = await _registerRepository.GetUserContactInfoAsync(validNationalIdentityNumbers); - return _mapper.Map>(userContactInfo); + return _mapper.Map>(userContactInfo); } /// diff --git a/src/Altinn.Profile/Controllers/RegisterController.cs b/src/Altinn.Profile/Controllers/RegisterController.cs index 6b15a38..5dda94f 100644 --- a/src/Altinn.Profile/Controllers/RegisterController.cs +++ b/src/Altinn.Profile/Controllers/RegisterController.cs @@ -37,9 +37,9 @@ public RegisterController(IRegisterService registerService) /// A collection of national identity numbers. /// A task that represents the asynchronous operation. The task result contains a collection of user contact points. [HttpPost] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task>> GetByNationalIdentityNumbersAsync([FromBody] IEnumerable nationalIdentityNumbers) { var data = await _registerService.GetUserContactInfoAsync(nationalIdentityNumbers); From 98d77865b8e9ea2246ee134ef95a0f16cafa71a0 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Fri, 4 Oct 2024 08:24:54 +0200 Subject: [PATCH 25/98] Add a validation rule to validate national security numbers --- src/Altinn.Profile.Core/StringExtensions.cs | 113 ++++++++++++++++++ .../Services/IRegisterService.cs | 10 +- .../Services/RegisterService.cs | 23 +--- 3 files changed, 124 insertions(+), 22 deletions(-) diff --git a/src/Altinn.Profile.Core/StringExtensions.cs b/src/Altinn.Profile.Core/StringExtensions.cs index 864e047..1e84b3a 100644 --- a/src/Altinn.Profile.Core/StringExtensions.cs +++ b/src/Altinn.Profile.Core/StringExtensions.cs @@ -1,3 +1,5 @@ +using System.Collections.Concurrent; +using System.Globalization; using System.Text.RegularExpressions; namespace Altinn.Profile.Core; @@ -46,6 +48,107 @@ public static bool IsDigitsOnly(this string input) return WhitespaceRegex().Replace(stringToClean, string.Empty); } + /// + /// Determines whether a given string represents a valid format for a Norwegian Social Security Number (SSN). + /// + /// The Norwegian Social Security Number (SSN) to validate. + /// Indicates whether to validate the control digits. + /// + /// true if the given string represents a valid format for a Norwegian Social Security Number (SSN) and, if specified, the control digits are valid; otherwise, false. + /// + /// + /// A valid Norwegian Social Security Number (SSN) is an 11-digit number where: + /// - The first six digits represent the date of birth in the format DDMMYY. + /// - The next three digits are an individual number where the first digit indicates the century of birth. + /// - The last two digits are control digits. + /// + /// Thrown when the individual number part of the SSN cannot be parsed into an integer. + /// Thrown when the parsed date is outside the range of DateTime. + public static bool IsValidSocialSecurityNumber(this string socialSecurityNumber, bool controlDigits = true) + { + if (string.IsNullOrWhiteSpace(socialSecurityNumber) || socialSecurityNumber.Length != 11) + { + return false; + } + + // Return the cached result if the given string has been checked once. + if (CachedSocialSecurityNumber.TryGetValue(socialSecurityNumber, out var cachedResult)) + { + return cachedResult; + } + + ReadOnlySpan socialSecurityNumberSpan = socialSecurityNumber.AsSpan(); + + for (int i = 0; i < socialSecurityNumberSpan.Length; i++) + { + if (!char.IsDigit(socialSecurityNumberSpan[i])) + { + return false; + } + } + + // Extract parts of the Social Security Number (SSN) using slicing. + ReadOnlySpan datePart = socialSecurityNumberSpan[..6]; + ReadOnlySpan controlDigitsPart = socialSecurityNumberSpan[9..11]; + ReadOnlySpan individualNumberPart = socialSecurityNumberSpan[6..9]; + + // If parsing the individual number part fails, return false. + if (!int.TryParse(individualNumberPart, out _)) + { + return false; + } + + // Validate the date part. + if (!DateTime.TryParseExact(datePart.ToString(), "ddMMyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out _)) + { + return false; + } + + var isValidSocialSecurityNumber = !controlDigits || CalculateControlDigits(socialSecurityNumberSpan.Slice(0, 9).ToString()) == controlDigitsPart.ToString(); + + CachedSocialSecurityNumber.TryAdd(socialSecurityNumber, isValidSocialSecurityNumber); + + return isValidSocialSecurityNumber; + } + + + /// + /// Calculates the control digits used to validate a Norwegian Social Security Number. + /// + /// The first nine digits of the Social Security Number. + /// A represents the two control digits. + private static string CalculateControlDigits(string firstNineDigits) + { + int[] weightsFirst = [3, 7, 6, 1, 8, 9, 4, 5, 2]; + + int[] weightsSecond = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]; + + int firstControlDigit = CalculateControlDigit(firstNineDigits, weightsFirst); + + int secondControlDigit = CalculateControlDigit(firstNineDigits + firstControlDigit, weightsSecond); + + return $"{firstControlDigit}{secondControlDigit}"; + } + + /// + /// Calculates a control digit using the specified weights. + /// + /// The digits to use in the calculation. + /// The weights for each digit. + /// An represents the calculated control digit. + private static int CalculateControlDigit(string digits, int[] weights) + { + int sum = 0; + + for (int i = 0; i < weights.Length; i++) + { + sum += (int)char.GetNumericValue(digits[i]) * weights[i]; + } + + int remainder = sum % 11; + return remainder == 0 ? 0 : 11 - remainder; + } + /// /// Generates a compiled regular expression for matching all whitespace characters in a string. /// @@ -63,4 +166,14 @@ public static bool IsDigitsOnly(this string input) /// [GeneratedRegex(@"^\d+$", RegexOptions.Compiled)] private static partial Regex DigitsOnlyRegex(); + + /// + /// A cache for storing the validation results of Norwegian Social Security Numbers (SSNs). + /// + /// + /// This cache helps to avoid redundant validation checks for SSNs that have already been processed. + /// It maps the SSN as a string to a boolean indicating whether the SSN is valid (true) or not (false). + /// Utilizing this cache can significantly improve performance for applications that frequently validate the same SSNs. + /// + private static ConcurrentDictionary CachedSocialSecurityNumber => new(); } diff --git a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs index be466ff..72cde12 100644 --- a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs @@ -5,25 +5,25 @@ namespace Altinn.Profile.Integrations.Services; /// -/// Interface for a service that handles operations related to user register data. +/// Defines a service for handling operations related to user register data. /// public interface IRegisterService { /// - /// Asynchronously retrieves the contact point information for a user based on their national identity number. + /// Asynchronously retrieves the contact information for a user based on their national identity number. /// /// The national identity number of the user. /// - /// A task that represents the asynchronous operation. The task result contains the user's register information, or null if not found. + /// A task that represents the asynchronous operation. The task result contains the user's contact information, or null if not found. /// Task GetUserContactInfoAsync(string nationalIdentityNumber); /// - /// Asynchronously retrieves the contact point information for multiple users based on their national identity numbers. + /// Asynchronously retrieves the contact information for multiple users based on their national identity numbers. /// /// A collection of national identity numbers. /// - /// A task that represents the asynchronous operation. The task result contains a collection of user register information, or an empty collection if none are found. + /// A task that represents the asynchronous operation. The task result contains a collection of user contact information, or an empty collection if none are found. /// Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers); } diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs index d1726ba..344ec39 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -27,15 +27,15 @@ public RegisterService(IMapper mapper, IRegisterRepository registerRepository) } /// - /// Asynchronously retrieves the contact point information for a user based on their national identity number. + /// Asynchronously retrieves the contact information for a user based on their national identity number. /// /// The national identity number of the user. /// - /// A task that represents the asynchronous operation. The task result contains the user's register information, or null if not found. + /// A task that represents the asynchronous operation. The task result contains the user's contact information, or null if not found. /// public async Task GetUserContactInfoAsync(string nationalIdentityNumber) { - if (!IsValidNationalIdentityNumber(nationalIdentityNumber)) + if (!nationalIdentityNumber.IsValidSocialSecurityNumber()) { return null; } @@ -45,7 +45,7 @@ public RegisterService(IMapper mapper, IRegisterRepository registerRepository) } /// - /// Asynchronously retrieves the contact point information for multiple users based on their national identity numbers. + /// Asynchronously retrieves the contact information for multiple users based on their national identity numbers. /// /// A collection of national identity numbers. /// @@ -59,8 +59,7 @@ public async Task> GetUserContactInfoAsync(IEnumer } // Filter out invalid national identity numbers - var validNationalIdentityNumbers = nationalIdentityNumbers.Where(IsValidNationalIdentityNumber).ToList(); - + var validNationalIdentityNumbers = nationalIdentityNumbers.Where(e => e.IsValidSocialSecurityNumber()); if (!validNationalIdentityNumbers.Any()) { return []; @@ -68,16 +67,6 @@ public async Task> GetUserContactInfoAsync(IEnumer var userContactInfo = await _registerRepository.GetUserContactInfoAsync(validNationalIdentityNumbers); - return _mapper.Map>(userContactInfo); - } - - /// - /// Validates the national identity number. - /// - /// The national identity number to validate. - /// True if the national identity number is valid; otherwise, false. - private bool IsValidNationalIdentityNumber(string? nationalIdentityNumber) - { - return !string.IsNullOrWhiteSpace(nationalIdentityNumber) && nationalIdentityNumber.IsDigitsOnly(); + return _mapper.Map>(userContactInfo); } } From 5f0cc46c797578697c995f00c451acb8868b8d73 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Fri, 4 Oct 2024 08:30:00 +0200 Subject: [PATCH 26/98] Rearrange core extensions --- .../{ => Extensions}/ServiceCollectionExtensions.cs | 2 +- src/Altinn.Profile.Core/{ => Extensions}/StringExtensions.cs | 3 +-- src/Altinn.Profile.Integrations/Services/RegisterService.cs | 2 +- src/Altinn.Profile/Program.cs | 3 +-- 4 files changed, 4 insertions(+), 6 deletions(-) rename src/Altinn.Profile.Core/{ => Extensions}/ServiceCollectionExtensions.cs (96%) rename src/Altinn.Profile.Core/{ => Extensions}/StringExtensions.cs (99%) diff --git a/src/Altinn.Profile.Core/ServiceCollectionExtensions.cs b/src/Altinn.Profile.Core/Extensions/ServiceCollectionExtensions.cs similarity index 96% rename from src/Altinn.Profile.Core/ServiceCollectionExtensions.cs rename to src/Altinn.Profile.Core/Extensions/ServiceCollectionExtensions.cs index c72d26b..0461968 100644 --- a/src/Altinn.Profile.Core/ServiceCollectionExtensions.cs +++ b/src/Altinn.Profile.Core/Extensions/ServiceCollectionExtensions.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -namespace Altinn.Profile.Core; +namespace Altinn.Profile.Core.Extensions; /// /// Extension class for diff --git a/src/Altinn.Profile.Core/StringExtensions.cs b/src/Altinn.Profile.Core/Extensions/StringExtensions.cs similarity index 99% rename from src/Altinn.Profile.Core/StringExtensions.cs rename to src/Altinn.Profile.Core/Extensions/StringExtensions.cs index 1e84b3a..ebdb9de 100644 --- a/src/Altinn.Profile.Core/StringExtensions.cs +++ b/src/Altinn.Profile.Core/Extensions/StringExtensions.cs @@ -2,7 +2,7 @@ using System.Globalization; using System.Text.RegularExpressions; -namespace Altinn.Profile.Core; +namespace Altinn.Profile.Core.Extensions; /// /// Extension class for to add more members. @@ -111,7 +111,6 @@ public static bool IsValidSocialSecurityNumber(this string socialSecurityNumber, return isValidSocialSecurityNumber; } - /// /// Calculates the control digits used to validate a Norwegian Social Security Number. /// diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs index 344ec39..10fe280 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -1,4 +1,4 @@ -using Altinn.Profile.Core; +using Altinn.Profile.Core.Extensions; using Altinn.Profile.Integrations.Entities; using Altinn.Profile.Integrations.Repositories; diff --git a/src/Altinn.Profile/Program.cs b/src/Altinn.Profile/Program.cs index 4b7928e..2eb4f19 100644 --- a/src/Altinn.Profile/Program.cs +++ b/src/Altinn.Profile/Program.cs @@ -7,11 +7,10 @@ using Altinn.Common.AccessToken.Configuration; using Altinn.Common.AccessToken.Services; using Altinn.Profile.Configuration; -using Altinn.Profile.Core; +using Altinn.Profile.Core.Extensions; using Altinn.Profile.Filters; using Altinn.Profile.Health; using Altinn.Profile.Integrations.Extensions; -using Altinn.Profile.Integrations.Repositories; using AltinnCore.Authentication.JwtCookie; From b595849a200fa0eb0d0d874410a7eaa0b47ca48e Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Fri, 4 Oct 2024 12:24:58 +0200 Subject: [PATCH 27/98] Organize the matched SSN and the unmatched SSN in a respone object --- .../Services/RegisterService.cs | 2 +- .../Controllers/RegisterController.cs | 67 ---------------- .../UserNotificationsController.cs | 76 +++++++++++++++++++ .../Models/UserContactPointLookup.cs | 2 +- ...oint.cs => UserNotificationPreferences.cs} | 16 ++-- .../UserNotificationPreferencesResponse.cs | 37 +++++++++ 6 files changed, 123 insertions(+), 77 deletions(-) delete mode 100644 src/Altinn.Profile/Controllers/RegisterController.cs create mode 100644 src/Altinn.Profile/Controllers/UserNotificationsController.cs rename src/Altinn.Profile/Models/{UserContactPoint.cs => UserNotificationPreferences.cs} (69%) create mode 100644 src/Altinn.Profile/Models/UserNotificationPreferencesResponse.cs diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs index 10fe280..fa90aef 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -67,6 +67,6 @@ public async Task> GetUserContactInfoAsync(IEnumer var userContactInfo = await _registerRepository.GetUserContactInfoAsync(validNationalIdentityNumbers); - return _mapper.Map>(userContactInfo); + return _mapper.Map>(userContactInfo); } } diff --git a/src/Altinn.Profile/Controllers/RegisterController.cs b/src/Altinn.Profile/Controllers/RegisterController.cs deleted file mode 100644 index 5dda94f..0000000 --- a/src/Altinn.Profile/Controllers/RegisterController.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -using Altinn.Profile.Integrations.Services; -using Altinn.Profile.Models; - -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; - -namespace Altinn.Profile.Controllers -{ - /// - /// Handles the presentation of unhandled exceptions during the execution of a request. - /// - [ApiController] - [Route("profile/api/v1/user")] - [Consumes("application/json")] - [Produces("application/json")] - public class RegisterController : ControllerBase - { - private readonly IRegisterService _registerService; - - /// - /// Values the tuple. - /// - /// The type of the register service. - /// - public RegisterController(IRegisterService registerService) - { - _registerService = registerService; - } - - /// - /// Gets the user contact points by a collection of national identity numbers. - /// - /// A collection of national identity numbers. - /// A task that represents the asynchronous operation. The task result contains a collection of user contact points. - [HttpPost] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - public async Task>> GetByNationalIdentityNumbersAsync([FromBody] IEnumerable nationalIdentityNumbers) - { - var data = await _registerService.GetUserContactInfoAsync(nationalIdentityNumbers); - if (data == null || data.Count() == 0) - { - return NotFound(); - } - - var result = new List(); - foreach (var item in data) - { - result.Add(new UserContactPoint - { - Reservation = item.IsReserved, - EmailAddress = item.EmailAddress, - LanguageCode = item.LanguageCode, - MobilePhoneNumber = item.MobilePhoneNumber, - NationalIdentityNumber = item.NationalIdentityNumber, - }); - } - - return Ok(result); - } - } -} diff --git a/src/Altinn.Profile/Controllers/UserNotificationsController.cs b/src/Altinn.Profile/Controllers/UserNotificationsController.cs new file mode 100644 index 0000000..fa98564 --- /dev/null +++ b/src/Altinn.Profile/Controllers/UserNotificationsController.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Altinn.Profile.Core.Extensions; +using Altinn.Profile.Integrations.Services; +using Altinn.Profile.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Altinn.Profile.Controllers; + +/// +/// Controller to retrieve user notification preferences. +/// +[ApiController] +[Route("profile/api/v1/user-notification")] +[Consumes("application/json")] +[Produces("application/json")] +public class UserNotificationsController : ControllerBase +{ + private readonly IRegisterService _registerService; + + /// + /// Initializes a new instance of the class. + /// + /// The register service. + /// Thrown when the is null. + public UserNotificationsController(IRegisterService registerService) + { + _registerService = registerService ?? throw new ArgumentNullException(nameof(registerService)); + } + + /// + /// Retrieves notification preferences for users based on their national identity numbers. + /// + /// A collection of national identity numbers. + /// A task that represents the asynchronous operation, containing a response with user notification preferences. + [HttpPost("GetNotificationPreferences")] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(UserNotificationPreferencesResponse), StatusCodes.Status200OK)] + public async Task> GetNotificationPreferences([FromBody] UserContactPointLookup request) + { + if (request?.NationalIdentityNumbers?.Count == 0) + { + return BadRequest("No national identity numbers provided."); + } + + var validSSNs = request.NationalIdentityNumbers.Where(e => e.IsValidSocialSecurityNumber()).ToList(); + var invalidSSNs = request.NationalIdentityNumbers.Except(validSSNs).ToList(); + + var notificationPreferences = await _registerService.GetUserContactInfoAsync(validSSNs); + var matches = notificationPreferences.Select(np => new UserNotificationPreferences + { + NationalIdentityNumber = np.NationalIdentityNumber, + Reservation = np.IsReserved, + EmailAddress = np.EmailAddress, + LanguageCode = np.LanguageCode, + MobilePhoneNumber = np.MobilePhoneNumber, + }).ToList(); + + var noMatches = invalidSSNs.Select(invalid => new UserNotificationPreferences + { + NationalIdentityNumber = invalid + }).ToList(); + + noMatches.AddRange(validSSNs.Except(matches.Select(m => m.NationalIdentityNumber)).Select(item => new UserNotificationPreferences + { + NationalIdentityNumber = item + })); + + var response = new UserNotificationPreferencesResponse(matches, noMatches); + return Ok(response); + } +} diff --git a/src/Altinn.Profile/Models/UserContactPointLookup.cs b/src/Altinn.Profile/Models/UserContactPointLookup.cs index 1451e30..f7c38c4 100644 --- a/src/Altinn.Profile/Models/UserContactPointLookup.cs +++ b/src/Altinn.Profile/Models/UserContactPointLookup.cs @@ -3,7 +3,7 @@ namespace Altinn.Profile.Models; /// -/// A class respresenting a user contact point lookup object +/// A class representing a user contact point lookup object /// public class UserContactPointLookup { diff --git a/src/Altinn.Profile/Models/UserContactPoint.cs b/src/Altinn.Profile/Models/UserNotificationPreferences.cs similarity index 69% rename from src/Altinn.Profile/Models/UserContactPoint.cs rename to src/Altinn.Profile/Models/UserNotificationPreferences.cs index 98ed0c5..a699278 100644 --- a/src/Altinn.Profile/Models/UserContactPoint.cs +++ b/src/Altinn.Profile/Models/UserNotificationPreferences.cs @@ -5,39 +5,39 @@ namespace Altinn.Profile.Models; /// -/// Represents a user contact point. +/// Represents a user's notification preferences. /// -public record UserContactPoint +public record UserNotificationPreferences { /// - /// Gets or sets the national identity number of the user. + /// Gets the national identity number of the user. /// - [JsonPropertyName("nationalIdentityNumber")] + [JsonPropertyName("nationalIdentityNumbers")] public required string NationalIdentityNumber { get; init; } /// - /// Gets or sets a value indicating whether the user opts out of being contacted. + /// Gets a value indicating whether the user opts out of being contacted. /// [JsonPropertyName("reservation")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? Reservation { get; init; } /// - /// Gets or sets the mobile phone number of the user. + /// Gets the mobile phone number of the user. /// [JsonPropertyName("mobilePhoneNumber")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? MobilePhoneNumber { get; init; } /// - /// Gets or sets the email address of the user. + /// Gets the email address of the user. /// [JsonPropertyName("emailAddress")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? EmailAddress { get; init; } /// - /// Gets or sets the language code of the user. + /// Gets the language code of the user. /// [JsonPropertyName("languageCode")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] diff --git a/src/Altinn.Profile/Models/UserNotificationPreferencesResponse.cs b/src/Altinn.Profile/Models/UserNotificationPreferencesResponse.cs new file mode 100644 index 0000000..b67d3c5 --- /dev/null +++ b/src/Altinn.Profile/Models/UserNotificationPreferencesResponse.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Altinn.Profile.Models; + +/// +/// Represents the response containing user notification preferences. +/// +public record UserNotificationPreferencesResponse +{ + /// + /// Gets the list of user notification preferences that are matched. + /// + [JsonPropertyName("matched")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyList Matched { get; init; } = []; + + /// + /// Gets the list of user notification preferences that are invalid or unmatched. + /// + [JsonPropertyName("Unmatched")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyList Unmatched { get; init; } = []; + + /// + /// Initializes a new instance of the record. + /// + /// A list of user notification preferences that are matched. + /// A list of user notification preferences that are invalid or unmatched. + public UserNotificationPreferencesResponse( + List matched, + List unmatched) + { + Matched = matched ?? []; + Unmatched = unmatched ?? []; + } +} From 0dbe1390bb52319f0a676098052eabf95a85ab18 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Fri, 4 Oct 2024 22:28:09 +0200 Subject: [PATCH 28/98] Implement validation layer and refactor existing code. --- .../{IUserContactInfo.cs => IUserContact.cs} | 2 +- .../Entities/IUserContactResult.cs | 21 +++++++++ .../{UserContactInfo.cs => UserContact.cs} | 2 +- .../Entities/UserContactResult.cs | 21 +++++++++ .../Extensions/ServiceCollectionExtensions.cs | 8 ++-- .../RegisterToUserContactInfoProfile.cs | 29 ------------ .../Mappings/RegisterToUserContactProfile.cs | 26 +++++++++++ .../Repositories/IRegisterRepository.cs | 4 +- .../Repositories/RegisterRepository.cs | 8 +++- .../INationalIdentityNumberChecker.cs | 31 +++++++++++++ .../Services/IRegisterService.cs | 4 +- .../Services/NationalIdentityNumberChecker.cs | 43 +++++++++++++++++ .../Services/RegisterService.cs | 45 ++++++++++-------- .../UserNotificationsController.cs | 46 +++++++++---------- ...onPreferences.cs => UserContactDetails.cs} | 13 ++++-- .../Models/UserContactDetailsResult.cs | 41 +++++++++++++++++ .../Models/UserContactPointLookup.cs | 4 +- .../UserNotificationPreferencesResponse.cs | 37 --------------- .../Register/RegisterServiceTests.cs | 18 ++++---- 19 files changed, 267 insertions(+), 136 deletions(-) rename src/Altinn.Profile.Integrations/Entities/{IUserContactInfo.cs => IUserContact.cs} (97%) create mode 100644 src/Altinn.Profile.Integrations/Entities/IUserContactResult.cs rename src/Altinn.Profile.Integrations/Entities/{UserContactInfo.cs => UserContact.cs} (96%) create mode 100644 src/Altinn.Profile.Integrations/Entities/UserContactResult.cs delete mode 100644 src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactInfoProfile.cs create mode 100644 src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactProfile.cs create mode 100644 src/Altinn.Profile.Integrations/Services/INationalIdentityNumberChecker.cs create mode 100644 src/Altinn.Profile.Integrations/Services/NationalIdentityNumberChecker.cs rename src/Altinn.Profile/Models/{UserNotificationPreferences.cs => UserContactDetails.cs} (63%) create mode 100644 src/Altinn.Profile/Models/UserContactDetailsResult.cs delete mode 100644 src/Altinn.Profile/Models/UserNotificationPreferencesResponse.cs diff --git a/src/Altinn.Profile.Integrations/Entities/IUserContactInfo.cs b/src/Altinn.Profile.Integrations/Entities/IUserContact.cs similarity index 97% rename from src/Altinn.Profile.Integrations/Entities/IUserContactInfo.cs rename to src/Altinn.Profile.Integrations/Entities/IUserContact.cs index 42ddd45..d3059b3 100644 --- a/src/Altinn.Profile.Integrations/Entities/IUserContactInfo.cs +++ b/src/Altinn.Profile.Integrations/Entities/IUserContact.cs @@ -5,7 +5,7 @@ namespace Altinn.Profile.Integrations.Entities; /// /// Represents a user's contact information. /// -public interface IUserContactInfo +public interface IUserContact { /// /// Gets the national identity number of the user. diff --git a/src/Altinn.Profile.Integrations/Entities/IUserContactResult.cs b/src/Altinn.Profile.Integrations/Entities/IUserContactResult.cs new file mode 100644 index 0000000..f9d590b --- /dev/null +++ b/src/Altinn.Profile.Integrations/Entities/IUserContactResult.cs @@ -0,0 +1,21 @@ +#nullable enable + +using System.Collections.Immutable; + +namespace Altinn.Profile.Integrations.Entities; + +/// +/// Defines the result of a user contact information lookup. +/// +public interface IUserContactResult +{ + /// + /// Gets a list of user contact information that was successfully matched during the lookup. + /// + ImmutableList? MatchedUserContact { get; init; } + + /// + /// Gets a list of user contact information that was not matched during the lookup. + /// + ImmutableList? UnmatchedUserContact { get; init; } +} diff --git a/src/Altinn.Profile.Integrations/Entities/UserContactInfo.cs b/src/Altinn.Profile.Integrations/Entities/UserContact.cs similarity index 96% rename from src/Altinn.Profile.Integrations/Entities/UserContactInfo.cs rename to src/Altinn.Profile.Integrations/Entities/UserContact.cs index d9d7359..64ae7bc 100644 --- a/src/Altinn.Profile.Integrations/Entities/UserContactInfo.cs +++ b/src/Altinn.Profile.Integrations/Entities/UserContact.cs @@ -5,7 +5,7 @@ namespace Altinn.Profile.Integrations.Entities; /// /// Represents a user's contact information. /// -public class UserContactInfo : IUserContactInfo +public record UserContact : IUserContact { /// /// Gets the national identity number of the user. diff --git a/src/Altinn.Profile.Integrations/Entities/UserContactResult.cs b/src/Altinn.Profile.Integrations/Entities/UserContactResult.cs new file mode 100644 index 0000000..0b8a75b --- /dev/null +++ b/src/Altinn.Profile.Integrations/Entities/UserContactResult.cs @@ -0,0 +1,21 @@ +#nullable enable + +using System.Collections.Immutable; + +namespace Altinn.Profile.Integrations.Entities; + +/// +/// Represents the result of a user contact information lookup, containing matched and unmatched entries. +/// +public record UserContactResult : IUserContactResult +{ + /// + /// Gets a list of user contact information that was successfully matched during the lookup. + /// + public ImmutableList? MatchedUserContact { get; init; } + + /// + /// Gets a list of user contact information that was not matched during the lookup. + /// + public ImmutableList? UnmatchedUserContact { get; init; } +} diff --git a/src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs index ff9442b..72a92e9 100644 --- a/src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs @@ -15,10 +15,6 @@ namespace Altinn.Profile.Integrations.Extensions; /// public static class ServiceCollectionExtensions { - private const string _profileDbAdminUserNameKey = "PostgreSqlSettings--ProfileDbAdminUserName"; - private const string _profileDbAdminPasswordKey = "PostgreSqlSettings--ProfileDbAdminPassword"; - private const string _profileDbConnectionStringKey = "PostgreSqlSettings--ProfileDbConnectionString"; - /// /// Adds SBL Bridge clients and configurations to the DI container. /// @@ -55,8 +51,12 @@ public static void AddRegisterService(this IServiceCollection services, IConfigu } services.AddDbContext(options => options.UseNpgsql(connectionString)); + services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); + services.AddScoped(); services.AddScoped(); + + services.AddSingleton(); } } diff --git a/src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactInfoProfile.cs b/src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactInfoProfile.cs deleted file mode 100644 index e447b1e..0000000 --- a/src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactInfoProfile.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Altinn.Profile.Integrations.Entities; - -namespace Altinn.Profile.Integrations.Mappings; - -/// -/// AutoMapper profile for configuring mappings between and a class implementing the interface. -/// -/// -/// This profile defines the mapping rules required to transform a object into a class implementing the interface. -/// It is used by AutoMapper to facilitate the conversion of data between these two models. -/// -public class RegisterToUserContactInfoProfile : AutoMapper.Profile -{ - /// - /// Initializes a new instance of the class. - /// - /// - /// The constructor configures the mappings between the class and a class implementing the interface. - /// - public RegisterToUserContactInfoProfile() - { - CreateMap() - .ForMember(dest => dest.IsReserved, opt => opt.MapFrom(src => src.Reservation)) - .ForMember(dest => dest.EmailAddress, opt => opt.MapFrom(src => src.EmailAddress)) - .ForMember(dest => dest.LanguageCode, opt => opt.MapFrom(src => src.LanguageCode)) - .ForMember(dest => dest.NationalIdentityNumber, opt => opt.MapFrom(src => src.FnumberAk)) - .ForMember(dest => dest.MobilePhoneNumber, opt => opt.MapFrom(src => src.MobilePhoneNumber)); - } -} diff --git a/src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactProfile.cs b/src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactProfile.cs new file mode 100644 index 0000000..5c922b7 --- /dev/null +++ b/src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactProfile.cs @@ -0,0 +1,26 @@ +using Altinn.Profile.Integrations.Entities; + +namespace Altinn.Profile.Integrations.Mappings; + +/// +/// AutoMapper profile for mapping between and . +/// +/// +/// This profile defines the mapping rules to convert a object into a instance. +/// +public class RegisterToUserContactProfile : AutoMapper.Profile +{ + /// + /// Initializes a new instance of the class + /// and configures the mappings. + /// + public RegisterToUserContactProfile() + { + CreateMap() + .ForMember(dest => dest.IsReserved, opt => opt.MapFrom(src => src.Reservation)) + .ForMember(dest => dest.EmailAddress, opt => opt.MapFrom(src => src.EmailAddress)) + .ForMember(dest => dest.LanguageCode, opt => opt.MapFrom(src => src.LanguageCode)) + .ForMember(dest => dest.NationalIdentityNumber, opt => opt.MapFrom(src => src.FnumberAk)) + .ForMember(dest => dest.MobilePhoneNumber, opt => opt.MapFrom(src => src.MobilePhoneNumber)); + } +} diff --git a/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs index a3e9ad0..358e4d6 100644 --- a/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs @@ -1,5 +1,7 @@ #nullable enable +using System.Collections.Immutable; + using Altinn.Profile.Core.Domain; using Altinn.Profile.Integrations.Entities; @@ -17,5 +19,5 @@ public interface IRegisterRepository : IRepository /// /// A task that represents the asynchronous operation. The task result contains a collection of register data for the users. /// - Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers); + Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers); } diff --git a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs index 532a049..027a866 100644 --- a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs @@ -1,5 +1,7 @@ #nullable enable +using System.Collections.Immutable; + using Altinn.Profile.Integrations.Entities; using Altinn.Profile.Integrations.Persistence; @@ -32,8 +34,10 @@ public RegisterRepository(ProfileDbContext context) : base(context) /// /// A task that represents the asynchronous operation. The task result contains a collection of register data for the users. /// - public async Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers) + public async Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers) { - return await _context.Registers.Where(k => nationalIdentityNumbers.Contains(k.FnumberAk)).ToListAsync(); + var registers = await _context.Registers.Where(k => nationalIdentityNumbers.Contains(k.FnumberAk)).ToListAsync(); + + return registers.ToImmutableList(); } } diff --git a/src/Altinn.Profile.Integrations/Services/INationalIdentityNumberChecker.cs b/src/Altinn.Profile.Integrations/Services/INationalIdentityNumberChecker.cs new file mode 100644 index 0000000..f0f0a2b --- /dev/null +++ b/src/Altinn.Profile.Integrations/Services/INationalIdentityNumberChecker.cs @@ -0,0 +1,31 @@ +using System.Collections.Immutable; + +namespace Altinn.Profile.Integrations.Services; + +/// +/// Provides functionality for checking the validity of national identity numbers. +/// +public interface INationalIdentityNumberChecker +{ + /// + /// Validates a collection of national identity numbers and categorizes them into valid and invalid groups. + /// + /// A collection of national identity numbers. + /// + /// A tuple containing two immutable lists: + /// + /// Valid: An immutable list of valid national identity numbers. + /// Invalid: An immutable list of invalid national identity numbers. + /// + /// + (IImmutableList Valid, IImmutableList Invalid) Categorize(IEnumerable nationalIdentityNumbers); + + /// + /// Checks the validity of a single national identity number. + /// + /// The national identity number. + /// + /// true if the national identity number is valid; otherwise, false. + /// + bool IsValid(string nationalIdentityNumber); +} diff --git a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs index 72cde12..949bb9f 100644 --- a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs @@ -16,7 +16,7 @@ public interface IRegisterService /// /// A task that represents the asynchronous operation. The task result contains the user's contact information, or null if not found. /// - Task GetUserContactInfoAsync(string nationalIdentityNumber); + Task GetUserContactAsync(string nationalIdentityNumber); /// /// Asynchronously retrieves the contact information for multiple users based on their national identity numbers. @@ -25,5 +25,5 @@ public interface IRegisterService /// /// A task that represents the asynchronous operation. The task result contains a collection of user contact information, or an empty collection if none are found. /// - Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers); + Task GetUserContactAsync(IEnumerable nationalIdentityNumbers); } diff --git a/src/Altinn.Profile.Integrations/Services/NationalIdentityNumberChecker.cs b/src/Altinn.Profile.Integrations/Services/NationalIdentityNumberChecker.cs new file mode 100644 index 0000000..c35d9cc --- /dev/null +++ b/src/Altinn.Profile.Integrations/Services/NationalIdentityNumberChecker.cs @@ -0,0 +1,43 @@ +using System.Collections.Immutable; + +using Altinn.Profile.Core.Extensions; + +namespace Altinn.Profile.Integrations.Services; + +/// +/// Implementation of the national identity number checking service. +/// +public class NationalIdentityNumberChecker : INationalIdentityNumberChecker +{ + /// + /// Validates a collection of national identity numbers and categorizes them into valid and invalid groups. + /// + /// A collection of national identity numbers. + /// + /// A tuple containing two immutable lists: + /// + /// Valid: An immutable list of valid national identity numbers. + /// Invalid: An immutable list of invalid national identity numbers. + /// + /// + public (IImmutableList Valid, IImmutableList Invalid) Categorize(IEnumerable nationalIdentityNumbers) + { + // Filter valid and invalid national identity numbers + var validSocialSecurityNumbers = nationalIdentityNumbers.Where(e => e.IsValidSocialSecurityNumber()).ToImmutableList(); + var invalidSocialSecurityNumbers = nationalIdentityNumbers.Except(validSocialSecurityNumbers).ToImmutableList(); + + return (validSocialSecurityNumbers, invalidSocialSecurityNumbers); + } + + /// + /// Checks the validity of a single national identity number. + /// + /// The national identity number. + /// + /// true if the national identity number is valid; otherwise, false. + /// + public bool IsValid(string nationalIdentityNumber) + { + return nationalIdentityNumber.IsValidSocialSecurityNumber(); + } +} diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs index fa90aef..13a2dd2 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -1,4 +1,5 @@ -using Altinn.Profile.Core.Extensions; +using System.Collections.Immutable; + using Altinn.Profile.Integrations.Entities; using Altinn.Profile.Integrations.Repositories; @@ -13,17 +14,20 @@ public class RegisterService : IRegisterService { private readonly IMapper _mapper; private readonly IRegisterRepository _registerRepository; + private readonly INationalIdentityNumberChecker _nationalIdentityNumberChecker; /// /// Initializes a new instance of the class. /// /// The mapper used for object mapping. /// The repository used for accessing register data. - /// Thrown when the or object is null. - public RegisterService(IMapper mapper, IRegisterRepository registerRepository) + /// The service used for checking the validity of national identity numbers. + /// Thrown if , , or is null. + public RegisterService(IMapper mapper, IRegisterRepository registerRepository, INationalIdentityNumberChecker nationalIdentityNumberChecker) { _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); _registerRepository = registerRepository ?? throw new ArgumentNullException(nameof(registerRepository)); + _nationalIdentityNumberChecker = nationalIdentityNumberChecker ?? throw new ArgumentNullException(nameof(nationalIdentityNumberChecker)); } /// @@ -33,15 +37,15 @@ public RegisterService(IMapper mapper, IRegisterRepository registerRepository) /// /// A task that represents the asynchronous operation. The task result contains the user's contact information, or null if not found. /// - public async Task GetUserContactInfoAsync(string nationalIdentityNumber) + public async Task GetUserContactAsync(string nationalIdentityNumber) { - if (!nationalIdentityNumber.IsValidSocialSecurityNumber()) + if (!_nationalIdentityNumberChecker.IsValid(nationalIdentityNumber)) { return null; } - var userContactInfo = await _registerRepository.GetUserContactInfoAsync([nationalIdentityNumber]); - return _mapper.Map(userContactInfo); + var userContactInfoEntity = await _registerRepository.GetUserContactInfoAsync([nationalIdentityNumber]); + return _mapper.Map(userContactInfoEntity); } /// @@ -51,22 +55,23 @@ public RegisterService(IMapper mapper, IRegisterRepository registerRepository) /// /// A task that represents the asynchronous operation. The task result contains a collection of user contact information, or an empty collection if none are found. /// - public async Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers) + public async Task GetUserContactAsync(IEnumerable nationalIdentityNumbers) { - if (nationalIdentityNumbers == null || !nationalIdentityNumbers.Any()) - { - return []; - } + var (validSocialSecurityNumbers, invalidSocialSecurityNumbers) = _nationalIdentityNumberChecker.Categorize(nationalIdentityNumbers); - // Filter out invalid national identity numbers - var validNationalIdentityNumbers = nationalIdentityNumbers.Where(e => e.IsValidSocialSecurityNumber()); - if (!validNationalIdentityNumbers.Any()) - { - return []; - } + var userContactInfoEntities = await _registerRepository.GetUserContactInfoAsync(validSocialSecurityNumbers); + + var matchedUserContact = userContactInfoEntities.Select(_mapper.Map); - var userContactInfo = await _registerRepository.GetUserContactInfoAsync(validNationalIdentityNumbers); + var unmatchedUserContact = nationalIdentityNumbers + .Except(userContactInfoEntities.Select(e => e.FnumberAk)) + .Select(e => new UserContact { NationalIdentityNumber = e }) + .Select(_mapper.Map); - return _mapper.Map>(userContactInfo); + return new UserContactResult + { + MatchedUserContact = matchedUserContact.ToImmutableList(), + UnmatchedUserContact = unmatchedUserContact.ToImmutableList(), + }; } } diff --git a/src/Altinn.Profile/Controllers/UserNotificationsController.cs b/src/Altinn.Profile/Controllers/UserNotificationsController.cs index fa98564..50252a2 100644 --- a/src/Altinn.Profile/Controllers/UserNotificationsController.cs +++ b/src/Altinn.Profile/Controllers/UserNotificationsController.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; + using Altinn.Profile.Core.Extensions; using Altinn.Profile.Integrations.Services; using Altinn.Profile.Models; + using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -20,14 +23,17 @@ namespace Altinn.Profile.Controllers; public class UserNotificationsController : ControllerBase { private readonly IRegisterService _registerService; + private readonly INationalIdentityNumberChecker _validator; /// /// Initializes a new instance of the class. /// /// The register service. + /// The social security number validator /// Thrown when the is null. - public UserNotificationsController(IRegisterService registerService) + public UserNotificationsController(IRegisterService registerService, INationalIdentityNumberChecker validator) { + _validator = validator ?? throw new ArgumentNullException(nameof(validator)); _registerService = registerService ?? throw new ArgumentNullException(nameof(registerService)); } @@ -39,38 +45,32 @@ public UserNotificationsController(IRegisterService registerService) [HttpPost("GetNotificationPreferences")] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(UserNotificationPreferencesResponse), StatusCodes.Status200OK)] - public async Task> GetNotificationPreferences([FromBody] UserContactPointLookup request) + [ProducesResponseType(typeof(UserContactDetailsResult), StatusCodes.Status200OK)] + public async Task> GetNotificationPreferences([FromBody] UserContactPointLookup request) { if (request?.NationalIdentityNumbers?.Count == 0) { - return BadRequest("No national identity numbers provided."); + return BadRequest(); } - var validSSNs = request.NationalIdentityNumbers.Where(e => e.IsValidSocialSecurityNumber()).ToList(); - var invalidSSNs = request.NationalIdentityNumbers.Except(validSSNs).ToList(); - - var notificationPreferences = await _registerService.GetUserContactInfoAsync(validSSNs); - var matches = notificationPreferences.Select(np => new UserNotificationPreferences - { - NationalIdentityNumber = np.NationalIdentityNumber, - Reservation = np.IsReserved, - EmailAddress = np.EmailAddress, - LanguageCode = np.LanguageCode, - MobilePhoneNumber = np.MobilePhoneNumber, - }).ToList(); + var notificationPreferences = await _registerService.GetUserContactAsync(request.NationalIdentityNumbers).ConfigureAwait(false); - var noMatches = invalidSSNs.Select(invalid => new UserNotificationPreferences + var matches = notificationPreferences.MatchedUserContact.Select(e => new UserContactDetails { - NationalIdentityNumber = invalid - }).ToList(); + Reservation = e.IsReserved, + EmailAddress = e.EmailAddress, + LanguageCode = e.LanguageCode, + MobilePhoneNumber = e.MobilePhoneNumber, + NationalIdentityNumber = e.NationalIdentityNumber, + }).ToImmutableList(); - noMatches.AddRange(validSSNs.Except(matches.Select(m => m.NationalIdentityNumber)).Select(item => new UserNotificationPreferences + // Create a list for no matches + var noMatches = notificationPreferences.UnmatchedUserContact.Select(e => new UserContactDetails { - NationalIdentityNumber = item - })); + NationalIdentityNumber = e.NationalIdentityNumber, + }).ToImmutableList(); - var response = new UserNotificationPreferencesResponse(matches, noMatches); + var response = new UserContactDetailsResult(matches, [.. noMatches]); return Ok(response); } } diff --git a/src/Altinn.Profile/Models/UserNotificationPreferences.cs b/src/Altinn.Profile/Models/UserContactDetails.cs similarity index 63% rename from src/Altinn.Profile/Models/UserNotificationPreferences.cs rename to src/Altinn.Profile/Models/UserContactDetails.cs index a699278..fd9dfc2 100644 --- a/src/Altinn.Profile/Models/UserNotificationPreferences.cs +++ b/src/Altinn.Profile/Models/UserContactDetails.cs @@ -5,18 +5,21 @@ namespace Altinn.Profile.Models; /// -/// Represents a user's notification preferences. +/// Represents a user's contact information for communication purposes. +/// This includes the user's identity number, contact methods (mobile phone and email), +/// language preference, and a flag indicating whether the user has opted out of contact. /// -public record UserNotificationPreferences +public record UserContactDetails { /// /// Gets the national identity number of the user. /// - [JsonPropertyName("nationalIdentityNumbers")] + [JsonPropertyName("nationalIdentityNumber")] public required string NationalIdentityNumber { get; init; } /// - /// Gets a value indicating whether the user opts out of being contacted. + /// Gets a value indicating whether the user has opted out of being contacted. + /// A value of true indicates that the user does not wish to receive communications, while false or null indicates that they have not opted out. /// [JsonPropertyName("reservation")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -37,7 +40,7 @@ public record UserNotificationPreferences public string? EmailAddress { get; init; } /// - /// Gets the language code of the user. + /// Gets the language code preferred by the user for communication. /// [JsonPropertyName("languageCode")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] diff --git a/src/Altinn.Profile/Models/UserContactDetailsResult.cs b/src/Altinn.Profile/Models/UserContactDetailsResult.cs new file mode 100644 index 0000000..6e77b5c --- /dev/null +++ b/src/Altinn.Profile/Models/UserContactDetailsResult.cs @@ -0,0 +1,41 @@ +#nullable enable + +using System.Collections.Immutable; +using System.Text.Json.Serialization; + +namespace Altinn.Profile.Models; + +/// +/// Represents the result of a lookup operation for a user's contact details. +/// It contains information about which contact details were successfully matched and which could not be matched during the lookup process. +/// +public record UserContactDetailsResult +{ + /// + /// Gets the list of user contact details that were successfully matched based on the national identity number. + /// + [JsonPropertyName("matchedContacts")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ImmutableList? MatchedContacts { get; init; } + + /// + /// Gets the list of user contact details that could not be matched based on the national identity number. + /// This could be due to an invalid or nonexistent national identity number or other reasons. + /// + [JsonPropertyName("unmatchedContacts")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ImmutableList? UnmatchedContacts { get; init; } + + /// + /// Initializes a new instance of the record. + /// + /// The list of contact details that were matched based on the national identity number. + /// The list of contact details that were not matched based on the national identity number. + public UserContactDetailsResult( + ImmutableList matchedContacts, + ImmutableList unmatchedContacts) + { + MatchedContacts = matchedContacts; + UnmatchedContacts = unmatchedContacts; + } +} diff --git a/src/Altinn.Profile/Models/UserContactPointLookup.cs b/src/Altinn.Profile/Models/UserContactPointLookup.cs index f7c38c4..399d480 100644 --- a/src/Altinn.Profile/Models/UserContactPointLookup.cs +++ b/src/Altinn.Profile/Models/UserContactPointLookup.cs @@ -3,12 +3,12 @@ namespace Altinn.Profile.Models; /// -/// A class representing a user contact point lookup object +/// A class representing 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 + /// A collection of national identity numbers used to retrieve contact points or check contact point availability. /// public List NationalIdentityNumbers { get; set; } = []; } diff --git a/src/Altinn.Profile/Models/UserNotificationPreferencesResponse.cs b/src/Altinn.Profile/Models/UserNotificationPreferencesResponse.cs deleted file mode 100644 index b67d3c5..0000000 --- a/src/Altinn.Profile/Models/UserNotificationPreferencesResponse.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Altinn.Profile.Models; - -/// -/// Represents the response containing user notification preferences. -/// -public record UserNotificationPreferencesResponse -{ - /// - /// Gets the list of user notification preferences that are matched. - /// - [JsonPropertyName("matched")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public IReadOnlyList Matched { get; init; } = []; - - /// - /// Gets the list of user notification preferences that are invalid or unmatched. - /// - [JsonPropertyName("Unmatched")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public IReadOnlyList Unmatched { get; init; } = []; - - /// - /// Initializes a new instance of the record. - /// - /// A list of user notification preferences that are matched. - /// A list of user notification preferences that are invalid or unmatched. - public UserNotificationPreferencesResponse( - List matched, - List unmatched) - { - Matched = matched ?? []; - Unmatched = unmatched ?? []; - } -} diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs index 61cd028..90bbd51 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs @@ -64,7 +64,7 @@ public async Task GetUserContactInfoAsync_ReturnsMappedInfo_WhenValidIds() // Act var registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object); - var result = await registerService.GetUserContactInfoAsync(ids); + var result = await registerService.GetUserContactAsync(ids); // Assert Assert.NotNull(result); @@ -84,7 +84,7 @@ public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenInvalidNationalIds() // Act var registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object); - var result = await registerService.GetUserContactInfoAsync(nationalIdentityNumbers); + var result = await registerService.GetUserContactAsync(nationalIdentityNumbers); // Assert Assert.NotNull(result); @@ -116,7 +116,7 @@ public async Task GetUserContactInfoAsync_ReturnsMapped_WhenOneValidAndOneInvali // Act var registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object); - var result = await registerService.GetUserContactInfoAsync(nationalIdentityNumbers); + var result = await registerService.GetUserContactAsync(nationalIdentityNumbers); // Assert Assert.NotNull(result); @@ -129,9 +129,9 @@ private void SetupRegisterRepository(params Register[] registers) _mockRegisterRepository.Setup(repo => repo.GetUserContactInfoAsync(It.IsAny>())).ReturnsAsync(registers.AsEnumerable()); } - private static Mock SetupUserContactInfo(Register register) + private static Mock SetupUserContactInfo(Register register) { - var mockUserContactInfo = new Mock(); + var mockUserContactInfo = new Mock(); mockUserContactInfo.SetupGet(u => u.IsReserved).Returns(register.Reservation); mockUserContactInfo.SetupGet(u => u.LanguageCode).Returns(register.LanguageCode); mockUserContactInfo.SetupGet(u => u.EmailAddress).Returns(register.EmailAddress); @@ -140,17 +140,17 @@ private static Mock SetupUserContactInfo(Register register) return mockUserContactInfo; } - private void SetupMapper(params (Register Register, Mock UserContactInfo)[] mappings) + private void SetupMapper(params (Register Register, Mock UserContactInfo)[] mappings) { - _mockMapper.Setup(m => m.Map>(It.IsAny>())) + _mockMapper.Setup(m => m.Map>(It.IsAny>())) .Returns((IEnumerable registers) => registers.Select(r => mappings.FirstOrDefault(m => m.Register.FnumberAk == r.FnumberAk).UserContactInfo.Object) .Where(u => u != null) - .Cast()); + .Cast()); } - private static void AssertUserContactInfoMatches(Register expectedRegister, IUserContactInfo actualContactInfo) + private static void AssertUserContactInfoMatches(Register expectedRegister, IUserContact actualContactInfo) { Assert.Equal(expectedRegister.Reservation, actualContactInfo.IsReserved); Assert.Equal(expectedRegister.EmailAddress, actualContactInfo.EmailAddress); From 31c2329a58e7b89310c233f9de419ca4a7dfdd2d Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Sat, 5 Oct 2024 22:56:12 +0200 Subject: [PATCH 29/98] =?UTF-8?q?Implement=20a=20use=20case=20for=20contro?= =?UTF-8?q?llers=20to=20access=20and=20retrieve=20users=E2=80=99=20contact?= =?UTF-8?q?=20details?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/IRegisterService.cs | 3 +- .../Services/NationalIdentityNumberChecker.cs | 1 - .../Services/RegisterService.cs | 7 +- src/Altinn.Profile/Altinn.Profile.csproj | 10 --- .../UserContactDetailsController.cs | 52 +++++++++++++ .../UserContactDetailsInternalController.cs | 50 ++++++++++++ .../UserNotificationsController.cs | 76 ------------------ src/Altinn.Profile/Program.cs | 2 + .../UseCases/IUserContactDetailsRetriever.cs | 19 +++++ .../UseCases/UserContactDetailsRetriever.cs | 77 +++++++++++++++++++ .../Register/RegisterServiceTests.cs | 23 +++--- 11 files changed, 218 insertions(+), 102 deletions(-) create mode 100644 src/Altinn.Profile/Controllers/UserContactDetailsController.cs create mode 100644 src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs delete mode 100644 src/Altinn.Profile/Controllers/UserNotificationsController.cs create mode 100644 src/Altinn.Profile/UseCases/IUserContactDetailsRetriever.cs create mode 100644 src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs diff --git a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs index 949bb9f..7c6feea 100644 --- a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs @@ -1,5 +1,6 @@ #nullable enable +using Altinn.Profile.Core; using Altinn.Profile.Integrations.Entities; namespace Altinn.Profile.Integrations.Services; @@ -25,5 +26,5 @@ public interface IRegisterService /// /// A task that represents the asynchronous operation. The task result contains a collection of user contact information, or an empty collection if none are found. /// - Task GetUserContactAsync(IEnumerable nationalIdentityNumbers); + Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers); } diff --git a/src/Altinn.Profile.Integrations/Services/NationalIdentityNumberChecker.cs b/src/Altinn.Profile.Integrations/Services/NationalIdentityNumberChecker.cs index c35d9cc..850371f 100644 --- a/src/Altinn.Profile.Integrations/Services/NationalIdentityNumberChecker.cs +++ b/src/Altinn.Profile.Integrations/Services/NationalIdentityNumberChecker.cs @@ -22,7 +22,6 @@ public class NationalIdentityNumberChecker : INationalIdentityNumberChecker /// public (IImmutableList Valid, IImmutableList Invalid) Categorize(IEnumerable nationalIdentityNumbers) { - // Filter valid and invalid national identity numbers var validSocialSecurityNumbers = nationalIdentityNumbers.Where(e => e.IsValidSocialSecurityNumber()).ToImmutableList(); var invalidSocialSecurityNumbers = nationalIdentityNumbers.Except(validSocialSecurityNumbers).ToImmutableList(); diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs index 13a2dd2..ec387d3 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; +using Altinn.Profile.Core; using Altinn.Profile.Integrations.Entities; using Altinn.Profile.Integrations.Repositories; @@ -55,7 +56,7 @@ public RegisterService(IMapper mapper, IRegisterRepository registerRepository, I /// /// A task that represents the asynchronous operation. The task result contains a collection of user contact information, or an empty collection if none are found. /// - public async Task GetUserContactAsync(IEnumerable nationalIdentityNumbers) + public async Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers) { var (validSocialSecurityNumbers, invalidSocialSecurityNumbers) = _nationalIdentityNumberChecker.Categorize(nationalIdentityNumbers); @@ -70,8 +71,8 @@ public async Task GetUserContactAsync(IEnumerable na return new UserContactResult { - MatchedUserContact = matchedUserContact.ToImmutableList(), - UnmatchedUserContact = unmatchedUserContact.ToImmutableList(), + MatchedUserContact = matchedUserContact?.ToImmutableList(), + UnmatchedUserContact = unmatchedUserContact?.ToImmutableList(), }; } } diff --git a/src/Altinn.Profile/Altinn.Profile.csproj b/src/Altinn.Profile/Altinn.Profile.csproj index 4456994..4d39599 100644 --- a/src/Altinn.Profile/Altinn.Profile.csproj +++ b/src/Altinn.Profile/Altinn.Profile.csproj @@ -10,20 +10,10 @@ - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - diff --git a/src/Altinn.Profile/Controllers/UserContactDetailsController.cs b/src/Altinn.Profile/Controllers/UserContactDetailsController.cs new file mode 100644 index 0000000..5f2cc7b --- /dev/null +++ b/src/Altinn.Profile/Controllers/UserContactDetailsController.cs @@ -0,0 +1,52 @@ +using System; +using System.Threading.Tasks; + +using Altinn.Profile.Models; +using Altinn.Profile.UseCases; + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Altinn.Profile.Controllers; + +/// +/// Controller to retrieve users contact details. +/// +[Authorize] +[ApiController] +[Consumes("application/json")] +[Produces("application/json")] +[Route("profile/api/v1/contact/details")] +public class UserContactDetailsController : ControllerBase +{ + private readonly IUserContactDetailsRetriever _contactDetailsRetriever; + + /// + /// Initializes a new instance of the class. + /// + /// The use case for retrieving user contact details. + /// Thrown when the is null. + public UserContactDetailsController(IUserContactDetailsRetriever contactDetailsRetriever) + { + _contactDetailsRetriever = contactDetailsRetriever ?? throw new ArgumentNullException(nameof(contactDetailsRetriever)); + } + + /// + /// Retrieves the contact details for users based on their national identity numbers. + /// + /// A collection of national identity numbers. + /// A task that represents the asynchronous operation, containing a response with users' contact details. + [HttpPost("lookup")] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(UserContactDetailsResult), StatusCodes.Status200OK)] + public async Task> PostLookup([FromBody] UserContactPointLookup request) + { + var result = await _contactDetailsRetriever.RetrieveAsync(request); + + return result.Match>( + success => Ok(success), + failure => Problem("Failed to retrieve contact details. Please check the provided national identity numbers and try again.")); + } +} diff --git a/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs b/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs new file mode 100644 index 0000000..c2ba45b --- /dev/null +++ b/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading.Tasks; + +using Altinn.Profile.Models; +using Altinn.Profile.UseCases; + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Altinn.Profile.Controllers; + +/// +/// Controller to retrieve users contact details for internal consumption (e.g. Authorization) requiring neither authenticated user token nor access token authorization. +/// +[ApiController] +[Consumes("application/json")] +[Produces("application/json")] +[ApiExplorerSettings(IgnoreApi = true)] +[Route("profile/api/v1/contact/details/internal")] +public class UserContactDetailsInternalController : ControllerBase +{ + private readonly IUserContactDetailsRetriever _contactDetailsRetriever; + + /// + /// Initializes a new instance of the class. + /// + /// The use case for retrieving user contact details. + /// Thrown when the is null. + public UserContactDetailsInternalController(IUserContactDetailsRetriever contactDetailsRetriever) + { + _contactDetailsRetriever = contactDetailsRetriever ?? throw new ArgumentNullException(nameof(contactDetailsRetriever)); + } + + /// + /// Retrieves the contact details for users based on their national identity numbers. + /// + /// A collection of national identity numbers. + /// A task that represents the asynchronous operation, containing a response with users' contact details. + [HttpPost("lookup")] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(UserContactDetailsResult), StatusCodes.Status200OK)] + public async Task> PostLookup([FromBody] UserContactPointLookup request) + { + var result = await _contactDetailsRetriever.RetrieveAsync(request); + return result.Match>( + success => Ok(success), + failure => Problem("Failed to retrieve contact details. Please check the provided national identity numbers and try again.")); + } +} diff --git a/src/Altinn.Profile/Controllers/UserNotificationsController.cs b/src/Altinn.Profile/Controllers/UserNotificationsController.cs deleted file mode 100644 index 50252a2..0000000 --- a/src/Altinn.Profile/Controllers/UserNotificationsController.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading.Tasks; - -using Altinn.Profile.Core.Extensions; -using Altinn.Profile.Integrations.Services; -using Altinn.Profile.Models; - -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; - -namespace Altinn.Profile.Controllers; - -/// -/// Controller to retrieve user notification preferences. -/// -[ApiController] -[Route("profile/api/v1/user-notification")] -[Consumes("application/json")] -[Produces("application/json")] -public class UserNotificationsController : ControllerBase -{ - private readonly IRegisterService _registerService; - private readonly INationalIdentityNumberChecker _validator; - - /// - /// Initializes a new instance of the class. - /// - /// The register service. - /// The social security number validator - /// Thrown when the is null. - public UserNotificationsController(IRegisterService registerService, INationalIdentityNumberChecker validator) - { - _validator = validator ?? throw new ArgumentNullException(nameof(validator)); - _registerService = registerService ?? throw new ArgumentNullException(nameof(registerService)); - } - - /// - /// Retrieves notification preferences for users based on their national identity numbers. - /// - /// A collection of national identity numbers. - /// A task that represents the asynchronous operation, containing a response with user notification preferences. - [HttpPost("GetNotificationPreferences")] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(UserContactDetailsResult), StatusCodes.Status200OK)] - public async Task> GetNotificationPreferences([FromBody] UserContactPointLookup request) - { - if (request?.NationalIdentityNumbers?.Count == 0) - { - return BadRequest(); - } - - var notificationPreferences = await _registerService.GetUserContactAsync(request.NationalIdentityNumbers).ConfigureAwait(false); - - var matches = notificationPreferences.MatchedUserContact.Select(e => new UserContactDetails - { - Reservation = e.IsReserved, - EmailAddress = e.EmailAddress, - LanguageCode = e.LanguageCode, - MobilePhoneNumber = e.MobilePhoneNumber, - NationalIdentityNumber = e.NationalIdentityNumber, - }).ToImmutableList(); - - // Create a list for no matches - var noMatches = notificationPreferences.UnmatchedUserContact.Select(e => new UserContactDetails - { - NationalIdentityNumber = e.NationalIdentityNumber, - }).ToImmutableList(); - - var response = new UserContactDetailsResult(matches, [.. noMatches]); - return Ok(response); - } -} diff --git a/src/Altinn.Profile/Program.cs b/src/Altinn.Profile/Program.cs index 2eb4f19..5cdfad4 100644 --- a/src/Altinn.Profile/Program.cs +++ b/src/Altinn.Profile/Program.cs @@ -11,6 +11,7 @@ using Altinn.Profile.Filters; using Altinn.Profile.Health; using Altinn.Profile.Integrations.Extensions; +using Altinn.Profile.UseCases; using AltinnCore.Authentication.JwtCookie; @@ -169,6 +170,7 @@ void ConfigureServices(IServiceCollection services, IConfiguration config) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddScoped(); services.AddAuthentication(JwtCookieDefaults.AuthenticationScheme) .AddJwtCookie(JwtCookieDefaults.AuthenticationScheme, options => diff --git a/src/Altinn.Profile/UseCases/IUserContactDetailsRetriever.cs b/src/Altinn.Profile/UseCases/IUserContactDetailsRetriever.cs new file mode 100644 index 0000000..cc2600e --- /dev/null +++ b/src/Altinn.Profile/UseCases/IUserContactDetailsRetriever.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; + +using Altinn.Profile.Core; +using Altinn.Profile.Models; + +namespace Altinn.Profile.UseCases; + +/// +/// Defines a use case for retrieving user contact details. +/// +public interface IUserContactDetailsRetriever +{ + /// + /// Asynchronously retrieves the contact details for one or more users based on the specified lookup criteria. + /// + /// The user contact point lookup criteria, which includes national identity numbers. + /// A task representing the asynchronous operation. The task result contains the outcome of the user contact details retrieval. + Task> RetrieveAsync(UserContactPointLookup lookupCriteria); +} diff --git a/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs b/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs new file mode 100644 index 0000000..8c6dd53 --- /dev/null +++ b/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; + +using Altinn.Profile.Core; +using Altinn.Profile.Integrations.Entities; +using Altinn.Profile.Integrations.Services; +using Altinn.Profile.Models; + +namespace Altinn.Profile.UseCases; + +/// +/// Implementation of the use case for retrieving user contact details. +/// +public class UserContactDetailsRetriever : IUserContactDetailsRetriever +{ + private readonly IRegisterService _registerService; + + /// + /// Initializes a new instance of the class. + /// + /// The register service for retrieving user contact details. + /// Thrown when is null. + public UserContactDetailsRetriever(IRegisterService registerService) + { + _registerService = registerService ?? throw new ArgumentNullException(nameof(registerService)); + } + + /// + /// Asynchronously retrieves the contact details for one or more users based on the specified lookup criteria. + /// + /// The user contact point lookup criteria, which includes national identity numbers. + /// A task representing the asynchronous operation. The task result contains the outcome of the user contact details retrieval. + public async Task> RetrieveAsync(UserContactPointLookup lookupCriteria) + { + if (lookupCriteria?.NationalIdentityNumbers == null || lookupCriteria.NationalIdentityNumbers.Count == 0) + { + return false; + } + + var userContactDetails = await _registerService.GetUserContactAsync(lookupCriteria.NationalIdentityNumbers); + return userContactDetails.Match( + MapToUserContactDetailsResult, + _ => false); + } + + /// + /// Maps an to a . + /// + /// The user contact details to map. + /// The mapped user contact details. + private UserContactDetails MapToUserContactDetails(IUserContact userContactDetails) + { + return new UserContactDetails + { + Reservation = userContactDetails.IsReserved, + EmailAddress = userContactDetails.EmailAddress, + LanguageCode = userContactDetails.LanguageCode, + MobilePhoneNumber = userContactDetails.MobilePhoneNumber, + NationalIdentityNumber = userContactDetails.NationalIdentityNumber + }; + } + + /// + /// Maps the user contact details lookup result to a . + /// + /// The user contact details lookup result. + /// A result containing the mapped user contact details. + private Result MapToUserContactDetailsResult(IUserContactResult userContactResult) + { + var matchedContacts = userContactResult?.MatchedUserContact?.Select(MapToUserContactDetails).ToImmutableList(); + var unmatchedContacts = userContactResult?.UnmatchedUserContact?.Select(MapToUserContactDetails).ToImmutableList(); + + return new UserContactDetailsResult(matchedContacts, unmatchedContacts); + } +} diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs index 90bbd51..e6f4b7e 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs @@ -19,12 +19,14 @@ namespace Altinn.Profile.Tests.Profile.Integrations; public class RegisterServiceTests { private readonly Mock _mockRegisterRepository; + private readonly Mock _mockNationalIdentityNumberChecker; private readonly Mock _mockMapper; public RegisterServiceTests() { _mockMapper = new Mock(); _mockRegisterRepository = new Mock(); + _mockNationalIdentityNumberChecker = new Mock(); } [Fact] @@ -63,15 +65,14 @@ public async Task GetUserContactInfoAsync_ReturnsMappedInfo_WhenValidIds() SetupMapper((firstRegister, firstUserContactInfo), (secondRegister, secondUserContactInfo)); // Act - var registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object); + var registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object, _mockNationalIdentityNumberChecker.Object); var result = await registerService.GetUserContactAsync(ids); // Assert Assert.NotNull(result); - Assert.Equal(2, result.Count()); - - AssertUserContactInfoMatches(firstRegister, result.ElementAt(0)); - AssertUserContactInfoMatches(secondRegister, result.ElementAt(1)); + ///Assert.Equal(2, result.Count()); + ///AssertUserContactInfoMatches(firstRegister, result.ElementAt(0)); + ///AssertUserContactInfoMatches(secondRegister, result.ElementAt(1)); } [Fact] @@ -83,12 +84,12 @@ public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenInvalidNationalIds() SetupRegisterRepository(); // Return empty // Act - var registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object); + var registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object, _mockNationalIdentityNumberChecker.Object); var result = await registerService.GetUserContactAsync(nationalIdentityNumbers); // Assert Assert.NotNull(result); - Assert.Empty(result); + ///Assert.Empty(result); } [Fact] @@ -115,18 +116,18 @@ public async Task GetUserContactInfoAsync_ReturnsMapped_WhenOneValidAndOneInvali SetupMapper((validRegister, mockUserContactInfo)); // Act - var registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object); + var registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object, _mockNationalIdentityNumberChecker.Object); var result = await registerService.GetUserContactAsync(nationalIdentityNumbers); // Assert Assert.NotNull(result); - Assert.Single(result); - AssertUserContactInfoMatches(validRegister, result.First()); + ///Assert.Single(result); + ///AssertUserContactInfoMatches(validRegister, result.First()); } private void SetupRegisterRepository(params Register[] registers) { - _mockRegisterRepository.Setup(repo => repo.GetUserContactInfoAsync(It.IsAny>())).ReturnsAsync(registers.AsEnumerable()); + ///_mockRegisterRepository.Setup(repo => repo.GetUserContactInfoAsync(It.IsAny>())).ReturnsAsync(registers.AsEnumerable()); } private static Mock SetupUserContactInfo(Register register) From 039998102002789f7906c42b5cb1619f50151cb8 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 08:07:28 +0200 Subject: [PATCH 30/98] Add unit tests for string extension methods --- .../Entities/IUserContactResult.cs | 4 +- .../Entities/UserContactResult.cs | 4 +- .../UserContactDetailsController.cs | 8 +- .../UserContactDetailsInternalController.cs | 9 +- .../Models/UserContactDetails.cs | 9 +- .../Models/UserContactDetailsLookupResult.cs | 39 ++++++ .../Models/UserContactDetailsResult.cs | 41 ------ .../Models/UserContactPointLookup.cs | 2 +- .../UseCases/IUserContactDetailsRetriever.cs | 2 +- .../UseCases/UserContactDetailsRetriever.cs | 27 ++-- .../Extensions/StringExtensionsTests.cs | 122 ++++++++++++++++++ 11 files changed, 194 insertions(+), 73 deletions(-) create mode 100644 src/Altinn.Profile/Models/UserContactDetailsLookupResult.cs delete mode 100644 src/Altinn.Profile/Models/UserContactDetailsResult.cs create mode 100644 test/Altinn.Profile.Tests/Profile.Core/Extensions/StringExtensionsTests.cs diff --git a/src/Altinn.Profile.Integrations/Entities/IUserContactResult.cs b/src/Altinn.Profile.Integrations/Entities/IUserContactResult.cs index f9d590b..19e474e 100644 --- a/src/Altinn.Profile.Integrations/Entities/IUserContactResult.cs +++ b/src/Altinn.Profile.Integrations/Entities/IUserContactResult.cs @@ -15,7 +15,7 @@ public interface IUserContactResult ImmutableList? MatchedUserContact { get; init; } /// - /// Gets a list of user contact information that was not matched during the lookup. + /// Gets a list of national identity numbers that could not be matched with user contact details. /// - ImmutableList? UnmatchedUserContact { get; init; } + ImmutableList? UnmatchedNationalIdentityNumbers { get; init; } } diff --git a/src/Altinn.Profile.Integrations/Entities/UserContactResult.cs b/src/Altinn.Profile.Integrations/Entities/UserContactResult.cs index 0b8a75b..a931dff 100644 --- a/src/Altinn.Profile.Integrations/Entities/UserContactResult.cs +++ b/src/Altinn.Profile.Integrations/Entities/UserContactResult.cs @@ -15,7 +15,7 @@ public record UserContactResult : IUserContactResult public ImmutableList? MatchedUserContact { get; init; } /// - /// Gets a list of user contact information that was not matched during the lookup. + /// Gets a list of national identity numbers that could not be matched with user contact details. /// - public ImmutableList? UnmatchedUserContact { get; init; } + public ImmutableList? UnmatchedNationalIdentityNumbers { get; init; } } diff --git a/src/Altinn.Profile/Controllers/UserContactDetailsController.cs b/src/Altinn.Profile/Controllers/UserContactDetailsController.cs index 5f2cc7b..b535c55 100644 --- a/src/Altinn.Profile/Controllers/UserContactDetailsController.cs +++ b/src/Altinn.Profile/Controllers/UserContactDetailsController.cs @@ -40,13 +40,13 @@ public UserContactDetailsController(IUserContactDetailsRetriever contactDetailsR [HttpPost("lookup")] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(UserContactDetailsResult), StatusCodes.Status200OK)] - public async Task> PostLookup([FromBody] UserContactPointLookup request) + [ProducesResponseType(typeof(UserContactDetailsLookupResult), StatusCodes.Status200OK)] + public async Task> PostLookup([FromBody] UserContactPointLookup request) { var result = await _contactDetailsRetriever.RetrieveAsync(request); - return result.Match>( + return result.Match>( success => Ok(success), - failure => Problem("Failed to retrieve contact details. Please check the provided national identity numbers and try again.")); + failure => Problem("Unable to retrieve contact details. Please verify the provided national identity numbers and try again.")); } } diff --git a/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs b/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs index c2ba45b..43ef1c7 100644 --- a/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs +++ b/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs @@ -39,12 +39,13 @@ public UserContactDetailsInternalController(IUserContactDetailsRetriever contact [HttpPost("lookup")] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(UserContactDetailsResult), StatusCodes.Status200OK)] - public async Task> PostLookup([FromBody] UserContactPointLookup request) + [ProducesResponseType(typeof(UserContactDetailsLookupResult), StatusCodes.Status200OK)] + public async Task> PostLookup([FromBody] UserContactPointLookup request) { var result = await _contactDetailsRetriever.RetrieveAsync(request); - return result.Match>( + + return result.Match>( success => Ok(success), - failure => Problem("Failed to retrieve contact details. Please check the provided national identity numbers and try again.")); + failure => Problem("Unable to retrieve contact details. Please verify the provided national identity numbers and try again.")); } } diff --git a/src/Altinn.Profile/Models/UserContactDetails.cs b/src/Altinn.Profile/Models/UserContactDetails.cs index fd9dfc2..13d12c9 100644 --- a/src/Altinn.Profile/Models/UserContactDetails.cs +++ b/src/Altinn.Profile/Models/UserContactDetails.cs @@ -5,9 +5,7 @@ namespace Altinn.Profile.Models; /// -/// Represents a user's contact information for communication purposes. -/// This includes the user's identity number, contact methods (mobile phone and email), -/// language preference, and a flag indicating whether the user has opted out of contact. +/// Represents a user's contact information, including national identity number, contact methods, language preference, and opt-out status. /// public record UserContactDetails { @@ -19,30 +17,25 @@ public record UserContactDetails /// /// Gets a value indicating whether the user has opted out of being contacted. - /// A value of true indicates that the user does not wish to receive communications, while false or null indicates that they have not opted out. /// [JsonPropertyName("reservation")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? Reservation { get; init; } /// /// Gets the mobile phone number of the user. /// [JsonPropertyName("mobilePhoneNumber")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? MobilePhoneNumber { get; init; } /// /// Gets the email address of the user. /// [JsonPropertyName("emailAddress")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? EmailAddress { get; init; } /// /// Gets the language code preferred by the user for communication. /// [JsonPropertyName("languageCode")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? LanguageCode { get; init; } } diff --git a/src/Altinn.Profile/Models/UserContactDetailsLookupResult.cs b/src/Altinn.Profile/Models/UserContactDetailsLookupResult.cs new file mode 100644 index 0000000..83654b0 --- /dev/null +++ b/src/Altinn.Profile/Models/UserContactDetailsLookupResult.cs @@ -0,0 +1,39 @@ +#nullable enable + +using System.Collections.Immutable; +using System.Text.Json.Serialization; + +namespace Altinn.Profile.Models; + +/// +/// Represents the results of a user contact details lookup operation. +/// +public record UserContactDetailsLookupResult +{ + /// + /// Gets a list of user contact details that were successfully matched based on the national identity number. + /// + [JsonPropertyName("matchedUserContactDetails")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ImmutableList? MatchedUserContactDetails { get; init; } + + /// + /// Gets a list of national identity numbers that could not be matched with user contact details. + /// + [JsonPropertyName("unmatchedNationalIdentityNumbers")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ImmutableList? UnmatchedNationalIdentityNumbers { get; init; } + + /// + /// Initializes a new instance of the record. + /// + /// The list of contact details that were matched based on the national identity number. + /// The list of national identity numbers that could not be matched with user contact details. + public UserContactDetailsLookupResult( + ImmutableList matchedUserContactDetails, + ImmutableList unmatchedNationalIdentityNumbers) + { + MatchedUserContactDetails = matchedUserContactDetails; + UnmatchedNationalIdentityNumbers = unmatchedNationalIdentityNumbers; + } +} diff --git a/src/Altinn.Profile/Models/UserContactDetailsResult.cs b/src/Altinn.Profile/Models/UserContactDetailsResult.cs deleted file mode 100644 index 6e77b5c..0000000 --- a/src/Altinn.Profile/Models/UserContactDetailsResult.cs +++ /dev/null @@ -1,41 +0,0 @@ -#nullable enable - -using System.Collections.Immutable; -using System.Text.Json.Serialization; - -namespace Altinn.Profile.Models; - -/// -/// Represents the result of a lookup operation for a user's contact details. -/// It contains information about which contact details were successfully matched and which could not be matched during the lookup process. -/// -public record UserContactDetailsResult -{ - /// - /// Gets the list of user contact details that were successfully matched based on the national identity number. - /// - [JsonPropertyName("matchedContacts")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public ImmutableList? MatchedContacts { get; init; } - - /// - /// Gets the list of user contact details that could not be matched based on the national identity number. - /// This could be due to an invalid or nonexistent national identity number or other reasons. - /// - [JsonPropertyName("unmatchedContacts")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public ImmutableList? UnmatchedContacts { get; init; } - - /// - /// Initializes a new instance of the record. - /// - /// The list of contact details that were matched based on the national identity number. - /// The list of contact details that were not matched based on the national identity number. - public UserContactDetailsResult( - ImmutableList matchedContacts, - ImmutableList unmatchedContacts) - { - MatchedContacts = matchedContacts; - UnmatchedContacts = unmatchedContacts; - } -} diff --git a/src/Altinn.Profile/Models/UserContactPointLookup.cs b/src/Altinn.Profile/Models/UserContactPointLookup.cs index 399d480..8ee6bb4 100644 --- a/src/Altinn.Profile/Models/UserContactPointLookup.cs +++ b/src/Altinn.Profile/Models/UserContactPointLookup.cs @@ -8,7 +8,7 @@ namespace Altinn.Profile.Models; public class UserContactPointLookup { /// - /// A collection of national identity numbers used to retrieve contact points or check contact point availability. + /// A collection of national identity numbers used to retrieve contact points, obtain contact details, or check the availability of contact points. /// public List NationalIdentityNumbers { get; set; } = []; } diff --git a/src/Altinn.Profile/UseCases/IUserContactDetailsRetriever.cs b/src/Altinn.Profile/UseCases/IUserContactDetailsRetriever.cs index cc2600e..912200b 100644 --- a/src/Altinn.Profile/UseCases/IUserContactDetailsRetriever.cs +++ b/src/Altinn.Profile/UseCases/IUserContactDetailsRetriever.cs @@ -15,5 +15,5 @@ public interface IUserContactDetailsRetriever /// /// The user contact point lookup criteria, which includes national identity numbers. /// A task representing the asynchronous operation. The task result contains the outcome of the user contact details retrieval. - Task> RetrieveAsync(UserContactPointLookup lookupCriteria); + Task> RetrieveAsync(UserContactPointLookup lookupCriteria); } diff --git a/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs b/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs index 8c6dd53..6b39d68 100644 --- a/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs +++ b/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs @@ -11,7 +11,7 @@ namespace Altinn.Profile.UseCases; /// -/// Implementation of the use case for retrieving user contact details. +/// Provides an implementation for retrieving user contact details based on specified lookup criteria. /// public class UserContactDetailsRetriever : IUserContactDetailsRetriever { @@ -31,8 +31,11 @@ public UserContactDetailsRetriever(IRegisterService registerService) /// Asynchronously retrieves the contact details for one or more users based on the specified lookup criteria. /// /// The user contact point lookup criteria, which includes national identity numbers. - /// A task representing the asynchronous operation. The task result contains the outcome of the user contact details retrieval. - public async Task> RetrieveAsync(UserContactPointLookup lookupCriteria) + /// + /// A task representing the asynchronous operation. The task result contains a + /// where the value is and the error is . + /// + public async Task> RetrieveAsync(UserContactPointLookup lookupCriteria) { if (lookupCriteria?.NationalIdentityNumbers == null || lookupCriteria.NationalIdentityNumbers.Count == 0) { @@ -40,6 +43,7 @@ public async Task> RetrieveAsync(UserCont } var userContactDetails = await _registerService.GetUserContactAsync(lookupCriteria.NationalIdentityNumbers); + return userContactDetails.Match( MapToUserContactDetailsResult, _ => false); @@ -49,9 +53,12 @@ public async Task> RetrieveAsync(UserCont /// Maps an to a . /// /// The user contact details to map. - /// The mapped user contact details. + /// The mapped . + /// Thrown when is null. private UserContactDetails MapToUserContactDetails(IUserContact userContactDetails) { + ArgumentNullException.ThrowIfNull(userContactDetails, nameof(userContactDetails)); + return new UserContactDetails { Reservation = userContactDetails.IsReserved, @@ -63,15 +70,15 @@ private UserContactDetails MapToUserContactDetails(IUserContact userContactDetai } /// - /// Maps the user contact details lookup result to a . + /// Maps the user contact details lookup result to a . /// /// The user contact details lookup result. - /// A result containing the mapped user contact details. - private Result MapToUserContactDetailsResult(IUserContactResult userContactResult) + /// A containing the mapped user contact details. + private Result MapToUserContactDetailsResult(IUserContactResult userContactResult) { - var matchedContacts = userContactResult?.MatchedUserContact?.Select(MapToUserContactDetails).ToImmutableList(); - var unmatchedContacts = userContactResult?.UnmatchedUserContact?.Select(MapToUserContactDetails).ToImmutableList(); + var unmatchedNationalIdentityNumbers = userContactResult?.UnmatchedNationalIdentityNumbers ?? null; + var matchedUserContactDetails = userContactResult?.MatchedUserContact?.Select(MapToUserContactDetails).ToImmutableList(); - return new UserContactDetailsResult(matchedContacts, unmatchedContacts); + return new UserContactDetailsLookupResult(matchedUserContactDetails, unmatchedNationalIdentityNumbers); } } diff --git a/test/Altinn.Profile.Tests/Profile.Core/Extensions/StringExtensionsTests.cs b/test/Altinn.Profile.Tests/Profile.Core/Extensions/StringExtensionsTests.cs new file mode 100644 index 0000000..cbb7df4 --- /dev/null +++ b/test/Altinn.Profile.Tests/Profile.Core/Extensions/StringExtensionsTests.cs @@ -0,0 +1,122 @@ +using Altinn.Profile.Core.Extensions; +using Xunit; + +namespace Altinn.Profile.Tests.Core.Extensions; + +public class StringExtensionsTests +{ + [Fact] + public void IsDigitsOnly_InvalidInput_ReturnsFalse() + { + Assert.False("123A456".IsDigitsOnly()); + Assert.False("123 456".IsDigitsOnly()); + Assert.False(string.Empty.IsDigitsOnly()); + Assert.False(((string)null).IsDigitsOnly()); + } + + [Fact] + public void IsDigitsOnly_ValidDigits_ReturnsTrue() + { + Assert.True("1234567890".IsDigitsOnly()); + } + + [Theory] + [InlineData("", false)] + [InlineData(null, false)] + [InlineData("12345", true)] + [InlineData("123A456", false)] + [InlineData("123 456", false)] + [InlineData("1234567890", true)] + public void IsDigitsOnly_VariousInputs_ReturnsExpected(string input, bool expected) + { + var result = input.IsDigitsOnly(); + Assert.Equal(expected, result); + } + + [Fact] + public void IsValidSocialSecurityNumber_CacheWorks() + { + var socialSecurityNumber = "08119043698"; + var firstCheck = socialSecurityNumber.IsValidSocialSecurityNumber(); + var secondCheck = socialSecurityNumber.IsValidSocialSecurityNumber(); // Should hit cache + + Assert.True(firstCheck); + Assert.True(secondCheck); + } + + [Theory] + [InlineData("12345678900")] + [InlineData("98765432100")] + [InlineData("11111111111")] + public void IsValidSocialSecurityNumber_InvalidNumbers_ReturnsFalse(string number) + { + var result = number.IsValidSocialSecurityNumber(); + Assert.False(result); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + [InlineData("12345")] + [InlineData("0811a043698")] + public void IsValidSocialSecurityNumber_InvalidFormat_ReturnsFalse(string number) + { + var result = number.IsValidSocialSecurityNumber(); + Assert.False(result); + } + + [Theory] + [InlineData("08119043698")] + [InlineData("11111111111")] + public void IsValidSocialSecurityNumber_NoControlDigits(string socialSecurityNumber) + { + var result = socialSecurityNumber.IsValidSocialSecurityNumber(false); + Assert.True(result || !result); + } + + [Theory] + [InlineData("08119043698")] + [InlineData("23017126016")] + [InlineData("03087937150")] + public void IsValidSocialSecurityNumber_ValidNumbers_ReturnsTrue(string number) + { + var result = number.IsValidSocialSecurityNumber(); + Assert.True(result); + } + + [Theory] + [InlineData("08119043698", true)] + [InlineData("12345678901", false)] + public void IsValidSocialSecurityNumber_WithControlDigits(string socialSecurityNumber, bool expected) + { + var result = socialSecurityNumber.IsValidSocialSecurityNumber(true); + Assert.Equal(expected, result); + } + + [Fact] + public void RemoveWhitespace_EmptyOrNullInput_ReturnsInput() + { + Assert.Null(((string)null).RemoveWhitespace()); + Assert.Equal(string.Empty, " ".RemoveWhitespace()); + } + + [Fact] + public void RemoveWhitespace_ValidInput_RemovesWhitespace() + { + var result = " Hello World ".RemoveWhitespace(); + Assert.Equal("HelloWorld", result); + } + + [Theory] + [InlineData("", "")] + [InlineData(" ", "")] + [InlineData(null, null)] + [InlineData("NoSpaces", "NoSpaces")] + [InlineData(" Hello World ", "HelloWorld")] + public void RemoveWhitespace_VariousInputs_ReturnsExpected(string input, string expected) + { + var result = input.RemoveWhitespace(); + Assert.Equal(expected, result); + } +} From 7b1838edb3a26f97cf8d6d027622b9ca974c5b5b Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 09:23:25 +0200 Subject: [PATCH 31/98] Code refactoring --- .../Extensions/StringExtensions.cs | 2 +- .../{IUserContact.cs => IUserContactInfo.cs} | 2 +- ...ult.cs => IUserContactInfoLookupResult.cs} | 8 +++--- .../{UserContact.cs => UserContactInfo.cs} | 2 +- ...sult.cs => UserContactInfoLookupResult.cs} | 6 ++--- ...cs => RegisterToUserContactInfoProfile.cs} | 12 ++++----- .../Repositories/RegisterRepository.cs | 5 +++- .../Services/IRegisterService.cs | 4 +-- .../Services/RegisterService.cs | 27 ++++++++++--------- .../UseCases/UserContactDetailsRetriever.cs | 6 ++--- .../Register/RegisterServiceTests.cs | 12 ++++----- 11 files changed, 45 insertions(+), 41 deletions(-) rename src/Altinn.Profile.Integrations/Entities/{IUserContact.cs => IUserContactInfo.cs} (97%) rename src/Altinn.Profile.Integrations/Entities/{IUserContactResult.cs => IUserContactInfoLookupResult.cs} (76%) rename src/Altinn.Profile.Integrations/Entities/{UserContact.cs => UserContactInfo.cs} (96%) rename src/Altinn.Profile.Integrations/Entities/{UserContactResult.cs => UserContactInfoLookupResult.cs} (74%) rename src/Altinn.Profile.Integrations/Mappings/{RegisterToUserContactProfile.cs => RegisterToUserContactInfoProfile.cs} (75%) diff --git a/src/Altinn.Profile.Core/Extensions/StringExtensions.cs b/src/Altinn.Profile.Core/Extensions/StringExtensions.cs index ebdb9de..b59627c 100644 --- a/src/Altinn.Profile.Core/Extensions/StringExtensions.cs +++ b/src/Altinn.Profile.Core/Extensions/StringExtensions.cs @@ -104,7 +104,7 @@ public static bool IsValidSocialSecurityNumber(this string socialSecurityNumber, return false; } - var isValidSocialSecurityNumber = !controlDigits || CalculateControlDigits(socialSecurityNumberSpan.Slice(0, 9).ToString()) == controlDigitsPart.ToString(); + var isValidSocialSecurityNumber = !controlDigits || CalculateControlDigits(socialSecurityNumberSpan[..9].ToString()) == controlDigitsPart.ToString(); CachedSocialSecurityNumber.TryAdd(socialSecurityNumber, isValidSocialSecurityNumber); diff --git a/src/Altinn.Profile.Integrations/Entities/IUserContact.cs b/src/Altinn.Profile.Integrations/Entities/IUserContactInfo.cs similarity index 97% rename from src/Altinn.Profile.Integrations/Entities/IUserContact.cs rename to src/Altinn.Profile.Integrations/Entities/IUserContactInfo.cs index d3059b3..42ddd45 100644 --- a/src/Altinn.Profile.Integrations/Entities/IUserContact.cs +++ b/src/Altinn.Profile.Integrations/Entities/IUserContactInfo.cs @@ -5,7 +5,7 @@ namespace Altinn.Profile.Integrations.Entities; /// /// Represents a user's contact information. /// -public interface IUserContact +public interface IUserContactInfo { /// /// Gets the national identity number of the user. diff --git a/src/Altinn.Profile.Integrations/Entities/IUserContactResult.cs b/src/Altinn.Profile.Integrations/Entities/IUserContactInfoLookupResult.cs similarity index 76% rename from src/Altinn.Profile.Integrations/Entities/IUserContactResult.cs rename to src/Altinn.Profile.Integrations/Entities/IUserContactInfoLookupResult.cs index 19e474e..cc7c423 100644 --- a/src/Altinn.Profile.Integrations/Entities/IUserContactResult.cs +++ b/src/Altinn.Profile.Integrations/Entities/IUserContactInfoLookupResult.cs @@ -7,15 +7,15 @@ namespace Altinn.Profile.Integrations.Entities; /// /// Defines the result of a user contact information lookup. /// -public interface IUserContactResult +public interface IUserContactInfoLookupResult { /// /// Gets a list of user contact information that was successfully matched during the lookup. /// - ImmutableList? MatchedUserContact { get; init; } + ImmutableList? MatchedUserContact { get; } /// - /// Gets a list of national identity numbers that could not be matched with user contact details. + /// Gets a list of national identity numbers that could not be matched with any user contact details. /// - ImmutableList? UnmatchedNationalIdentityNumbers { get; init; } + ImmutableList? UnmatchedNationalIdentityNumbers { get; } } diff --git a/src/Altinn.Profile.Integrations/Entities/UserContact.cs b/src/Altinn.Profile.Integrations/Entities/UserContactInfo.cs similarity index 96% rename from src/Altinn.Profile.Integrations/Entities/UserContact.cs rename to src/Altinn.Profile.Integrations/Entities/UserContactInfo.cs index 64ae7bc..762de09 100644 --- a/src/Altinn.Profile.Integrations/Entities/UserContact.cs +++ b/src/Altinn.Profile.Integrations/Entities/UserContactInfo.cs @@ -5,7 +5,7 @@ namespace Altinn.Profile.Integrations.Entities; /// /// Represents a user's contact information. /// -public record UserContact : IUserContact +public record UserContactInfo : IUserContactInfo { /// /// Gets the national identity number of the user. diff --git a/src/Altinn.Profile.Integrations/Entities/UserContactResult.cs b/src/Altinn.Profile.Integrations/Entities/UserContactInfoLookupResult.cs similarity index 74% rename from src/Altinn.Profile.Integrations/Entities/UserContactResult.cs rename to src/Altinn.Profile.Integrations/Entities/UserContactInfoLookupResult.cs index a931dff..20e1c73 100644 --- a/src/Altinn.Profile.Integrations/Entities/UserContactResult.cs +++ b/src/Altinn.Profile.Integrations/Entities/UserContactInfoLookupResult.cs @@ -7,15 +7,15 @@ namespace Altinn.Profile.Integrations.Entities; /// /// Represents the result of a user contact information lookup, containing matched and unmatched entries. /// -public record UserContactResult : IUserContactResult +public record UserContactInfoLookupResult : IUserContactInfoLookupResult { /// /// Gets a list of user contact information that was successfully matched during the lookup. /// - public ImmutableList? MatchedUserContact { get; init; } + public ImmutableList? MatchedUserContact { get; init; } /// - /// Gets a list of national identity numbers that could not be matched with user contact details. + /// Gets a list of national identity numbers that could not be matched with any user contact details. /// public ImmutableList? UnmatchedNationalIdentityNumbers { get; init; } } diff --git a/src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactProfile.cs b/src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactInfoProfile.cs similarity index 75% rename from src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactProfile.cs rename to src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactInfoProfile.cs index 5c922b7..7c74596 100644 --- a/src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactProfile.cs +++ b/src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactInfoProfile.cs @@ -3,20 +3,20 @@ namespace Altinn.Profile.Integrations.Mappings; /// -/// AutoMapper profile for mapping between and . +/// AutoMapper profile for mapping between and . /// /// -/// This profile defines the mapping rules to convert a object into a instance. +/// This profile defines the mapping rules to convert a object into a instance. /// -public class RegisterToUserContactProfile : AutoMapper.Profile +public class RegisterToUserContactInfoProfile : AutoMapper.Profile { /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// and configures the mappings. /// - public RegisterToUserContactProfile() + public RegisterToUserContactInfoProfile() { - CreateMap() + CreateMap() .ForMember(dest => dest.IsReserved, opt => opt.MapFrom(src => src.Reservation)) .ForMember(dest => dest.EmailAddress, opt => opt.MapFrom(src => src.EmailAddress)) .ForMember(dest => dest.LanguageCode, opt => opt.MapFrom(src => src.LanguageCode)) diff --git a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs index 027a866..bb96f22 100644 --- a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs @@ -34,9 +34,12 @@ public RegisterRepository(ProfileDbContext context) : base(context) /// /// A task that represents the asynchronous operation. The task result contains a collection of register data for the users. /// + /// Thrown when the is null. public async Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers) { - var registers = await _context.Registers.Where(k => nationalIdentityNumbers.Contains(k.FnumberAk)).ToListAsync(); + ArgumentNullException.ThrowIfNull(nationalIdentityNumbers, nameof(nationalIdentityNumbers)); + + var registers = await _context.Registers.Where(e => nationalIdentityNumbers.Contains(e.FnumberAk)).ToListAsync(); return registers.ToImmutableList(); } diff --git a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs index 7c6feea..d64aa80 100644 --- a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs @@ -17,7 +17,7 @@ public interface IRegisterService /// /// A task that represents the asynchronous operation. The task result contains the user's contact information, or null if not found. /// - Task GetUserContactAsync(string nationalIdentityNumber); + Task GetUserContactAsync(string nationalIdentityNumber); /// /// Asynchronously retrieves the contact information for multiple users based on their national identity numbers. @@ -26,5 +26,5 @@ public interface IRegisterService /// /// A task that represents the asynchronous operation. The task result contains a collection of user contact information, or an empty collection if none are found. /// - Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers); + Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers); } diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs index ec387d3..f066f83 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -38,7 +38,7 @@ public RegisterService(IMapper mapper, IRegisterRepository registerRepository, I /// /// A task that represents the asynchronous operation. The task result contains the user's contact information, or null if not found. /// - public async Task GetUserContactAsync(string nationalIdentityNumber) + public async Task GetUserContactAsync(string nationalIdentityNumber) { if (!_nationalIdentityNumberChecker.IsValid(nationalIdentityNumber)) { @@ -46,7 +46,7 @@ public RegisterService(IMapper mapper, IRegisterRepository registerRepository, I } var userContactInfoEntity = await _registerRepository.GetUserContactInfoAsync([nationalIdentityNumber]); - return _mapper.Map(userContactInfoEntity); + return _mapper.Map(userContactInfoEntity); } /// @@ -56,23 +56,24 @@ public RegisterService(IMapper mapper, IRegisterRepository registerRepository, I /// /// A task that represents the asynchronous operation. The task result contains a collection of user contact information, or an empty collection if none are found. /// - public async Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers) + /// Thrown if is null. + public async Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers) { - var (validSocialSecurityNumbers, invalidSocialSecurityNumbers) = _nationalIdentityNumberChecker.Categorize(nationalIdentityNumbers); + ArgumentNullException.ThrowIfNull(nationalIdentityNumbers, nameof(nationalIdentityNumbers)); - var userContactInfoEntities = await _registerRepository.GetUserContactInfoAsync(validSocialSecurityNumbers); + var (validnNtionalIdentityNumbers, invalidNationalIdentityNumbers) = _nationalIdentityNumberChecker.Categorize(nationalIdentityNumbers); - var matchedUserContact = userContactInfoEntities.Select(_mapper.Map); + var usersContactInfo = await _registerRepository.GetUserContactInfoAsync(validnNtionalIdentityNumbers); - var unmatchedUserContact = nationalIdentityNumbers - .Except(userContactInfoEntities.Select(e => e.FnumberAk)) - .Select(e => new UserContact { NationalIdentityNumber = e }) - .Select(_mapper.Map); + var matchedUserContact = usersContactInfo.Select(_mapper.Map); - return new UserContactResult + var matchedNationalIdentityNumbers = new HashSet(usersContactInfo.Select(e => e.FnumberAk)); + var unmatchedNationalIdentityNumbers = nationalIdentityNumbers.Where(e => !matchedNationalIdentityNumbers.Contains(e)); + + return new UserContactInfoLookupResult { - MatchedUserContact = matchedUserContact?.ToImmutableList(), - UnmatchedUserContact = unmatchedUserContact?.ToImmutableList(), + MatchedUserContact = matchedUserContact?.ToImmutableList(), + UnmatchedNationalIdentityNumbers = unmatchedNationalIdentityNumbers.ToImmutableList() }; } } diff --git a/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs b/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs index 6b39d68..967d355 100644 --- a/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs +++ b/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs @@ -50,12 +50,12 @@ public async Task> RetrieveAsync(Us } /// - /// Maps an to a . + /// Maps an to a . /// /// The user contact details to map. /// The mapped . /// Thrown when is null. - private UserContactDetails MapToUserContactDetails(IUserContact userContactDetails) + private UserContactDetails MapToUserContactDetails(IUserContactInfo userContactDetails) { ArgumentNullException.ThrowIfNull(userContactDetails, nameof(userContactDetails)); @@ -74,7 +74,7 @@ private UserContactDetails MapToUserContactDetails(IUserContact userContactDetai /// /// The user contact details lookup result. /// A containing the mapped user contact details. - private Result MapToUserContactDetailsResult(IUserContactResult userContactResult) + private Result MapToUserContactDetailsResult(IUserContactInfoLookupResult userContactResult) { var unmatchedNationalIdentityNumbers = userContactResult?.UnmatchedNationalIdentityNumbers ?? null; var matchedUserContactDetails = userContactResult?.MatchedUserContact?.Select(MapToUserContactDetails).ToImmutableList(); diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs index e6f4b7e..5cf9a15 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs @@ -130,9 +130,9 @@ private void SetupRegisterRepository(params Register[] registers) ///_mockRegisterRepository.Setup(repo => repo.GetUserContactInfoAsync(It.IsAny>())).ReturnsAsync(registers.AsEnumerable()); } - private static Mock SetupUserContactInfo(Register register) + private static Mock SetupUserContactInfo(Register register) { - var mockUserContactInfo = new Mock(); + var mockUserContactInfo = new Mock(); mockUserContactInfo.SetupGet(u => u.IsReserved).Returns(register.Reservation); mockUserContactInfo.SetupGet(u => u.LanguageCode).Returns(register.LanguageCode); mockUserContactInfo.SetupGet(u => u.EmailAddress).Returns(register.EmailAddress); @@ -141,17 +141,17 @@ private static Mock SetupUserContactInfo(Register register) return mockUserContactInfo; } - private void SetupMapper(params (Register Register, Mock UserContactInfo)[] mappings) + private void SetupMapper(params (Register Register, Mock UserContactInfo)[] mappings) { - _mockMapper.Setup(m => m.Map>(It.IsAny>())) + _mockMapper.Setup(m => m.Map>(It.IsAny>())) .Returns((IEnumerable registers) => registers.Select(r => mappings.FirstOrDefault(m => m.Register.FnumberAk == r.FnumberAk).UserContactInfo.Object) .Where(u => u != null) - .Cast()); + .Cast()); } - private static void AssertUserContactInfoMatches(Register expectedRegister, IUserContact actualContactInfo) + private static void AssertUserContactInfoMatches(Register expectedRegister, IUserContactInfo actualContactInfo) { Assert.Equal(expectedRegister.Reservation, actualContactInfo.IsReserved); Assert.Equal(expectedRegister.EmailAddress, actualContactInfo.EmailAddress); From 0280fdfeba3ce8ca8f5be513f86fe31d8e40833e Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 13:15:11 +0200 Subject: [PATCH 32/98] Remove old unit tests --- .../Register/RegisterServiceTests.cs | 138 ------------------ 1 file changed, 138 deletions(-) diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs index 5cf9a15..690e7ef 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs @@ -1,10 +1,5 @@ #nullable enable -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -using Altinn.Profile.Integrations.Entities; using Altinn.Profile.Integrations.Repositories; using Altinn.Profile.Integrations.Services; @@ -12,8 +7,6 @@ using Moq; -using Xunit; - namespace Altinn.Profile.Tests.Profile.Integrations; public class RegisterServiceTests @@ -28,135 +21,4 @@ public RegisterServiceTests() _mockRegisterRepository = new Mock(); _mockNationalIdentityNumberChecker = new Mock(); } - - [Fact] - public async Task GetUserContactInfoAsync_ReturnsMappedInfo_WhenValidIds() - { - // Arrange - var ids = new List { "11103048704", "30039302787" }; - - var firstRegister = new Register - { - Reservation = false, - LanguageCode = "NO", - FnumberAk = "11103048704", - MobilePhoneNumber = "1234567890", - EmailAddress = "test1@example.com", - Description = "Test Description 1", - MailboxAddress = "Test Mailbox Address 1" - }; - - var secondRegister = new Register - { - Reservation = false, - LanguageCode = "NO", - FnumberAk = "30039302787", - MobilePhoneNumber = "0987654321", - EmailAddress = "test2@example.com", - Description = "Test Description 2", - MailboxAddress = "Test Mailbox Address 2" - }; - - SetupRegisterRepository(firstRegister, secondRegister); - - var firstUserContactInfo = SetupUserContactInfo(firstRegister); - var secondUserContactInfo = SetupUserContactInfo(secondRegister); - - SetupMapper((firstRegister, firstUserContactInfo), (secondRegister, secondUserContactInfo)); - - // Act - var registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object, _mockNationalIdentityNumberChecker.Object); - var result = await registerService.GetUserContactAsync(ids); - - // Assert - Assert.NotNull(result); - ///Assert.Equal(2, result.Count()); - ///AssertUserContactInfoMatches(firstRegister, result.ElementAt(0)); - ///AssertUserContactInfoMatches(secondRegister, result.ElementAt(1)); - } - - [Fact] - public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenInvalidNationalIds() - { - // Arrange - var nationalIdentityNumbers = new List { "invalid1", "invalid2" }; - - SetupRegisterRepository(); // Return empty - - // Act - var registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object, _mockNationalIdentityNumberChecker.Object); - var result = await registerService.GetUserContactAsync(nationalIdentityNumbers); - - // Assert - Assert.NotNull(result); - ///Assert.Empty(result); - } - - [Fact] - public async Task GetUserContactInfoAsync_ReturnsMapped_WhenOneValidAndOneInvalidId() - { - // Arrange - var nationalIdentityNumbers = new List { "11103048704", "invalid" }; - - var validRegister = new Register - { - Reservation = false, - LanguageCode = "NO", - FnumberAk = "11103048704", - MobilePhoneNumber = "1234567890", - EmailAddress = "test@example.com", - Description = "Test Description", - MailboxAddress = "Test Mailbox Address" - }; - - SetupRegisterRepository(validRegister); - - var mockUserContactInfo = SetupUserContactInfo(validRegister); - - SetupMapper((validRegister, mockUserContactInfo)); - - // Act - var registerService = new RegisterService(_mockMapper.Object, _mockRegisterRepository.Object, _mockNationalIdentityNumberChecker.Object); - var result = await registerService.GetUserContactAsync(nationalIdentityNumbers); - - // Assert - Assert.NotNull(result); - ///Assert.Single(result); - ///AssertUserContactInfoMatches(validRegister, result.First()); - } - - private void SetupRegisterRepository(params Register[] registers) - { - ///_mockRegisterRepository.Setup(repo => repo.GetUserContactInfoAsync(It.IsAny>())).ReturnsAsync(registers.AsEnumerable()); - } - - private static Mock SetupUserContactInfo(Register register) - { - var mockUserContactInfo = new Mock(); - mockUserContactInfo.SetupGet(u => u.IsReserved).Returns(register.Reservation); - mockUserContactInfo.SetupGet(u => u.LanguageCode).Returns(register.LanguageCode); - mockUserContactInfo.SetupGet(u => u.EmailAddress).Returns(register.EmailAddress); - mockUserContactInfo.SetupGet(u => u.NationalIdentityNumber).Returns(register.FnumberAk); - mockUserContactInfo.SetupGet(u => u.MobilePhoneNumber).Returns(register.MobilePhoneNumber); - return mockUserContactInfo; - } - - private void SetupMapper(params (Register Register, Mock UserContactInfo)[] mappings) - { - _mockMapper.Setup(m => m.Map>(It.IsAny>())) - .Returns((IEnumerable registers) => - registers.Select(r => - mappings.FirstOrDefault(m => m.Register.FnumberAk == r.FnumberAk).UserContactInfo.Object) - .Where(u => u != null) - .Cast()); - } - - private static void AssertUserContactInfoMatches(Register expectedRegister, IUserContactInfo actualContactInfo) - { - Assert.Equal(expectedRegister.Reservation, actualContactInfo.IsReserved); - Assert.Equal(expectedRegister.EmailAddress, actualContactInfo.EmailAddress); - Assert.Equal(expectedRegister.LanguageCode, actualContactInfo.LanguageCode); - Assert.Equal(expectedRegister.FnumberAk, actualContactInfo.NationalIdentityNumber); - Assert.Equal(expectedRegister.MobilePhoneNumber, actualContactInfo.MobilePhoneNumber); - } } From e074f561c8ca608351784ff29e1672b1697e910b Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 13:40:15 +0200 Subject: [PATCH 33/98] Fix typos --- .../Services/IRegisterService.cs | 4 ++-- .../Services/NationalIdentityNumberChecker.cs | 6 +++--- src/Altinn.Profile.Integrations/Services/RegisterService.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs index d64aa80..57c971c 100644 --- a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/IRegisterService.cs @@ -6,7 +6,7 @@ namespace Altinn.Profile.Integrations.Services; /// -/// Defines a service for handling operations related to user register data. +/// Defines a service for handling operations related to user contact information. /// public interface IRegisterService { @@ -17,7 +17,7 @@ public interface IRegisterService /// /// A task that represents the asynchronous operation. The task result contains the user's contact information, or null if not found. /// - Task GetUserContactAsync(string nationalIdentityNumber); + Task GetUserContactInfoAsync(string nationalIdentityNumber); /// /// Asynchronously retrieves the contact information for multiple users based on their national identity numbers. diff --git a/src/Altinn.Profile.Integrations/Services/NationalIdentityNumberChecker.cs b/src/Altinn.Profile.Integrations/Services/NationalIdentityNumberChecker.cs index 850371f..e1e485a 100644 --- a/src/Altinn.Profile.Integrations/Services/NationalIdentityNumberChecker.cs +++ b/src/Altinn.Profile.Integrations/Services/NationalIdentityNumberChecker.cs @@ -22,10 +22,10 @@ public class NationalIdentityNumberChecker : INationalIdentityNumberChecker /// public (IImmutableList Valid, IImmutableList Invalid) Categorize(IEnumerable nationalIdentityNumbers) { - var validSocialSecurityNumbers = nationalIdentityNumbers.Where(e => e.IsValidSocialSecurityNumber()).ToImmutableList(); - var invalidSocialSecurityNumbers = nationalIdentityNumbers.Except(validSocialSecurityNumbers).ToImmutableList(); + var validNnationalIdentityNumbers = nationalIdentityNumbers.Where(e => e.IsValidSocialSecurityNumber()).ToImmutableList(); + var invalidNnationalIdentityNumbers = nationalIdentityNumbers.Except(validNnationalIdentityNumbers).ToImmutableList(); - return (validSocialSecurityNumbers, invalidSocialSecurityNumbers); + return (validNnationalIdentityNumbers, invalidNnationalIdentityNumbers); } /// diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs index f066f83..b3d5f18 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -38,7 +38,7 @@ public RegisterService(IMapper mapper, IRegisterRepository registerRepository, I /// /// A task that represents the asynchronous operation. The task result contains the user's contact information, or null if not found. /// - public async Task GetUserContactAsync(string nationalIdentityNumber) + public async Task GetUserContactInfoAsync(string nationalIdentityNumber) { if (!_nationalIdentityNumberChecker.IsValid(nationalIdentityNumber)) { From 02df646a0112564beab4fe6a4c7601354c31e6a9 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 14:30:01 +0200 Subject: [PATCH 34/98] Fix a conflict --- .../ServiceCollectionExtensions.cs | 24 +++++++++++-------- src/Altinn.Profile/Program.cs | 2 +- 2 files changed, 15 insertions(+), 11 deletions(-) rename src/Altinn.Profile.Integrations/{Extensions => }/ServiceCollectionExtensions.cs (70%) diff --git a/src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs similarity index 70% rename from src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs rename to src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs index 72a92e9..da952e2 100644 --- a/src/Altinn.Profile.Integrations/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs @@ -1,33 +1,37 @@ using Altinn.Profile.Core.Integrations; +using Altinn.Profile.Integrations.Extensions; using Altinn.Profile.Integrations.Persistence; using Altinn.Profile.Integrations.Repositories; using Altinn.Profile.Integrations.SblBridge; +using Altinn.Profile.Integrations.SblBridge.Unit.Profile; +using Altinn.Profile.Integrations.SblBridge.User.Profile; using Altinn.Profile.Integrations.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -namespace Altinn.Profile.Integrations.Extensions; +namespace Altinn.Profile.Integrations; /// -/// Extension class for to add services and configurations. +/// Extension class for /// public static class ServiceCollectionExtensions { /// - /// Adds SBL Bridge clients and configurations to the DI container. + /// Adds Altinn clients and configurations to DI container. /// - /// The service collection. - /// The configuration collection. - /// Thrown when the required SblBridgeSettings are missing from the configuration. + /// service collection. + /// the configuration collection public static void AddSblBridgeClients(this IServiceCollection services, IConfiguration config) { - var sblBridgeSettings = config.GetSection(nameof(SblBridgeSettings)).Get() ?? throw new ArgumentNullException(nameof(config), "Required SblBridgeSettings is missing from application configuration"); - services.Configure(config.GetSection(nameof(SblBridgeSettings))); + _ = config.GetSection(nameof(SblBridgeSettings)) + .Get() + ?? throw new ArgumentNullException(nameof(config), "Required SblBridgeSettings is missing from application configuration"); - services.AddHttpClient(); - services.AddHttpClient(); + services.Configure(config.GetSection(nameof(SblBridgeSettings))); + services.AddHttpClient(); + services.AddHttpClient(); } /// diff --git a/src/Altinn.Profile/Program.cs b/src/Altinn.Profile/Program.cs index 5cdfad4..50caefd 100644 --- a/src/Altinn.Profile/Program.cs +++ b/src/Altinn.Profile/Program.cs @@ -10,7 +10,7 @@ using Altinn.Profile.Core.Extensions; using Altinn.Profile.Filters; using Altinn.Profile.Health; -using Altinn.Profile.Integrations.Extensions; +using Altinn.Profile.Integrations; using Altinn.Profile.UseCases; using AltinnCore.Authentication.JwtCookie; From 3beb232531f09cb7c2519d980328243aa6120619 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 14:44:05 +0200 Subject: [PATCH 35/98] Rewrite a number of unit tests --- .../Register/RegisterRepositoryTests.cs | 86 +++++---------- .../Testdata/PersonTestData.cs | 102 ++++++++++++++++++ 2 files changed, 129 insertions(+), 59 deletions(-) create mode 100644 test/Altinn.Profile.Tests/Testdata/PersonTestData.cs diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs index b1f2832..e7d3d15 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs @@ -6,6 +6,7 @@ using Altinn.Profile.Integrations.Entities; using Altinn.Profile.Integrations.Persistence; using Altinn.Profile.Integrations.Repositories; +using Altinn.Profile.Tests.Testdata; using Microsoft.EntityFrameworkCore; @@ -20,12 +21,21 @@ public class RegisterRepositoryTests : IDisposable { private readonly ProfileDbContext _context; private readonly RegisterRepository _registerRepository; - + private readonly List _personContactAndReservationTestData; + public RegisterRepositoryTests() { - var options = new DbContextOptionsBuilder().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options; + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + _context = new ProfileDbContext(options); _registerRepository = new RegisterRepository(_context); + + _personContactAndReservationTestData = [.. PersonTestData.GetContactAndReservationTestData()]; + + _context.Registers.AddRange(_personContactAndReservationTestData); + _context.SaveChanges(); } public void Dispose() @@ -38,34 +48,27 @@ public void Dispose() [Fact] public async Task GetUserContactInfoAsync_ReturnsContactInfo_WhenFound() { - // Arrange - var register = CreateRegister("21102709516", "Test Description", "test@example.com", "Test Mailbox Address"); - - await AddRegisterToContext(register); - // Act - var result = await _registerRepository.GetUserContactInfoAsync(["21102709516"]); + var result = await _registerRepository.GetUserContactInfoAsync(new[] { "17111933790" }); + + var actual = result.FirstOrDefault(e => e.FnumberAk == "17111933790"); + var expected = _personContactAndReservationTestData.FirstOrDefault(e => e.FnumberAk == "17111933790"); // Assert - AssertSingleRegister(register, result.First()); + Assert.NotNull(actual); + AssertRegisterProperties(expected, actual); } [Fact] public async Task GetUserContactInfoAsync_ReturnsCorrectResults_WhenValidAndInvalidNumbers() { - // Arrange - var validRegister = CreateRegister("21102709516", "Valid Test Description", "valid@example.com", "Valid Mailbox Address"); - - await AddRegisterToContext(validRegister); - // Act - var result = await _registerRepository.GetUserContactInfoAsync(["21102709516", "nonexistent2"]); - - // Assert valid result - AssertSingleRegister(validRegister, result.FirstOrDefault(r => r.FnumberAk == "21102709516")); + var result = _personContactAndReservationTestData.Where(e => e.FnumberAk == "28026698350"); + var expected = await _registerRepository.GetUserContactInfoAsync(["28026698350", "nonexistent2"]); // Assert invalid result - Assert.Null(result.FirstOrDefault(r => r.FnumberAk == "nonexistent2")); + Assert.Single(result); + AssertRegisterProperties(expected.FirstOrDefault(), result.FirstOrDefault()); } [Fact] @@ -81,25 +84,16 @@ public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenNoneFound() [Fact] public async Task GetUserContactInfoAsync_ReturnsMultipleContacts_WhenFound() { - // Arrange - var registers = new List - { - CreateRegister("03062701187", "Test Description 1", "test1@example.com", "Test Mailbox Address 1", false), - CreateRegister("02024333593", "Test Description 2", "test2@example.com", "Test Mailbox Address 2", true) - }; - - await _context.Registers.AddRangeAsync(registers); - await _context.SaveChangesAsync(); - // Act - var result = await _registerRepository.GetUserContactInfoAsync(["03062701187", "02024333593"]); + var result = await _registerRepository.GetUserContactInfoAsync(["24064316776", "11044314101"]); + var expected = _personContactAndReservationTestData.Where(e => e.FnumberAk == "24064316776" || e.FnumberAk == "11044314101"); // Assert Assert.Equal(2, result.Count()); - foreach (var register in registers) + foreach (var register in result) { - var foundRegister = result.FirstOrDefault(r => r.FnumberAk == register.FnumberAk); + var foundRegister = expected.FirstOrDefault(r => r.FnumberAk == register.FnumberAk); Assert.NotNull(foundRegister); AssertRegisterProperties(register, foundRegister); } @@ -109,24 +103,12 @@ public async Task GetUserContactInfoAsync_ReturnsMultipleContacts_WhenFound() public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenNotFound() { // Act - var result = await _registerRepository.GetUserContactInfoAsync(["nonexistent"]); + var result = await _registerRepository.GetUserContactInfoAsync(["nonexistent", "11044314120"]); // Assert Assert.Empty(result); } - private async Task AddRegisterToContext(Register register) - { - await _context.Registers.AddAsync(register); - await _context.SaveChangesAsync(); - } - - private static void AssertSingleRegister(Register expected, Register actual) - { - Assert.NotNull(actual); - AssertRegisterProperties(expected, actual); - } - private static void AssertRegisterProperties(Register expected, Register actual) { Assert.Equal(expected.FnumberAk, actual.FnumberAk); @@ -137,18 +119,4 @@ private static void AssertRegisterProperties(Register expected, Register actual) Assert.Equal(expected.MailboxAddress, actual.MailboxAddress); Assert.Equal(expected.MobilePhoneNumber, actual.MobilePhoneNumber); } - - private static Register CreateRegister(string fnumberAk, string description, string emailAddress, string mailboxAddress, bool reservation = true) - { - return new Register - { - LanguageCode = "EN", - FnumberAk = fnumberAk, - Reservation = reservation, - Description = description, - EmailAddress = emailAddress, - MailboxAddress = mailboxAddress, - MobilePhoneNumber = "1234567890", - }; - } } diff --git a/test/Altinn.Profile.Tests/Testdata/PersonTestData.cs b/test/Altinn.Profile.Tests/Testdata/PersonTestData.cs new file mode 100644 index 0000000..7f44ba7 --- /dev/null +++ b/test/Altinn.Profile.Tests/Testdata/PersonTestData.cs @@ -0,0 +1,102 @@ +using System.Collections.Generic; + +using Altinn.Profile.Integrations.Entities; + +namespace Altinn.Profile.Tests.Testdata; + +/// +/// Provides test data for person contact information. +/// +public static class PersonTestData +{ + /// + /// Gets a list of test registers with predefined contact and reservation data. + /// + /// A list of objects containing test data. + public static List GetContactAndReservationTestData() + { + return + [ + new() + { + LanguageCode = "nb", + Reservation = false, + FnumberAk = "17111933790", + EmailAddress = "user1@example.com", + MobilePhoneNumber = "+4790077853" + }, + new() + { + LanguageCode = "nn", + Reservation = false, + FnumberAk = "06010941251", + EmailAddress = "user2@example.com", + MobilePhoneNumber = "+4790077854" + }, + new() + { + LanguageCode = "en", + Reservation = false, + FnumberAk = "28026698350", + EmailAddress = "user3@example.com", + MobilePhoneNumber = "+4790077855" + }, + new() + { + LanguageCode = "nb", + Reservation = false, + FnumberAk = "08117494927", + EmailAddress = "user4@example.com", + MobilePhoneNumber = "+4790077856" + }, + new() + { + LanguageCode = "nn", + Reservation = false, + FnumberAk = "11044314101", + EmailAddress = "user5@example.com", + MobilePhoneNumber = "+4790077857" + }, + new() + { + LanguageCode = "en", + Reservation = false, + FnumberAk = "07035704609", + EmailAddress = "user6@example.com", + MobilePhoneNumber = "+4790077858" + }, + new() + { + LanguageCode = "nb", + Reservation = false, + FnumberAk = "24064316776", + EmailAddress = "user7@example.com", + MobilePhoneNumber = "+4790077859" + }, + new() + { + LanguageCode = "nn", + Reservation = false, + FnumberAk = "20011400125", + EmailAddress = "user8@example.com", + MobilePhoneNumber = "+4790077860" + }, + new() + { + LanguageCode = "en", + Reservation = false, + FnumberAk = "13049846538", + EmailAddress = "user9@example.com", + MobilePhoneNumber = "+4790077861" + }, + new() + { + LanguageCode = "nb", + Reservation = false, + FnumberAk = "13045517963", + EmailAddress = "user10@example.com", + MobilePhoneNumber = "+4790077862" + } + ]; + } +} From 2aeb39e887edbd25021d992e37f909e2616fb295 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 15:11:49 +0200 Subject: [PATCH 36/98] Improve a comment --- test/Altinn.Profile.Tests/Testdata/PersonTestData.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Altinn.Profile.Tests/Testdata/PersonTestData.cs b/test/Altinn.Profile.Tests/Testdata/PersonTestData.cs index 7f44ba7..c8b066f 100644 --- a/test/Altinn.Profile.Tests/Testdata/PersonTestData.cs +++ b/test/Altinn.Profile.Tests/Testdata/PersonTestData.cs @@ -5,7 +5,7 @@ namespace Altinn.Profile.Tests.Testdata; /// -/// Provides test data for person contact information. +/// Provides test data for person contact and reservation information. /// public static class PersonTestData { From a260f477315bfb94454dd56c36519a99d2e6a981 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 15:20:13 +0200 Subject: [PATCH 37/98] Update the internal endpoint address --- .../Controllers/UserContactDetailsInternalController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs b/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs index 43ef1c7..e57db04 100644 --- a/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs +++ b/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs @@ -16,7 +16,7 @@ namespace Altinn.Profile.Controllers; [Consumes("application/json")] [Produces("application/json")] [ApiExplorerSettings(IgnoreApi = true)] -[Route("profile/api/v1/contact/details/internal")] +[Route("profile/api/v1/internal/contact/details")] public class UserContactDetailsInternalController : ControllerBase { private readonly IUserContactDetailsRetriever _contactDetailsRetriever; From 62fd87382747849fdab1b87db2374c466cf22d3a Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 15:21:43 +0200 Subject: [PATCH 38/98] Shorten the error message --- src/Altinn.Profile/Controllers/UserContactDetailsController.cs | 2 +- .../Controllers/UserContactDetailsInternalController.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Altinn.Profile/Controllers/UserContactDetailsController.cs b/src/Altinn.Profile/Controllers/UserContactDetailsController.cs index b535c55..ffffb99 100644 --- a/src/Altinn.Profile/Controllers/UserContactDetailsController.cs +++ b/src/Altinn.Profile/Controllers/UserContactDetailsController.cs @@ -47,6 +47,6 @@ public async Task> PostLookup([From return result.Match>( success => Ok(success), - failure => Problem("Unable to retrieve contact details. Please verify the provided national identity numbers and try again.")); + failure => Problem("Unable to retrieve contact details.")); } } diff --git a/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs b/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs index e57db04..5ccb5d8 100644 --- a/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs +++ b/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs @@ -46,6 +46,6 @@ public async Task> PostLookup([From return result.Match>( success => Ok(success), - failure => Problem("Unable to retrieve contact details. Please verify the provided national identity numbers and try again.")); + failure => Problem("Unable to retrieve contact details.")); } } From 2708b4f27c19144951e5faebac4d8d96077983b6 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 15:24:35 +0200 Subject: [PATCH 39/98] Remove the parameter name argument. --- src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs b/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs index 967d355..fd02b92 100644 --- a/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs +++ b/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs @@ -57,7 +57,7 @@ public async Task> RetrieveAsync(Us /// Thrown when is null. private UserContactDetails MapToUserContactDetails(IUserContactInfo userContactDetails) { - ArgumentNullException.ThrowIfNull(userContactDetails, nameof(userContactDetails)); + ArgumentNullException.ThrowIfNull(userContactDetails); return new UserContactDetails { From 8ca0b592766805734471d76ea0c1c1ddaa9eaaa8 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 15:33:07 +0200 Subject: [PATCH 40/98] Remove two unused variables --- .../Repositories/ProfileRepository.cs | 12 +----------- .../Repositories/RegisterRepository.cs | 4 ++-- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/Altinn.Profile.Integrations/Repositories/ProfileRepository.cs b/src/Altinn.Profile.Integrations/Repositories/ProfileRepository.cs index 6477900..bf4308a 100644 --- a/src/Altinn.Profile.Integrations/Repositories/ProfileRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/ProfileRepository.cs @@ -1,9 +1,6 @@ #nullable enable using Altinn.Profile.Core.Domain; -using Altinn.Profile.Integrations.Persistence; - -using Microsoft.EntityFrameworkCore; namespace Altinn.Profile.Integrations.Repositories; @@ -15,18 +12,11 @@ namespace Altinn.Profile.Integrations.Repositories; internal class ProfileRepository : IRepository where T : class { - private readonly ProfileDbContext _context; - private readonly DbSet _dbSet; - /// /// Initializes a new instance of the class. /// - /// The database context. - /// Thrown when the object is null. - internal ProfileRepository(ProfileDbContext context) + internal ProfileRepository() { - _context = context ?? throw new ArgumentNullException(nameof(context)); - _dbSet = _context.Set(); } /// diff --git a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs index bb96f22..d3d5c8a 100644 --- a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs @@ -22,7 +22,7 @@ internal class RegisterRepository : ProfileRepository, IRegisterReposi /// /// The context. /// Thrown when the object is null. - public RegisterRepository(ProfileDbContext context) : base(context) + public RegisterRepository(ProfileDbContext context) { _context = context ?? throw new ArgumentNullException(nameof(context)); } @@ -37,7 +37,7 @@ public RegisterRepository(ProfileDbContext context) : base(context) /// Thrown when the is null. public async Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers) { - ArgumentNullException.ThrowIfNull(nationalIdentityNumbers, nameof(nationalIdentityNumbers)); + ArgumentNullException.ThrowIfNull(nationalIdentityNumbers); var registers = await _context.Registers.Where(e => nationalIdentityNumbers.Contains(e.FnumberAk)).ToListAsync(); From 201732e195caca06f13ea85038e67471a22684e1 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 15:34:26 +0200 Subject: [PATCH 41/98] Changed the Register name to Person --- .../{IRegisterRepository.cs => IPersonRepository.cs} | 2 +- .../{RegisterRepository.cs => PersonRepository.cs} | 8 ++++---- .../ServiceCollectionExtensions.cs | 2 +- .../Services/RegisterService.cs | 4 ++-- .../Register/RegisterRepositoryTests.cs | 6 +++--- .../Profile.Integrations/Register/RegisterServiceTests.cs | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) rename src/Altinn.Profile.Integrations/Repositories/{IRegisterRepository.cs => IPersonRepository.cs} (92%) rename src/Altinn.Profile.Integrations/Repositories/{RegisterRepository.cs => PersonRepository.cs} (85%) diff --git a/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/IPersonRepository.cs similarity index 92% rename from src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs rename to src/Altinn.Profile.Integrations/Repositories/IPersonRepository.cs index 358e4d6..7c3ec19 100644 --- a/src/Altinn.Profile.Integrations/Repositories/IRegisterRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/IPersonRepository.cs @@ -10,7 +10,7 @@ namespace Altinn.Profile.Integrations.Repositories; /// /// Repository for handling register data. /// -public interface IRegisterRepository : IRepository +public interface IPersonRepository : IRepository { /// /// Asynchronously retrieves the register data for multiple users by their national identity numbers. diff --git a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs b/src/Altinn.Profile.Integrations/Repositories/PersonRepository.cs similarity index 85% rename from src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs rename to src/Altinn.Profile.Integrations/Repositories/PersonRepository.cs index d3d5c8a..376cf83 100644 --- a/src/Altinn.Profile.Integrations/Repositories/RegisterRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/PersonRepository.cs @@ -12,17 +12,17 @@ namespace Altinn.Profile.Integrations.Repositories; /// /// Repository for handling register data. /// -/// -internal class RegisterRepository : ProfileRepository, IRegisterRepository +/// +internal class PersonRepository : ProfileRepository, IPersonRepository { private readonly ProfileDbContext _context; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The context. /// Thrown when the object is null. - public RegisterRepository(ProfileDbContext context) + public PersonRepository(ProfileDbContext context) { _context = context ?? throw new ArgumentNullException(nameof(context)); } diff --git a/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs b/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs index da952e2..6895669 100644 --- a/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs +++ b/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs @@ -59,7 +59,7 @@ public static void AddRegisterService(this IServiceCollection services, IConfigu services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddSingleton(); } diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs index b3d5f18..7d729e4 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -14,7 +14,7 @@ namespace Altinn.Profile.Integrations.Services; public class RegisterService : IRegisterService { private readonly IMapper _mapper; - private readonly IRegisterRepository _registerRepository; + private readonly IPersonRepository _registerRepository; private readonly INationalIdentityNumberChecker _nationalIdentityNumberChecker; /// @@ -24,7 +24,7 @@ public class RegisterService : IRegisterService /// The repository used for accessing register data. /// The service used for checking the validity of national identity numbers. /// Thrown if , , or is null. - public RegisterService(IMapper mapper, IRegisterRepository registerRepository, INationalIdentityNumberChecker nationalIdentityNumberChecker) + public RegisterService(IMapper mapper, IPersonRepository registerRepository, INationalIdentityNumberChecker nationalIdentityNumberChecker) { _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); _registerRepository = registerRepository ?? throw new ArgumentNullException(nameof(registerRepository)); diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs index e7d3d15..ba111f1 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs @@ -15,12 +15,12 @@ namespace Altinn.Profile.Tests.Profile.Integrations; /// -/// Contains unit tests for the class. +/// Contains unit tests for the class. /// public class RegisterRepositoryTests : IDisposable { private readonly ProfileDbContext _context; - private readonly RegisterRepository _registerRepository; + private readonly PersonRepository _registerRepository; private readonly List _personContactAndReservationTestData; public RegisterRepositoryTests() @@ -30,7 +30,7 @@ public RegisterRepositoryTests() .Options; _context = new ProfileDbContext(options); - _registerRepository = new RegisterRepository(_context); + _registerRepository = new PersonRepository(_context); _personContactAndReservationTestData = [.. PersonTestData.GetContactAndReservationTestData()]; diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs index 690e7ef..31df335 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs @@ -11,14 +11,14 @@ namespace Altinn.Profile.Tests.Profile.Integrations; public class RegisterServiceTests { - private readonly Mock _mockRegisterRepository; + private readonly Mock _mockRegisterRepository; private readonly Mock _mockNationalIdentityNumberChecker; private readonly Mock _mockMapper; public RegisterServiceTests() { _mockMapper = new Mock(); - _mockRegisterRepository = new Mock(); + _mockRegisterRepository = new Mock(); _mockNationalIdentityNumberChecker = new Mock(); } } From 22264d288bf0955c20434220b389f92a7a12dabe Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 15:38:02 +0200 Subject: [PATCH 42/98] Add the default parameter value defined in the overridden method. --- .../Repositories/ProfileRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Altinn.Profile.Integrations/Repositories/ProfileRepository.cs b/src/Altinn.Profile.Integrations/Repositories/ProfileRepository.cs index bf4308a..dee31f3 100644 --- a/src/Altinn.Profile.Integrations/Repositories/ProfileRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/ProfileRepository.cs @@ -88,7 +88,7 @@ public Task> GetAllAsync() /// Number of entities to skip for pagination. /// Number of entities to take for pagination. /// A task that represents the asynchronous operation. The task result contains a collection of entities matching the criteria. - public Task> GetAsync(Func? filter, Func, IOrderedEnumerable>? orderBy, int? skip, int? take) + public Task> GetAsync(Func? filter = null, Func, IOrderedEnumerable>? orderBy = null, int? skip = null, int? take = null) { throw new NotImplementedException(); } From 6b3014f1669c1cd49aee977765bace8e27c8ebf6 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 15:39:01 +0200 Subject: [PATCH 43/98] Remove the nameof parameter --- src/Altinn.Profile.Integrations/Services/RegisterService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs index 7d729e4..130fa02 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -59,7 +59,7 @@ public RegisterService(IMapper mapper, IPersonRepository registerRepository, INa /// Thrown if is null. public async Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers) { - ArgumentNullException.ThrowIfNull(nationalIdentityNumbers, nameof(nationalIdentityNumbers)); + ArgumentNullException.ThrowIfNull(nationalIdentityNumbers); var (validnNtionalIdentityNumbers, invalidNationalIdentityNumbers) = _nationalIdentityNumberChecker.Categorize(nationalIdentityNumbers); From 69039986053a39328c5528e271aa9f254c832a6b Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 15:40:09 +0200 Subject: [PATCH 44/98] Remove an unnecessary check for null --- src/Altinn.Profile.Integrations/Services/RegisterService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/RegisterService.cs index 130fa02..71f9405 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/RegisterService.cs @@ -61,7 +61,7 @@ public async Task> GetUserContactAsyn { ArgumentNullException.ThrowIfNull(nationalIdentityNumbers); - var (validnNtionalIdentityNumbers, invalidNationalIdentityNumbers) = _nationalIdentityNumberChecker.Categorize(nationalIdentityNumbers); + var (validnNtionalIdentityNumbers, _) = _nationalIdentityNumberChecker.Categorize(nationalIdentityNumbers); var usersContactInfo = await _registerRepository.GetUserContactInfoAsync(validnNtionalIdentityNumbers); @@ -72,7 +72,7 @@ public async Task> GetUserContactAsyn return new UserContactInfoLookupResult { - MatchedUserContact = matchedUserContact?.ToImmutableList(), + MatchedUserContact = matchedUserContact.ToImmutableList(), UnmatchedNationalIdentityNumbers = unmatchedNationalIdentityNumbers.ToImmutableList() }; } From 2d755a4dc5e34fc9b1d08fedf06d70d377c4404b Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 15:42:59 +0200 Subject: [PATCH 45/98] Rename the service using the table name --- .../ServiceCollectionExtensions.cs | 2 +- .../Services/{IRegisterService.cs => IPersonService.cs} | 2 +- .../Services/{RegisterService.cs => PersonService.cs} | 6 +++--- src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) rename src/Altinn.Profile.Integrations/Services/{IRegisterService.cs => IPersonService.cs} (97%) rename src/Altinn.Profile.Integrations/Services/{RegisterService.cs => PersonService.cs} (93%) diff --git a/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs b/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs index 6895669..7acbfb4 100644 --- a/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs +++ b/src/Altinn.Profile.Integrations/ServiceCollectionExtensions.cs @@ -58,7 +58,7 @@ public static void AddRegisterService(this IServiceCollection services, IConfigu services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddSingleton(); diff --git a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs b/src/Altinn.Profile.Integrations/Services/IPersonService.cs similarity index 97% rename from src/Altinn.Profile.Integrations/Services/IRegisterService.cs rename to src/Altinn.Profile.Integrations/Services/IPersonService.cs index 57c971c..e09b42b 100644 --- a/src/Altinn.Profile.Integrations/Services/IRegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/IPersonService.cs @@ -8,7 +8,7 @@ namespace Altinn.Profile.Integrations.Services; /// /// Defines a service for handling operations related to user contact information. /// -public interface IRegisterService +public interface IPersonService { /// /// Asynchronously retrieves the contact information for a user based on their national identity number. diff --git a/src/Altinn.Profile.Integrations/Services/RegisterService.cs b/src/Altinn.Profile.Integrations/Services/PersonService.cs similarity index 93% rename from src/Altinn.Profile.Integrations/Services/RegisterService.cs rename to src/Altinn.Profile.Integrations/Services/PersonService.cs index 71f9405..1c4e9fb 100644 --- a/src/Altinn.Profile.Integrations/Services/RegisterService.cs +++ b/src/Altinn.Profile.Integrations/Services/PersonService.cs @@ -11,20 +11,20 @@ namespace Altinn.Profile.Integrations.Services; /// /// Service for handling operations related to user registration and contact points. /// -public class RegisterService : IRegisterService +public class PersonService : IPersonService { private readonly IMapper _mapper; private readonly IPersonRepository _registerRepository; private readonly INationalIdentityNumberChecker _nationalIdentityNumberChecker; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The mapper used for object mapping. /// The repository used for accessing register data. /// The service used for checking the validity of national identity numbers. /// Thrown if , , or is null. - public RegisterService(IMapper mapper, IPersonRepository registerRepository, INationalIdentityNumberChecker nationalIdentityNumberChecker) + public PersonService(IMapper mapper, IPersonRepository registerRepository, INationalIdentityNumberChecker nationalIdentityNumberChecker) { _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); _registerRepository = registerRepository ?? throw new ArgumentNullException(nameof(registerRepository)); diff --git a/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs b/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs index fd02b92..0cf2589 100644 --- a/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs +++ b/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs @@ -15,14 +15,14 @@ namespace Altinn.Profile.UseCases; /// public class UserContactDetailsRetriever : IUserContactDetailsRetriever { - private readonly IRegisterService _registerService; + private readonly IPersonService _registerService; /// /// Initializes a new instance of the class. /// /// The register service for retrieving user contact details. /// Thrown when is null. - public UserContactDetailsRetriever(IRegisterService registerService) + public UserContactDetailsRetriever(IPersonService registerService) { _registerService = registerService ?? throw new ArgumentNullException(nameof(registerService)); } From 3aa73a63f0b66b2853cee158fc4c4f52a0cd4f81 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 15:45:02 +0200 Subject: [PATCH 46/98] Improve code smell --- .../Profile.Integrations/Register/RegisterRepositoryTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs index ba111f1..457d103 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs @@ -49,7 +49,7 @@ public void Dispose() public async Task GetUserContactInfoAsync_ReturnsContactInfo_WhenFound() { // Act - var result = await _registerRepository.GetUserContactInfoAsync(new[] { "17111933790" }); + var result = await _registerRepository.GetUserContactInfoAsync(["17111933790"]); var actual = result.FirstOrDefault(e => e.FnumberAk == "17111933790"); var expected = _personContactAndReservationTestData.FirstOrDefault(e => e.FnumberAk == "17111933790"); @@ -89,7 +89,7 @@ public async Task GetUserContactInfoAsync_ReturnsMultipleContacts_WhenFound() var expected = _personContactAndReservationTestData.Where(e => e.FnumberAk == "24064316776" || e.FnumberAk == "11044314101"); // Assert - Assert.Equal(2, result.Count()); + Assert.Equal(2, result.Count); foreach (var register in result) { From 3723e74ddf258f64af53127980d344f41126dcef Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 7 Oct 2024 16:01:24 +0200 Subject: [PATCH 47/98] Code refactoring --- .../UseCases/UserContactDetailsRetriever.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs b/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs index 0cf2589..1b97c48 100644 --- a/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs +++ b/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs @@ -35,8 +35,11 @@ public UserContactDetailsRetriever(IPersonService registerService) /// A task representing the asynchronous operation. The task result contains a /// where the value is and the error is . /// + /// Thrown when is null. public async Task> RetrieveAsync(UserContactPointLookup lookupCriteria) { + ArgumentNullException.ThrowIfNull(lookupCriteria); + if (lookupCriteria?.NationalIdentityNumbers == null || lookupCriteria.NationalIdentityNumbers.Count == 0) { return false; @@ -45,7 +48,7 @@ public async Task> RetrieveAsync(Us var userContactDetails = await _registerService.GetUserContactAsync(lookupCriteria.NationalIdentityNumbers); return userContactDetails.Match( - MapToUserContactDetailsResult, + MapToUserContactDetailsLookupResult, _ => false); } @@ -74,9 +77,12 @@ private UserContactDetails MapToUserContactDetails(IUserContactInfo userContactD /// /// The user contact details lookup result. /// A containing the mapped user contact details. - private Result MapToUserContactDetailsResult(IUserContactInfoLookupResult userContactResult) + /// Thrown when is null. + private Result MapToUserContactDetailsLookupResult(IUserContactInfoLookupResult userContactResult) { - var unmatchedNationalIdentityNumbers = userContactResult?.UnmatchedNationalIdentityNumbers ?? null; + ArgumentNullException.ThrowIfNull(userContactResult); + + var unmatchedNationalIdentityNumbers = userContactResult?.UnmatchedNationalIdentityNumbers; var matchedUserContactDetails = userContactResult?.MatchedUserContact?.Select(MapToUserContactDetails).ToImmutableList(); return new UserContactDetailsLookupResult(matchedUserContactDetails, unmatchedNationalIdentityNumbers); From 643d984767ae12ebb71bf8e9dc30881afc880a6b Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 07:33:05 +0200 Subject: [PATCH 48/98] Implement two unit tests to validate the functionality of the user contact details retrieval use case. --- .../UserContactDetailsRetrieverTests.cs | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 test/Altinn.Profile.Tests/Profile.Integrations/UserContactDetailsRetrieverTests.cs diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/UserContactDetailsRetrieverTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/UserContactDetailsRetrieverTests.cs new file mode 100644 index 0000000..218e116 --- /dev/null +++ b/test/Altinn.Profile.Tests/Profile.Integrations/UserContactDetailsRetrieverTests.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; + +using Altinn.Profile.Core; +using Altinn.Profile.Integrations.Entities; +using Altinn.Profile.Integrations.Services; +using Altinn.Profile.Models; +using Altinn.Profile.UseCases; + +using Moq; + +using Xunit; + +namespace Altinn.Profile.Tests.Profile.Integrations; + +public class UserContactDetailsRetrieverTests +{ + private readonly Mock _mockRegisterService; + private readonly UserContactDetailsRetriever _retriever; + + public UserContactDetailsRetrieverTests() + { + _mockRegisterService = new Mock(); + _retriever = new UserContactDetailsRetriever(_mockRegisterService.Object); + } + + [Fact] + public async Task RetrieveAsync_ThrowsArgumentNullException_WhenLookupCriteriaIsNull() + { + // Act & Assert + await Assert.ThrowsAsync(() => _retriever.RetrieveAsync(null)); + } + + [Fact] + public async Task RetrieveAsync_ReturnsFalse_WhenNationalIdentityNumbersIsEmpty() + { + // Arrange + var lookupCriteria = new UserContactPointLookup { NationalIdentityNumbers = [] }; + + // Act + var result = await _retriever.RetrieveAsync(lookupCriteria); + + // Assert + Assert.False(result.IsSuccess); + } + + [Fact] + public async Task RetrieveAsync_ReturnsFalse_WhenNoContactDetailsFound() + { + // Arrange + var lookupCriteria = new UserContactPointLookup + { + NationalIdentityNumbers = new List { "08119043698" } + }; + + _mockRegisterService.Setup(s => s.GetUserContactAsync(lookupCriteria.NationalIdentityNumbers)).ReturnsAsync(false); + + // Act + var result = await _retriever.RetrieveAsync(lookupCriteria); + + // Assert + Assert.False(result.IsSuccess); + } +} From b14fe81dcd83ff96f43f6709f49fc20c5d002c95 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 08:13:21 +0200 Subject: [PATCH 49/98] Create a new Migration folder and attach the database creation script --- .../Migration/profiledb.sql | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/Altinn.Profile.Integrations/Migration/profiledb.sql diff --git a/src/Altinn.Profile.Integrations/Migration/profiledb.sql b/src/Altinn.Profile.Integrations/Migration/profiledb.sql new file mode 100644 index 0000000..b94539a --- /dev/null +++ b/src/Altinn.Profile.Integrations/Migration/profiledb.sql @@ -0,0 +1,54 @@ +-- Drop the database if it exists +DROP DATABASE IF EXISTS profiledb; + +-- Create the database +CREATE DATABASE profiledb + WITH + OWNER = postgres + ENCODING = 'UTF8' + LC_COLLATE = 'Norwegian_Norway.1252' + LC_CTYPE = 'Norwegian_Norway.1252' + LOCALE_PROVIDER = 'libc' + TABLESPACE = pg_default + CONNECTION LIMIT = -1 + IS_TEMPLATE = False; + +-- Create schema if it doesn't exist +CREATE SCHEMA IF NOT EXISTS contact_and_reservation; + +-- Create table MailboxSupplier +CREATE TABLE IF NOT EXISTS contact_and_reservation.mailbox_supplier ( + mailbox_supplier_id INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY, + org_number_ak CHAR(9) NOT NULL, + CONSTRAINT unique_org_number_ak UNIQUE (org_number_ak) +); + +-- Create table Metadata +CREATE TABLE IF NOT EXISTS contact_and_reservation.metadata ( + latest_change_number BIGINT PRIMARY KEY, + exported TIMESTAMPTZ +); + +-- Create table Person +CREATE TABLE IF NOT EXISTS contact_and_reservation.person ( + contact_and_reservation_user_id INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY, + fnumber_ak CHAR(11) NOT NULL UNIQUE, + reservation BOOLEAN, + description VARCHAR(20), + mobile_phone_number VARCHAR(20), + mobile_phone_number_last_updated TIMESTAMPTZ, + mobile_phone_number_last_verified TIMESTAMPTZ, + email_address VARCHAR(400), + email_address_last_updated TIMESTAMPTZ, + email_address_last_verified TIMESTAMPTZ, + mailbox_address VARCHAR(50), + mailbox_supplier_id_fk INT, + x509_certificate TEXT, + language_code CHAR(2) NULL, + CONSTRAINT fk_mailbox_supplier FOREIGN KEY (mailbox_supplier_id_fk) REFERENCES contact_and_reservation.mailbox_supplier (mailbox_supplier_id), + CONSTRAINT chk_language_code CHECK (language_code ~* '^[a-z]{2}$') +); + +-- Indexes for performance +CREATE INDEX idx_mailbox_supplier_id_fk ON contact_and_reservation.person (mailbox_supplier_id_fk); +CREATE INDEX idx_fnumber_ak ON contact_and_reservation.person (fnumber_ak); From 59cca9943c7f7af37ec5748f751d8fff536cf3d8 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 08:53:59 +0200 Subject: [PATCH 50/98] Regenerate the data models --- .../Models/DbContext/ProfiledbContext.cs | 112 ++++++++--------- src/Altinn.Profile/Models/MailboxSupplier.cs | 52 ++++---- src/Altinn.Profile/Models/Metadata.cs | 34 ++--- src/Altinn.Profile/Models/Person.cs | 118 ++++++++++++++++++ src/Altinn.Profile/Models/Register.cs | 117 ----------------- src/Altinn.Profile/efpt.config.json | 104 +++++++-------- 6 files changed, 270 insertions(+), 267 deletions(-) create mode 100644 src/Altinn.Profile/Models/Person.cs delete mode 100644 src/Altinn.Profile/Models/Register.cs diff --git a/src/Altinn.Profile/Models/DbContext/ProfiledbContext.cs b/src/Altinn.Profile/Models/DbContext/ProfiledbContext.cs index 7408e96..657e01d 100644 --- a/src/Altinn.Profile/Models/DbContext/ProfiledbContext.cs +++ b/src/Altinn.Profile/Models/DbContext/ProfiledbContext.cs @@ -1,79 +1,77 @@ // This file has been auto generated by EF Core Power Tools. #nullable disable + using Microsoft.EntityFrameworkCore; -namespace Altinn.Profile.Models +namespace Altinn.Profile.Models; + +/// +/// Represents the database context for the profile database. +/// +public partial class ProfileDbContext : DbContext { /// - /// Represents the database context for the profile database. + /// Initializes a new instance of the class. /// - public partial class ProfileDbContext : DbContext + /// The options to be used by a . + public ProfileDbContext(DbContextOptions options) + : base(options) { - /// - /// Initializes a new instance of the class. - /// - /// The options to be used by a . - public ProfileDbContext(DbContextOptions options) - : base(options) - { - } + } - /// - /// Gets or sets the representing the mailbox suppliers. - /// - public virtual DbSet MailboxSuppliers { get; set; } + /// + /// Gets or sets the representing the mailbox suppliers. + /// + public virtual DbSet MailboxSuppliers { get; set; } - /// - /// Gets or sets the representing the metadata. - /// - public virtual DbSet Metadata { get; set; } + /// + /// Gets or sets the representing the metadata. + /// + public virtual DbSet Metadata { get; set; } - /// - /// Gets or sets the representing the registers. - /// - public virtual DbSet Registers { get; set; } + /// + /// Gets or sets the representing the people. + /// + public virtual DbSet People { get; set; } - /// - /// Configures the schema needed for the context. - /// - /// The builder being used to construct the model for this context. - protected override void OnModelCreating(ModelBuilder modelBuilder) + /// + /// Configures the schema needed for the context. + /// + /// The builder being used to construct the model for this context. + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => { - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.MailboxSupplierId).HasName("mailbox_supplier_pkey"); + entity.HasKey(e => e.MailboxSupplierId).HasName("mailbox_supplier_pkey"); - entity.Property(e => e.MailboxSupplierId).UseIdentityAlwaysColumn(); - entity.Property(e => e.OrgNumberAk).IsFixedLength(); - }); + entity.Property(e => e.MailboxSupplierId).UseIdentityAlwaysColumn(); + entity.Property(e => e.OrgNumberAk).IsFixedLength(); + }); - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.LatestChangeNumber).HasName("metadata_pkey"); - - entity.Property(e => e.LatestChangeNumber).ValueGeneratedNever(); - }); + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.LatestChangeNumber).HasName("metadata_pkey"); - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.ContactAndReservationUserId).HasName("register_pkey"); + entity.Property(e => e.LatestChangeNumber).ValueGeneratedNever(); + }); - entity.Property(e => e.ContactAndReservationUserId).UseIdentityAlwaysColumn(); - entity.Property(e => e.FnumberAk).IsFixedLength(); - entity.Property(e => e.LanguageCode).IsFixedLength(); + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.ContactAndReservationUserId).HasName("person_pkey"); - entity.HasOne(d => d.MailboxSupplierIdFkNavigation) - .WithMany(p => p.Registers) - .HasConstraintName("fk_mailbox_supplier"); - }); + entity.Property(e => e.ContactAndReservationUserId).UseIdentityAlwaysColumn(); + entity.Property(e => e.FnumberAk).IsFixedLength(); + entity.Property(e => e.LanguageCode).IsFixedLength(); - OnModelCreatingPartial(modelBuilder); - } + entity.HasOne(d => d.MailboxSupplierIdFkNavigation).WithMany(p => p.People).HasConstraintName("fk_mailbox_supplier"); + }); - /// - /// A partial method that can be used to configure the model further. - /// - /// The builder being used to construct the model for this context. - partial void OnModelCreatingPartial(ModelBuilder modelBuilder); + OnModelCreatingPartial(modelBuilder); } + + /// + /// A partial method that can be used to configure the model further. + /// + /// The builder being used to construct the model for this context. + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); } diff --git a/src/Altinn.Profile/Models/MailboxSupplier.cs b/src/Altinn.Profile/Models/MailboxSupplier.cs index b5db3eb..9a9230c 100644 --- a/src/Altinn.Profile/Models/MailboxSupplier.cs +++ b/src/Altinn.Profile/Models/MailboxSupplier.cs @@ -1,36 +1,40 @@ // This file has been auto generated by EF Core Power Tools. #nullable disable + +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Altinn.Profile.Models +using Microsoft.EntityFrameworkCore; + +namespace Altinn.Profile.Models; + +/// +/// Represents a mailbox supplier in the contact and reservation schema. +/// +[Table("mailbox_supplier", Schema = "contact_and_reservation")] +[Index("OrgNumberAk", Name = "unique_org_number_ak", IsUnique = true)] +public partial class MailboxSupplier { /// - /// Represents a mailbox supplier entity. + /// Gets or sets the unique identifier for the mailbox supplier. /// - [Table("mailbox_supplier", Schema = "contact_and_reservation")] - public partial class MailboxSupplier - { - /// - /// Gets or sets the unique identifier for the mailbox supplier. - /// - [Key] - [Column("mailbox_supplier_id")] - public int MailboxSupplierId { get; set; } + [Key] + [Column("mailbox_supplier_id")] + public int MailboxSupplierId { get; set; } - /// - /// Gets or sets the organization number. - /// - [Required] - [Column("org_number_ak")] - [StringLength(9)] - public string OrgNumberAk { get; set; } + /// + /// Gets or sets the organization number of the mailbox supplier. + /// + [Required] + [Column("org_number_ak")] + [StringLength(9)] + public string OrgNumberAk { get; set; } - /// - /// Gets or sets the collection of registers associated with the mailbox supplier. - /// - [InverseProperty("MailboxSupplierIdFkNavigation")] - public virtual ICollection Registers { get; set; } = new List(); - } + /// + /// Gets or sets the collection of people associated with the mailbox supplier. + /// + [InverseProperty("MailboxSupplierIdFkNavigation")] + public virtual ICollection People { get; set; } = new List(); } diff --git a/src/Altinn.Profile/Models/Metadata.cs b/src/Altinn.Profile/Models/Metadata.cs index 4c295a7..aa15bc4 100644 --- a/src/Altinn.Profile/Models/Metadata.cs +++ b/src/Altinn.Profile/Models/Metadata.cs @@ -1,28 +1,28 @@ // This file has been auto generated by EF Core Power Tools. #nullable disable + using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Altinn.Profile.Models +namespace Altinn.Profile.Models; + +/// +/// Represents metadata in the contact and reservation schema. +/// +[Table("metadata", Schema = "contact_and_reservation")] +public partial class Metadata { /// - /// Represents metadata information. + /// Gets or sets the latest change number. /// - [Table("metadata", Schema = "contact_and_reservation")] - public partial class Metadata - { - /// - /// Gets or sets the latest change number. - /// - [Key] - [Column("latest_change_number")] - public long LatestChangeNumber { get; set; } + [Key] + [Column("latest_change_number")] + public long LatestChangeNumber { get; set; } - /// - /// Gets or sets the date and time when the metadata was exported. - /// - [Column("exported")] - public DateTime? Exported { get; set; } - } + /// + /// Gets or sets the date and time when the metadata was exported. + /// + [Column("exported")] + public DateTime? Exported { get; set; } } diff --git a/src/Altinn.Profile/Models/Person.cs b/src/Altinn.Profile/Models/Person.cs new file mode 100644 index 0000000..ccdb48f --- /dev/null +++ b/src/Altinn.Profile/Models/Person.cs @@ -0,0 +1,118 @@ +// This file has been auto generated by EF Core Power Tools. +#nullable disable + +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +using Microsoft.EntityFrameworkCore; + +namespace Altinn.Profile.Models; +/// +/// Represents a person in the contact and reservation schema. +/// +[Table("person", Schema = "contact_and_reservation")] +[Index("FnumberAk", Name = "idx_fnumber_ak")] +[Index("MailboxSupplierIdFk", Name = "idx_mailbox_supplier_id_fk")] +[Index("FnumberAk", Name = "person_fnumber_ak_key", IsUnique = true)] +public partial class Person +{ + /// + /// Gets or sets the unique identifier for the contact and reservation user. + /// + [Key] + [Column("contact_and_reservation_user_id")] + public int ContactAndReservationUserId { get; set; } + + /// + /// Gets or sets the F-number (a unique identifier) of the person. + /// + [Required] + [Column("fnumber_ak")] + [StringLength(11)] + public string FnumberAk { get; set; } + + /// + /// Gets or sets a value indicating whether the person has a reservation. + /// + [Column("reservation")] + public bool? Reservation { get; set; } + + /// + /// Gets or sets the description of the person. + /// + [Column("description")] + [StringLength(20)] + public string Description { get; set; } + + /// + /// Gets or sets the mobile phone number of the person. + /// + [Column("mobile_phone_number")] + [StringLength(20)] + public string MobilePhoneNumber { get; set; } + + /// + /// Gets or sets the date and time when the mobile phone number was last updated. + /// + [Column("mobile_phone_number_last_updated")] + public DateTime? MobilePhoneNumberLastUpdated { get; set; } + + /// + /// Gets or sets the date and time when the mobile phone number was last verified. + /// + [Column("mobile_phone_number_last_verified")] + public DateTime? MobilePhoneNumberLastVerified { get; set; } + + /// + /// Gets or sets the email address of the person. + /// + [Column("email_address")] + [StringLength(400)] + public string EmailAddress { get; set; } + + /// + /// Gets or sets the date and time when the email address was last updated. + /// + [Column("email_address_last_updated")] + public DateTime? EmailAddressLastUpdated { get; set; } + + /// + /// Gets or sets the date and time when the email address was last verified. + /// + [Column("email_address_last_verified")] + public DateTime? EmailAddressLastVerified { get; set; } + + /// + /// Gets or sets the mailbox address of the person. + /// + [Column("mailbox_address")] + [StringLength(50)] + public string MailboxAddress { get; set; } + + /// + /// Gets or sets the foreign key to the mailbox supplier. + /// + [Column("mailbox_supplier_id_fk")] + public int? MailboxSupplierIdFk { get; set; } + + /// + /// Gets or sets the X.509 certificate of the person. + /// + [Column("x509_certificate")] + public string X509Certificate { get; set; } + + /// + /// Gets or sets the language code of the person. + /// + [Column("language_code")] + [StringLength(2)] + public string LanguageCode { get; set; } + + /// + /// Gets or sets the navigation property to the mailbox supplier. + /// + [ForeignKey("MailboxSupplierIdFk")] + [InverseProperty("People")] + public virtual MailboxSupplier MailboxSupplierIdFkNavigation { get; set; } +} diff --git a/src/Altinn.Profile/Models/Register.cs b/src/Altinn.Profile/Models/Register.cs deleted file mode 100644 index 979e998..0000000 --- a/src/Altinn.Profile/Models/Register.cs +++ /dev/null @@ -1,117 +0,0 @@ -// This file has been auto generated by EF Core Power Tools. -#nullable disable -using System; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -using Microsoft.EntityFrameworkCore; - -namespace Altinn.Profile.Models -{ - /// - /// Represents a register entity. - /// - [Table("register", Schema = "contact_and_reservation")] - [Index("FnumberAk", Name = "register_fnumber_ak_key", IsUnique = true)] - public partial class Register - { - /// - /// Gets or sets the unique identifier for the contact and reservation user. - /// - [Key] - [Column("contact_and_reservation_user_id")] - public int ContactAndReservationUserId { get; set; } - - /// - /// Gets or sets the F-number. - /// - [Required] - [Column("fnumber_ak")] - [StringLength(11)] - public string FnumberAk { get; set; } - - /// - /// Gets or sets the reservation status. - /// - [Column("reservation")] - public bool? Reservation { get; set; } - - /// - /// Gets or sets the description. - /// - [Column("description")] - [StringLength(20)] - public string Description { get; set; } - - /// - /// Gets or sets the mobile phone number. - /// - [Column("mobile_phone_number")] - [StringLength(20)] - public string MobilePhoneNumber { get; set; } - - /// - /// Gets or sets the date and time when the mobile phone number was last updated. - /// - [Column("mobile_phone_number_last_updated")] - public DateTime? MobilePhoneNumberLastUpdated { get; set; } - - /// - /// Gets or sets the date and time when the mobile phone number was last verified. - /// - [Column("mobile_phone_number_last_verified")] - public DateTime? MobilePhoneNumberLastVerified { get; set; } - - /// - /// Gets or sets the email address. - /// - [Column("email_address")] - [StringLength(400)] - public string EmailAddress { get; set; } - - /// - /// Gets or sets the date and time when the email address was last updated. - /// - [Column("email_address_last_updated")] - public DateTime? EmailAddressLastUpdated { get; set; } - - /// - /// Gets or sets the date and time when the email address was last verified. - /// - [Column("email_address_last_verified")] - public DateTime? EmailAddressLastVerified { get; set; } - - /// - /// Gets or sets the mailbox address. - /// - [Column("mailbox_address")] - [StringLength(50)] - public string MailboxAddress { get; set; } - - /// - /// Gets or sets the foreign key for the mailbox supplier. - /// - [Column("mailbox_supplier_id_fk")] - public int? MailboxSupplierIdFk { get; set; } - - /// - /// Gets or sets the X.509 certificate. - /// - [Column("x509_certificate")] - public string X509Certificate { get; set; } - - /// - /// Gets or sets the language code. - /// - [Column("language_code")] - [StringLength(2)] - public string LanguageCode { get; set; } - - /// - /// Gets or sets the navigation property for the associated mailbox supplier. - /// - [ForeignKey("MailboxSupplierIdFk")] - [InverseProperty("Registers")] - public virtual MailboxSupplier MailboxSupplierIdFkNavigation { get; set; } - } -} diff --git a/src/Altinn.Profile/efpt.config.json b/src/Altinn.Profile/efpt.config.json index 93e19b2..ac5afc4 100644 --- a/src/Altinn.Profile/efpt.config.json +++ b/src/Altinn.Profile/efpt.config.json @@ -1,54 +1,54 @@ { - "CodeGenerationMode": 4, - "ContextClassName": "ProfileDbContext", - "ContextNamespace": null, - "FilterSchemas": false, - "IncludeConnectionString": false, - "ModelNamespace": null, - "OutputContextPath": null, - "OutputPath": "Models", - "PreserveCasingWithRegex": true, - "ProjectRootNamespace": "Altinn.Profile", - "Schemas": null, - "SelectedHandlebarsLanguage": 2, - "SelectedToBeGenerated": 0, - "T4TemplatePath": null, - "Tables": [ - { - "Name": "contact_and_reservation.mailbox_supplier", - "ObjectType": 0 - }, - { - "Name": "contact_and_reservation.metadata", - "ObjectType": 0 - }, - { - "Name": "contact_and_reservation.register", - "ObjectType": 0 - } - ], - "UiHint": null, - "UncountableWords": null, - "UseAsyncStoredProcedureCalls": true, - "UseBoolPropertiesWithoutDefaultSql": false, - "UseDatabaseNames": false, - "UseDateOnlyTimeOnly": true, - "UseDbContextSplitting": false, - "UseDecimalDataAnnotationForSprocResult": true, - "UseFluentApiOnly": false, - "UseHandleBars": false, - "UseHierarchyId": false, - "UseInflector": true, - "UseLegacyPluralizer": false, - "UseManyToManyEntity": false, - "UseNoDefaultConstructor": false, - "UseNoNavigations": false, - "UseNoObjectFilter": false, - "UseNodaTime": false, - "UseNullableReferences": false, - "UsePrefixNavigationNaming": false, - "UseSchemaFolders": false, - "UseSchemaNamespaces": false, - "UseSpatial": false, - "UseT4": false + "CodeGenerationMode": 4, + "ContextClassName": "ProfileDbContext", + "ContextNamespace": null, + "FilterSchemas": false, + "IncludeConnectionString": false, + "ModelNamespace": null, + "OutputContextPath": null, + "OutputPath": "Models", + "PreserveCasingWithRegex": true, + "ProjectRootNamespace": "Altinn.Profile", + "Schemas": null, + "SelectedHandlebarsLanguage": 2, + "SelectedToBeGenerated": 0, + "T4TemplatePath": null, + "Tables": [ + { + "Name": "contact_and_reservation.mailbox_supplier", + "ObjectType": 0 + }, + { + "Name": "contact_and_reservation.metadata", + "ObjectType": 0 + }, + { + "Name": "contact_and_reservation.person", + "ObjectType": 0 + } + ], + "UiHint": null, + "UncountableWords": null, + "UseAsyncStoredProcedureCalls": true, + "UseBoolPropertiesWithoutDefaultSql": false, + "UseDatabaseNames": false, + "UseDateOnlyTimeOnly": true, + "UseDbContextSplitting": false, + "UseDecimalDataAnnotationForSprocResult": true, + "UseFluentApiOnly": false, + "UseHandleBars": false, + "UseHierarchyId": false, + "UseInflector": true, + "UseLegacyPluralizer": false, + "UseManyToManyEntity": false, + "UseNoDefaultConstructor": false, + "UseNoNavigations": false, + "UseNoObjectFilter": false, + "UseNodaTime": false, + "UseNullableReferences": false, + "UsePrefixNavigationNaming": false, + "UseSchemaFolders": false, + "UseSchemaNamespaces": false, + "UseSpatial": false, + "UseT4": false } \ No newline at end of file From e4c7c3e8610e020a7fd245f9a3146aa7e49ffd30 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 10:03:46 +0200 Subject: [PATCH 51/98] Move the Person class to the entities folder --- .../Models => Altinn.Profile.Integrations/Entities}/Person.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{Altinn.Profile/Models => Altinn.Profile.Integrations/Entities}/Person.cs (100%) diff --git a/src/Altinn.Profile/Models/Person.cs b/src/Altinn.Profile.Integrations/Entities/Person.cs similarity index 100% rename from src/Altinn.Profile/Models/Person.cs rename to src/Altinn.Profile.Integrations/Entities/Person.cs From e64c1898c8ff61c85707797ad1ff4cbe2fd03903 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 10:34:38 +0200 Subject: [PATCH 52/98] Change the table name from Register to Person --- .../Entities/MailboxSupplier.cs | 11 +- .../Entities/Metadata.cs | 2 +- .../Entities/Person.cs | 3 +- .../Entities/Register.cs | 116 ------------------ ...e.cs => PersonToUserContactInfoProfile.cs} | 8 +- .../Persistence/ProfiledbContext.cs | 10 +- .../Repositories/IPersonRepository.cs | 4 +- .../Repositories/PersonRepository.cs | 6 +- src/Altinn.Profile/efpt.config.json | 54 -------- .../Register/RegisterRepositoryTests.cs | 6 +- .../Testdata/PersonTestData.cs | 2 +- 11 files changed, 27 insertions(+), 195 deletions(-) delete mode 100644 src/Altinn.Profile.Integrations/Entities/Register.cs rename src/Altinn.Profile.Integrations/Mappings/{RegisterToUserContactInfoProfile.cs => PersonToUserContactInfoProfile.cs} (79%) delete mode 100644 src/Altinn.Profile/efpt.config.json diff --git a/src/Altinn.Profile.Integrations/Entities/MailboxSupplier.cs b/src/Altinn.Profile.Integrations/Entities/MailboxSupplier.cs index a19a87e..600df3d 100644 --- a/src/Altinn.Profile.Integrations/Entities/MailboxSupplier.cs +++ b/src/Altinn.Profile.Integrations/Entities/MailboxSupplier.cs @@ -4,12 +4,15 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + namespace Altinn.Profile.Integrations.Entities; /// -/// Represents a mailbox supplier entity. +/// Represents a mailbox supplier in the contact and reservation schema. /// [Table("mailbox_supplier", Schema = "contact_and_reservation")] +[Index("OrgNumberAk", Name = "unique_org_number_ak", IsUnique = true)] public partial class MailboxSupplier { /// @@ -20,7 +23,7 @@ public partial class MailboxSupplier public int MailboxSupplierId { get; set; } /// - /// Gets or sets the organization number. + /// Gets or sets the organization number of the mailbox supplier. /// [Required] [Column("org_number_ak")] @@ -28,8 +31,8 @@ public partial class MailboxSupplier public string OrgNumberAk { get; set; } /// - /// Gets or sets the collection of registers associated with the mailbox supplier. + /// Gets or sets the collection of people associated with the mailbox supplier. /// [InverseProperty("MailboxSupplierIdFkNavigation")] - public virtual ICollection Registers { get; set; } = new List(); + public virtual ICollection People { get; set; } = new List(); } diff --git a/src/Altinn.Profile.Integrations/Entities/Metadata.cs b/src/Altinn.Profile.Integrations/Entities/Metadata.cs index 039641d..c21809a 100644 --- a/src/Altinn.Profile.Integrations/Entities/Metadata.cs +++ b/src/Altinn.Profile.Integrations/Entities/Metadata.cs @@ -7,7 +7,7 @@ namespace Altinn.Profile.Integrations.Entities; /// -/// Represents a metadata entity. +/// Represents metadata in the contact and reservation schema. /// [Table("metadata", Schema = "contact_and_reservation")] public partial class Metadata diff --git a/src/Altinn.Profile.Integrations/Entities/Person.cs b/src/Altinn.Profile.Integrations/Entities/Person.cs index ccdb48f..fe87840 100644 --- a/src/Altinn.Profile.Integrations/Entities/Person.cs +++ b/src/Altinn.Profile.Integrations/Entities/Person.cs @@ -1,13 +1,12 @@ // This file has been auto generated by EF Core Power Tools. #nullable disable -using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; -namespace Altinn.Profile.Models; +namespace Altinn.Profile.Integrations.Entities; /// /// Represents a person in the contact and reservation schema. /// diff --git a/src/Altinn.Profile.Integrations/Entities/Register.cs b/src/Altinn.Profile.Integrations/Entities/Register.cs deleted file mode 100644 index f90905c..0000000 --- a/src/Altinn.Profile.Integrations/Entities/Register.cs +++ /dev/null @@ -1,116 +0,0 @@ -// This file has been auto generated by EF Core Power Tools. -#nullable disable - -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -using Microsoft.EntityFrameworkCore; - -namespace Altinn.Profile.Integrations.Entities; - -/// -/// Represents a register entity. -/// -[Table("register", Schema = "contact_and_reservation")] -[Index("FnumberAk", Name = "register_fnumber_ak_key", IsUnique = true)] -public partial class Register -{ - /// - /// Gets or sets the unique identifier for the user that holds contact and reservation information. - /// - [Key] - [Column("contact_and_reservation_user_id")] - public int ContactAndReservationUserId { get; set; } - - /// - /// Gets or sets the national identity number. - /// - [Required] - [Column("fnumber_ak")] - [StringLength(11)] - public string FnumberAk { get; set; } - - /// - /// Gets or sets the reservation status. - /// - [Column("reservation")] - public bool Reservation { get; set; } - - /// - /// Gets or sets the description. - /// - [Column("description")] - [StringLength(20)] - public string Description { get; set; } - - /// - /// Gets or sets the mobile phone number. - /// - [Column("mobile_phone_number")] - [StringLength(20)] - public string MobilePhoneNumber { get; set; } - - /// - /// Gets or sets the date and time in which the mobile phone number was last updated. - /// - [Column("mobile_phone_number_last_updated")] - public DateTime? MobilePhoneNumberLastUpdated { get; set; } - - /// - /// Gets or sets the date and time in which the mobile phone number was last verified. - /// - [Column("mobile_phone_number_last_verified")] - public DateTime? MobilePhoneNumberLastVerified { get; set; } - - /// - /// Gets or sets the email address. - /// - [Column("email_address")] - [StringLength(400)] - public string EmailAddress { get; set; } - - /// - /// Gets or sets the date and time in which the email address was last updated. - /// - [Column("email_address_last_updated")] - public DateTime? EmailAddressLastUpdated { get; set; } - - /// - /// Gets or sets the date and time in which the email address was last verified. - /// - [Column("email_address_last_verified")] - public DateTime? EmailAddressLastVerified { get; set; } - - /// - /// Gets or sets the mailbox address. - /// - [Column("mailbox_address")] - [StringLength(50)] - public string MailboxAddress { get; set; } - - /// - /// Gets or sets the foreign key for the mailbox supplier. - /// - [Column("mailbox_supplier_id_fk")] - public int? MailboxSupplierIdFk { get; set; } - - /// - /// Gets or sets the X.509 certificate. - /// - [Column("x509_certificate")] - public string X509Certificate { get; set; } - - /// - /// Gets or sets the language code. - /// - [Column("language_code")] - [StringLength(2)] - public string LanguageCode { get; set; } - - /// - /// Gets or sets the navigation property for the associated mailbox supplier. - /// - [ForeignKey("MailboxSupplierIdFk")] - [InverseProperty("Registers")] - public virtual MailboxSupplier MailboxSupplierIdFkNavigation { get; set; } -} diff --git a/src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactInfoProfile.cs b/src/Altinn.Profile.Integrations/Mappings/PersonToUserContactInfoProfile.cs similarity index 79% rename from src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactInfoProfile.cs rename to src/Altinn.Profile.Integrations/Mappings/PersonToUserContactInfoProfile.cs index 7c74596..466b647 100644 --- a/src/Altinn.Profile.Integrations/Mappings/RegisterToUserContactInfoProfile.cs +++ b/src/Altinn.Profile.Integrations/Mappings/PersonToUserContactInfoProfile.cs @@ -8,15 +8,15 @@ namespace Altinn.Profile.Integrations.Mappings; /// /// This profile defines the mapping rules to convert a object into a instance. /// -public class RegisterToUserContactInfoProfile : AutoMapper.Profile +public class PersonToUserContactInfoProfile : AutoMapper.Profile { /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// and configures the mappings. /// - public RegisterToUserContactInfoProfile() + public PersonToUserContactInfoProfile() { - CreateMap() + CreateMap() .ForMember(dest => dest.IsReserved, opt => opt.MapFrom(src => src.Reservation)) .ForMember(dest => dest.EmailAddress, opt => opt.MapFrom(src => src.EmailAddress)) .ForMember(dest => dest.LanguageCode, opt => opt.MapFrom(src => src.LanguageCode)) diff --git a/src/Altinn.Profile.Integrations/Persistence/ProfiledbContext.cs b/src/Altinn.Profile.Integrations/Persistence/ProfiledbContext.cs index 93c7ed7..216e856 100644 --- a/src/Altinn.Profile.Integrations/Persistence/ProfiledbContext.cs +++ b/src/Altinn.Profile.Integrations/Persistence/ProfiledbContext.cs @@ -32,9 +32,9 @@ public ProfileDbContext(DbContextOptions options) public virtual DbSet Metadata { get; set; } /// - /// Gets or sets the representing the registers. + /// Gets or sets the representing the people. /// - public virtual DbSet Registers { get; set; } + public virtual DbSet People { get; set; } /// /// Configures the schema needed for the context. @@ -57,16 +57,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.LatestChangeNumber).ValueGeneratedNever(); }); - modelBuilder.Entity(entity => + modelBuilder.Entity(entity => { - entity.HasKey(e => e.ContactAndReservationUserId).HasName("register_pkey"); + entity.HasKey(e => e.ContactAndReservationUserId).HasName("person_pkey"); entity.Property(e => e.ContactAndReservationUserId).UseIdentityAlwaysColumn(); entity.Property(e => e.FnumberAk).IsFixedLength(); entity.Property(e => e.LanguageCode).IsFixedLength(); entity.HasOne(d => d.MailboxSupplierIdFkNavigation) - .WithMany(p => p.Registers) + .WithMany(p => p.People) .HasConstraintName("fk_mailbox_supplier"); }); diff --git a/src/Altinn.Profile.Integrations/Repositories/IPersonRepository.cs b/src/Altinn.Profile.Integrations/Repositories/IPersonRepository.cs index 7c3ec19..7be12fa 100644 --- a/src/Altinn.Profile.Integrations/Repositories/IPersonRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/IPersonRepository.cs @@ -10,7 +10,7 @@ namespace Altinn.Profile.Integrations.Repositories; /// /// Repository for handling register data. /// -public interface IPersonRepository : IRepository +public interface IPersonRepository : IRepository { /// /// Asynchronously retrieves the register data for multiple users by their national identity numbers. @@ -19,5 +19,5 @@ public interface IPersonRepository : IRepository /// /// A task that represents the asynchronous operation. The task result contains a collection of register data for the users. /// - Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers); + Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers); } diff --git a/src/Altinn.Profile.Integrations/Repositories/PersonRepository.cs b/src/Altinn.Profile.Integrations/Repositories/PersonRepository.cs index 376cf83..8517cb1 100644 --- a/src/Altinn.Profile.Integrations/Repositories/PersonRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/PersonRepository.cs @@ -13,7 +13,7 @@ namespace Altinn.Profile.Integrations.Repositories; /// Repository for handling register data. /// /// -internal class PersonRepository : ProfileRepository, IPersonRepository +internal class PersonRepository : ProfileRepository, IPersonRepository { private readonly ProfileDbContext _context; @@ -35,11 +35,11 @@ public PersonRepository(ProfileDbContext context) /// A task that represents the asynchronous operation. The task result contains a collection of register data for the users. /// /// Thrown when the is null. - public async Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers) + public async Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers) { ArgumentNullException.ThrowIfNull(nationalIdentityNumbers); - var registers = await _context.Registers.Where(e => nationalIdentityNumbers.Contains(e.FnumberAk)).ToListAsync(); + var registers = await _context.People.Where(e => nationalIdentityNumbers.Contains(e.FnumberAk)).ToListAsync(); return registers.ToImmutableList(); } diff --git a/src/Altinn.Profile/efpt.config.json b/src/Altinn.Profile/efpt.config.json deleted file mode 100644 index ac5afc4..0000000 --- a/src/Altinn.Profile/efpt.config.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "CodeGenerationMode": 4, - "ContextClassName": "ProfileDbContext", - "ContextNamespace": null, - "FilterSchemas": false, - "IncludeConnectionString": false, - "ModelNamespace": null, - "OutputContextPath": null, - "OutputPath": "Models", - "PreserveCasingWithRegex": true, - "ProjectRootNamespace": "Altinn.Profile", - "Schemas": null, - "SelectedHandlebarsLanguage": 2, - "SelectedToBeGenerated": 0, - "T4TemplatePath": null, - "Tables": [ - { - "Name": "contact_and_reservation.mailbox_supplier", - "ObjectType": 0 - }, - { - "Name": "contact_and_reservation.metadata", - "ObjectType": 0 - }, - { - "Name": "contact_and_reservation.person", - "ObjectType": 0 - } - ], - "UiHint": null, - "UncountableWords": null, - "UseAsyncStoredProcedureCalls": true, - "UseBoolPropertiesWithoutDefaultSql": false, - "UseDatabaseNames": false, - "UseDateOnlyTimeOnly": true, - "UseDbContextSplitting": false, - "UseDecimalDataAnnotationForSprocResult": true, - "UseFluentApiOnly": false, - "UseHandleBars": false, - "UseHierarchyId": false, - "UseInflector": true, - "UseLegacyPluralizer": false, - "UseManyToManyEntity": false, - "UseNoDefaultConstructor": false, - "UseNoNavigations": false, - "UseNoObjectFilter": false, - "UseNodaTime": false, - "UseNullableReferences": false, - "UsePrefixNavigationNaming": false, - "UseSchemaFolders": false, - "UseSchemaNamespaces": false, - "UseSpatial": false, - "UseT4": false -} \ No newline at end of file diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs index 457d103..c87a850 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs @@ -21,7 +21,7 @@ public class RegisterRepositoryTests : IDisposable { private readonly ProfileDbContext _context; private readonly PersonRepository _registerRepository; - private readonly List _personContactAndReservationTestData; + private readonly List _personContactAndReservationTestData; public RegisterRepositoryTests() { @@ -34,7 +34,7 @@ public RegisterRepositoryTests() _personContactAndReservationTestData = [.. PersonTestData.GetContactAndReservationTestData()]; - _context.Registers.AddRange(_personContactAndReservationTestData); + _context.People.AddRange(_personContactAndReservationTestData); _context.SaveChanges(); } @@ -109,7 +109,7 @@ public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenNotFound() Assert.Empty(result); } - private static void AssertRegisterProperties(Register expected, Register actual) + private static void AssertRegisterProperties(Person expected, Person actual) { Assert.Equal(expected.FnumberAk, actual.FnumberAk); Assert.Equal(expected.Description, actual.Description); diff --git a/test/Altinn.Profile.Tests/Testdata/PersonTestData.cs b/test/Altinn.Profile.Tests/Testdata/PersonTestData.cs index c8b066f..48be5a1 100644 --- a/test/Altinn.Profile.Tests/Testdata/PersonTestData.cs +++ b/test/Altinn.Profile.Tests/Testdata/PersonTestData.cs @@ -13,7 +13,7 @@ public static class PersonTestData /// Gets a list of test registers with predefined contact and reservation data. /// /// A list of objects containing test data. - public static List GetContactAndReservationTestData() + public static List GetContactAndReservationTestData() { return [ From 2a7bcf973944131b68eadaa91fb4f92b648e880a Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 11:58:27 +0200 Subject: [PATCH 53/98] Rename a number of classes --- ...pResult.cs => IContactInfoLookupResult.cs} | 2 +- .../Entities/UserContactInfoLookupResult.cs | 2 +- .../Services/IPersonService.cs | 2 +- .../Services/PersonService.cs | 2 +- ...troller.cs => ContactDetailsController.cs} | 25 ++--- .../ContactDetailsInternalController.cs | 56 ++++++++++++ .../UserContactDetailsInternalController.cs | 51 ----------- ...serContactDetails.cs => ContactDetails.cs} | 14 +-- .../Models/ContactDetailsLookupResult.cs | 39 ++++++++ .../Models/UserContactDetailsLookupResult.cs | 39 -------- src/Altinn.Profile/Program.cs | 2 +- .../UseCases/ContactDetailsRetriever.cs | 91 +++++++++++++++++++ .../UseCases/IContactDetailsRetriever.cs | 22 +++++ .../UseCases/IUserContactDetailsRetriever.cs | 19 ---- .../UseCases/UserContactDetailsRetriever.cs | 90 ------------------ .../UserContactDetailsRetrieverTests.cs | 4 +- 16 files changed, 236 insertions(+), 224 deletions(-) rename src/Altinn.Profile.Integrations/Entities/{IUserContactInfoLookupResult.cs => IContactInfoLookupResult.cs} (93%) rename src/Altinn.Profile/Controllers/{UserContactDetailsController.cs => ContactDetailsController.cs} (54%) create mode 100644 src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs delete mode 100644 src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs rename src/Altinn.Profile/Models/{UserContactDetails.cs => ContactDetails.cs} (61%) create mode 100644 src/Altinn.Profile/Models/ContactDetailsLookupResult.cs delete mode 100644 src/Altinn.Profile/Models/UserContactDetailsLookupResult.cs create mode 100644 src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs create mode 100644 src/Altinn.Profile/UseCases/IContactDetailsRetriever.cs delete mode 100644 src/Altinn.Profile/UseCases/IUserContactDetailsRetriever.cs delete mode 100644 src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs diff --git a/src/Altinn.Profile.Integrations/Entities/IUserContactInfoLookupResult.cs b/src/Altinn.Profile.Integrations/Entities/IContactInfoLookupResult.cs similarity index 93% rename from src/Altinn.Profile.Integrations/Entities/IUserContactInfoLookupResult.cs rename to src/Altinn.Profile.Integrations/Entities/IContactInfoLookupResult.cs index cc7c423..256acbd 100644 --- a/src/Altinn.Profile.Integrations/Entities/IUserContactInfoLookupResult.cs +++ b/src/Altinn.Profile.Integrations/Entities/IContactInfoLookupResult.cs @@ -7,7 +7,7 @@ namespace Altinn.Profile.Integrations.Entities; /// /// Defines the result of a user contact information lookup. /// -public interface IUserContactInfoLookupResult +public interface IContactInfoLookupResult { /// /// Gets a list of user contact information that was successfully matched during the lookup. diff --git a/src/Altinn.Profile.Integrations/Entities/UserContactInfoLookupResult.cs b/src/Altinn.Profile.Integrations/Entities/UserContactInfoLookupResult.cs index 20e1c73..d124ff9 100644 --- a/src/Altinn.Profile.Integrations/Entities/UserContactInfoLookupResult.cs +++ b/src/Altinn.Profile.Integrations/Entities/UserContactInfoLookupResult.cs @@ -7,7 +7,7 @@ namespace Altinn.Profile.Integrations.Entities; /// /// Represents the result of a user contact information lookup, containing matched and unmatched entries. /// -public record UserContactInfoLookupResult : IUserContactInfoLookupResult +public record UserContactInfoLookupResult : IContactInfoLookupResult { /// /// Gets a list of user contact information that was successfully matched during the lookup. diff --git a/src/Altinn.Profile.Integrations/Services/IPersonService.cs b/src/Altinn.Profile.Integrations/Services/IPersonService.cs index e09b42b..0920e05 100644 --- a/src/Altinn.Profile.Integrations/Services/IPersonService.cs +++ b/src/Altinn.Profile.Integrations/Services/IPersonService.cs @@ -26,5 +26,5 @@ public interface IPersonService /// /// A task that represents the asynchronous operation. The task result contains a collection of user contact information, or an empty collection if none are found. /// - Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers); + Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers); } diff --git a/src/Altinn.Profile.Integrations/Services/PersonService.cs b/src/Altinn.Profile.Integrations/Services/PersonService.cs index 1c4e9fb..ee8376f 100644 --- a/src/Altinn.Profile.Integrations/Services/PersonService.cs +++ b/src/Altinn.Profile.Integrations/Services/PersonService.cs @@ -57,7 +57,7 @@ public PersonService(IMapper mapper, IPersonRepository registerRepository, INati /// A task that represents the asynchronous operation. The task result contains a collection of user contact information, or an empty collection if none are found. /// /// Thrown if is null. - public async Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers) + public async Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers) { ArgumentNullException.ThrowIfNull(nationalIdentityNumbers); diff --git a/src/Altinn.Profile/Controllers/UserContactDetailsController.cs b/src/Altinn.Profile/Controllers/ContactDetailsController.cs similarity index 54% rename from src/Altinn.Profile/Controllers/UserContactDetailsController.cs rename to src/Altinn.Profile/Controllers/ContactDetailsController.cs index ffffb99..3520069 100644 --- a/src/Altinn.Profile/Controllers/UserContactDetailsController.cs +++ b/src/Altinn.Profile/Controllers/ContactDetailsController.cs @@ -11,41 +11,44 @@ namespace Altinn.Profile.Controllers; /// -/// Controller to retrieve users contact details. +/// Controller to retrieve the contact details for one or more persons. /// [Authorize] [ApiController] [Consumes("application/json")] [Produces("application/json")] [Route("profile/api/v1/contact/details")] -public class UserContactDetailsController : ControllerBase +public class ContactDetailsController : ControllerBase { - private readonly IUserContactDetailsRetriever _contactDetailsRetriever; + private readonly IContactDetailsRetriever _contactDetailsRetriever; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The use case for retrieving user contact details. + /// The use case for retrieving the contact details. /// Thrown when the is null. - public UserContactDetailsController(IUserContactDetailsRetriever contactDetailsRetriever) + public ContactDetailsController(IContactDetailsRetriever contactDetailsRetriever) { _contactDetailsRetriever = contactDetailsRetriever ?? throw new ArgumentNullException(nameof(contactDetailsRetriever)); } /// - /// Retrieves the contact details for users based on their national identity numbers. + /// Retrieves the contact details for persons based on their national identity numbers. /// /// A collection of national identity numbers. - /// A task that represents the asynchronous operation, containing a response with users' contact details. + /// + /// A task that represents the asynchronous operation, containing a response with persons' contact details. + /// Returns a with status 200 OK if successful. + /// [HttpPost("lookup")] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(UserContactDetailsLookupResult), StatusCodes.Status200OK)] - public async Task> PostLookup([FromBody] UserContactPointLookup request) + [ProducesResponseType(typeof(ContactDetailsLookupResult), StatusCodes.Status200OK)] + public async Task> PostLookup([FromBody] UserContactPointLookup request) { var result = await _contactDetailsRetriever.RetrieveAsync(request); - return result.Match>( + return result.Match>( success => Ok(success), failure => Problem("Unable to retrieve contact details.")); } diff --git a/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs b/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs new file mode 100644 index 0000000..28b56a0 --- /dev/null +++ b/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs @@ -0,0 +1,56 @@ +using System; +using System.Threading.Tasks; + +using Altinn.Profile.Models; +using Altinn.Profile.UseCases; + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Altinn.Profile.Controllers; + +/// +/// Controller to retrieve the contact details for one or more persons. +/// This controller is intended for internal consumption (e.g., Authorization) requiring neither authenticated user token nor access token authorization. +/// +[ApiController] +[Consumes("application/json")] +[Produces("application/json")] +[ApiExplorerSettings(IgnoreApi = true)] +[Route("profile/api/v1/internal/contact/details")] +public class ContactDetailsInternalController : ControllerBase +{ + private readonly IContactDetailsRetriever _contactDetailsRetriever; + + /// + /// Initializes a new instance of the class. + /// + /// The use case for retrieving the contact details. + /// Thrown when the is null. + public ContactDetailsInternalController(IContactDetailsRetriever contactDetailsRetriever) + { + _contactDetailsRetriever = contactDetailsRetriever ?? throw new ArgumentNullException(nameof(contactDetailsRetriever)); + } + + /// + /// Retrieves the contact details for persons based on their national identity numbers. + /// + /// A collection of national identity numbers. + /// + /// A task that represents the asynchronous operation, containing a response with persons' contact details. + /// Returns a with status 200 OK if successful, + /// 400 Bad Request if the request is invalid, or 404 Not Found if no contact details are found. + /// + [HttpPost("lookup")] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ContactDetailsLookupResult), StatusCodes.Status200OK)] + public async Task> PostLookup([FromBody] UserContactPointLookup request) + { + var result = await _contactDetailsRetriever.RetrieveAsync(request); + + return result.Match>( + success => Ok(success), + failure => Problem("Unable to retrieve contact details.")); + } +} diff --git a/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs b/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs deleted file mode 100644 index 5ccb5d8..0000000 --- a/src/Altinn.Profile/Controllers/UserContactDetailsInternalController.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Threading.Tasks; - -using Altinn.Profile.Models; -using Altinn.Profile.UseCases; - -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; - -namespace Altinn.Profile.Controllers; - -/// -/// Controller to retrieve users contact details for internal consumption (e.g. Authorization) requiring neither authenticated user token nor access token authorization. -/// -[ApiController] -[Consumes("application/json")] -[Produces("application/json")] -[ApiExplorerSettings(IgnoreApi = true)] -[Route("profile/api/v1/internal/contact/details")] -public class UserContactDetailsInternalController : ControllerBase -{ - private readonly IUserContactDetailsRetriever _contactDetailsRetriever; - - /// - /// Initializes a new instance of the class. - /// - /// The use case for retrieving user contact details. - /// Thrown when the is null. - public UserContactDetailsInternalController(IUserContactDetailsRetriever contactDetailsRetriever) - { - _contactDetailsRetriever = contactDetailsRetriever ?? throw new ArgumentNullException(nameof(contactDetailsRetriever)); - } - - /// - /// Retrieves the contact details for users based on their national identity numbers. - /// - /// A collection of national identity numbers. - /// A task that represents the asynchronous operation, containing a response with users' contact details. - [HttpPost("lookup")] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(UserContactDetailsLookupResult), StatusCodes.Status200OK)] - public async Task> PostLookup([FromBody] UserContactPointLookup request) - { - var result = await _contactDetailsRetriever.RetrieveAsync(request); - - return result.Match>( - success => Ok(success), - failure => Problem("Unable to retrieve contact details.")); - } -} diff --git a/src/Altinn.Profile/Models/UserContactDetails.cs b/src/Altinn.Profile/Models/ContactDetails.cs similarity index 61% rename from src/Altinn.Profile/Models/UserContactDetails.cs rename to src/Altinn.Profile/Models/ContactDetails.cs index 13d12c9..bba8c59 100644 --- a/src/Altinn.Profile/Models/UserContactDetails.cs +++ b/src/Altinn.Profile/Models/ContactDetails.cs @@ -5,36 +5,36 @@ namespace Altinn.Profile.Models; /// -/// Represents a user's contact information, including national identity number, contact methods, language preference, and opt-out status. +/// Represents the contact information for a single person, including national identity number, contact methods, language preference, and opt-out status. /// -public record UserContactDetails +public record ContactDetails { /// - /// Gets the national identity number of the user. + /// Gets the national identity number of the person. /// [JsonPropertyName("nationalIdentityNumber")] public required string NationalIdentityNumber { get; init; } /// - /// Gets a value indicating whether the user has opted out of being contacted. + /// Gets a value indicating whether the person has opted out of being contacted. /// [JsonPropertyName("reservation")] public bool? Reservation { get; init; } /// - /// Gets the mobile phone number of the user. + /// Gets the mobile phone number of the person. /// [JsonPropertyName("mobilePhoneNumber")] public string? MobilePhoneNumber { get; init; } /// - /// Gets the email address of the user. + /// Gets the email address of the person. /// [JsonPropertyName("emailAddress")] public string? EmailAddress { get; init; } /// - /// Gets the language code preferred by the user for communication. + /// Gets the language code preferred by the person for communication. /// [JsonPropertyName("languageCode")] public string? LanguageCode { get; init; } diff --git a/src/Altinn.Profile/Models/ContactDetailsLookupResult.cs b/src/Altinn.Profile/Models/ContactDetailsLookupResult.cs new file mode 100644 index 0000000..1a9d4a6 --- /dev/null +++ b/src/Altinn.Profile/Models/ContactDetailsLookupResult.cs @@ -0,0 +1,39 @@ +#nullable enable + +using System.Collections.Immutable; +using System.Text.Json.Serialization; + +namespace Altinn.Profile.Models; + +/// +/// Represents the results of a contact details lookup operation. +/// +public record ContactDetailsLookupResult +{ + /// + /// Gets a list of contact details that were successfully matched based on the national identity number. + /// + [JsonPropertyName("matchedContactDetails")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ImmutableList? MatchedContactDetails { get; init; } + + /// + /// Gets a list of national identity numbers that could not be matched with any contact details. + /// + [JsonPropertyName("unmatchedNationalIdentityNumbers")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ImmutableList? UnmatchedNationalIdentityNumbers { get; init; } + + /// + /// Initializes a new instance of the record. + /// + /// The list of contact details that were successfully matched based on the national identity number. + /// The list of national identity numbers that could not be matched with any contact details. + public ContactDetailsLookupResult( + ImmutableList matchedContactDetails, + ImmutableList unmatchedNationalIdentityNumbers) + { + MatchedContactDetails = matchedContactDetails; + UnmatchedNationalIdentityNumbers = unmatchedNationalIdentityNumbers; + } +} diff --git a/src/Altinn.Profile/Models/UserContactDetailsLookupResult.cs b/src/Altinn.Profile/Models/UserContactDetailsLookupResult.cs deleted file mode 100644 index 83654b0..0000000 --- a/src/Altinn.Profile/Models/UserContactDetailsLookupResult.cs +++ /dev/null @@ -1,39 +0,0 @@ -#nullable enable - -using System.Collections.Immutable; -using System.Text.Json.Serialization; - -namespace Altinn.Profile.Models; - -/// -/// Represents the results of a user contact details lookup operation. -/// -public record UserContactDetailsLookupResult -{ - /// - /// Gets a list of user contact details that were successfully matched based on the national identity number. - /// - [JsonPropertyName("matchedUserContactDetails")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public ImmutableList? MatchedUserContactDetails { get; init; } - - /// - /// Gets a list of national identity numbers that could not be matched with user contact details. - /// - [JsonPropertyName("unmatchedNationalIdentityNumbers")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public ImmutableList? UnmatchedNationalIdentityNumbers { get; init; } - - /// - /// Initializes a new instance of the record. - /// - /// The list of contact details that were matched based on the national identity number. - /// The list of national identity numbers that could not be matched with user contact details. - public UserContactDetailsLookupResult( - ImmutableList matchedUserContactDetails, - ImmutableList unmatchedNationalIdentityNumbers) - { - MatchedUserContactDetails = matchedUserContactDetails; - UnmatchedNationalIdentityNumbers = unmatchedNationalIdentityNumbers; - } -} diff --git a/src/Altinn.Profile/Program.cs b/src/Altinn.Profile/Program.cs index 50caefd..752fcbc 100644 --- a/src/Altinn.Profile/Program.cs +++ b/src/Altinn.Profile/Program.cs @@ -170,7 +170,7 @@ void ConfigureServices(IServiceCollection services, IConfiguration config) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddScoped(); + services.AddScoped(); services.AddAuthentication(JwtCookieDefaults.AuthenticationScheme) .AddJwtCookie(JwtCookieDefaults.AuthenticationScheme, options => diff --git a/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs b/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs new file mode 100644 index 0000000..38386f5 --- /dev/null +++ b/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; + +using Altinn.Profile.Core; +using Altinn.Profile.Integrations.Entities; +using Altinn.Profile.Integrations.Services; +using Altinn.Profile.Models; + +namespace Altinn.Profile.UseCases; + +/// +/// Provides an implementation for retrieving the contact details for one or more persons. +/// +public class ContactDetailsRetriever : IContactDetailsRetriever +{ + private readonly IPersonService _personService; + + /// + /// Initializes a new instance of the class. + /// + /// The person service for retrieving contact details. + /// Thrown when is null. + public ContactDetailsRetriever(IPersonService personService) + { + _personService = personService ?? throw new ArgumentNullException(nameof(personService)); + } + + /// + /// Asynchronously retrieves the contact details for one or more persons based on the specified lookup criteria. + /// + /// The criteria used to look up contact details, including the national identity numbers of the persons. + /// + /// A task representing the asynchronous operation. + /// The task result contains a object, where represents the successful outcome and indicates a failure. + /// + /// Thrown when is null. + public async Task> RetrieveAsync(UserContactPointLookup lookupCriteria) + { + ArgumentNullException.ThrowIfNull(lookupCriteria); + + if (lookupCriteria?.NationalIdentityNumbers == null || lookupCriteria.NationalIdentityNumbers.Count == 0) + { + return false; + } + + var contactDetails = await _personService.GetUserContactAsync(lookupCriteria.NationalIdentityNumbers); + + return contactDetails.Match( + MapToContactDetailsLookupResult, + _ => false); + } + + /// + /// Maps the person contact details to a . + /// + /// The person contact details to map. + /// The mapped . + /// Thrown when is null. + private ContactDetails MapToContactDetails(IUserContactInfo personContactDetails) + { + ArgumentNullException.ThrowIfNull(personContactDetails); + + return new ContactDetails + { + Reservation = personContactDetails.IsReserved, + EmailAddress = personContactDetails.EmailAddress, + LanguageCode = personContactDetails.LanguageCode, + MobilePhoneNumber = personContactDetails.MobilePhoneNumber, + NationalIdentityNumber = personContactDetails.NationalIdentityNumber + }; + } + + /// + /// Maps the person contact details lookup result to a . + /// + /// The lookup result containing the person contact details. + /// + /// A containing a if the mapping is successful, or false if the mapping fails. + /// + /// Thrown when is null. + private Result MapToContactDetailsLookupResult(IContactInfoLookupResult personContactDetailsLookupResult) + { + ArgumentNullException.ThrowIfNull(personContactDetailsLookupResult); + + var matchedContactDetails = personContactDetailsLookupResult.MatchedUserContact?.Select(MapToContactDetails).ToImmutableList(); + + return new ContactDetailsLookupResult(matchedContactDetails, personContactDetailsLookupResult?.UnmatchedNationalIdentityNumbers); + } +} diff --git a/src/Altinn.Profile/UseCases/IContactDetailsRetriever.cs b/src/Altinn.Profile/UseCases/IContactDetailsRetriever.cs new file mode 100644 index 0000000..b3ff503 --- /dev/null +++ b/src/Altinn.Profile/UseCases/IContactDetailsRetriever.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; + +using Altinn.Profile.Core; +using Altinn.Profile.Models; + +namespace Altinn.Profile.UseCases; + +/// +/// Defines a use case for retrieving the contact details for one or more persons. +/// +public interface IContactDetailsRetriever +{ + /// + /// Asynchronously retrieves the contact details for one or more persons based on the specified lookup criteria. + /// + /// The criteria used to look up contact details, including the national identity numbers of the persons. + /// + /// A task representing the asynchronous operation. + /// The task result contains a object, where represents the successful outcome and indicates a failure. + /// + Task> RetrieveAsync(UserContactPointLookup lookupCriteria); +} diff --git a/src/Altinn.Profile/UseCases/IUserContactDetailsRetriever.cs b/src/Altinn.Profile/UseCases/IUserContactDetailsRetriever.cs deleted file mode 100644 index 912200b..0000000 --- a/src/Altinn.Profile/UseCases/IUserContactDetailsRetriever.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Threading.Tasks; - -using Altinn.Profile.Core; -using Altinn.Profile.Models; - -namespace Altinn.Profile.UseCases; - -/// -/// Defines a use case for retrieving user contact details. -/// -public interface IUserContactDetailsRetriever -{ - /// - /// Asynchronously retrieves the contact details for one or more users based on the specified lookup criteria. - /// - /// The user contact point lookup criteria, which includes national identity numbers. - /// A task representing the asynchronous operation. The task result contains the outcome of the user contact details retrieval. - Task> RetrieveAsync(UserContactPointLookup lookupCriteria); -} diff --git a/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs b/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs deleted file mode 100644 index 1b97c48..0000000 --- a/src/Altinn.Profile/UseCases/UserContactDetailsRetriever.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Immutable; -using System.Linq; -using System.Threading.Tasks; - -using Altinn.Profile.Core; -using Altinn.Profile.Integrations.Entities; -using Altinn.Profile.Integrations.Services; -using Altinn.Profile.Models; - -namespace Altinn.Profile.UseCases; - -/// -/// Provides an implementation for retrieving user contact details based on specified lookup criteria. -/// -public class UserContactDetailsRetriever : IUserContactDetailsRetriever -{ - private readonly IPersonService _registerService; - - /// - /// Initializes a new instance of the class. - /// - /// The register service for retrieving user contact details. - /// Thrown when is null. - public UserContactDetailsRetriever(IPersonService registerService) - { - _registerService = registerService ?? throw new ArgumentNullException(nameof(registerService)); - } - - /// - /// Asynchronously retrieves the contact details for one or more users based on the specified lookup criteria. - /// - /// The user contact point lookup criteria, which includes national identity numbers. - /// - /// A task representing the asynchronous operation. The task result contains a - /// where the value is and the error is . - /// - /// Thrown when is null. - public async Task> RetrieveAsync(UserContactPointLookup lookupCriteria) - { - ArgumentNullException.ThrowIfNull(lookupCriteria); - - if (lookupCriteria?.NationalIdentityNumbers == null || lookupCriteria.NationalIdentityNumbers.Count == 0) - { - return false; - } - - var userContactDetails = await _registerService.GetUserContactAsync(lookupCriteria.NationalIdentityNumbers); - - return userContactDetails.Match( - MapToUserContactDetailsLookupResult, - _ => false); - } - - /// - /// Maps an to a . - /// - /// The user contact details to map. - /// The mapped . - /// Thrown when is null. - private UserContactDetails MapToUserContactDetails(IUserContactInfo userContactDetails) - { - ArgumentNullException.ThrowIfNull(userContactDetails); - - return new UserContactDetails - { - Reservation = userContactDetails.IsReserved, - EmailAddress = userContactDetails.EmailAddress, - LanguageCode = userContactDetails.LanguageCode, - MobilePhoneNumber = userContactDetails.MobilePhoneNumber, - NationalIdentityNumber = userContactDetails.NationalIdentityNumber - }; - } - - /// - /// Maps the user contact details lookup result to a . - /// - /// The user contact details lookup result. - /// A containing the mapped user contact details. - /// Thrown when is null. - private Result MapToUserContactDetailsLookupResult(IUserContactInfoLookupResult userContactResult) - { - ArgumentNullException.ThrowIfNull(userContactResult); - - var unmatchedNationalIdentityNumbers = userContactResult?.UnmatchedNationalIdentityNumbers; - var matchedUserContactDetails = userContactResult?.MatchedUserContact?.Select(MapToUserContactDetails).ToImmutableList(); - - return new UserContactDetailsLookupResult(matchedUserContactDetails, unmatchedNationalIdentityNumbers); - } -} diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/UserContactDetailsRetrieverTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/UserContactDetailsRetrieverTests.cs index 218e116..ae8e0cc 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/UserContactDetailsRetrieverTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/UserContactDetailsRetrieverTests.cs @@ -18,12 +18,12 @@ namespace Altinn.Profile.Tests.Profile.Integrations; public class UserContactDetailsRetrieverTests { private readonly Mock _mockRegisterService; - private readonly UserContactDetailsRetriever _retriever; + private readonly ContactDetailsRetriever _retriever; public UserContactDetailsRetrieverTests() { _mockRegisterService = new Mock(); - _retriever = new UserContactDetailsRetriever(_mockRegisterService.Object); + _retriever = new ContactDetailsRetriever(_mockRegisterService.Object); } [Fact] From 16dc240288f64a5e80e8398bec7bcbf3f7a15071 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 12:15:09 +0200 Subject: [PATCH 54/98] Rename some classes --- .../Entities/IContactInfoLookupResult.cs | 21 -------- .../Entities/IPersonContactDetails.cs | 34 +++++++++++++ .../IPersonContactDetailsLookupResult.cs | 27 ++++++++++ .../Entities/IUserContactInfo.cs | 49 ------------------- .../Entities/PersonContactDetails.cs | 34 +++++++++++++ .../PersonContactDetailsLookupResult.cs | 27 ++++++++++ .../Entities/UserContactInfo.cs | 49 ------------------- .../Entities/UserContactInfoLookupResult.cs | 21 -------- ...file.cs => PersonContactDetailsProfile.cs} | 13 +++-- .../Services/IPersonService.cs | 4 +- .../Services/PersonService.cs | 12 ++--- .../UseCases/ContactDetailsRetriever.cs | 30 ++++++------ 12 files changed, 151 insertions(+), 170 deletions(-) delete mode 100644 src/Altinn.Profile.Integrations/Entities/IContactInfoLookupResult.cs create mode 100644 src/Altinn.Profile.Integrations/Entities/IPersonContactDetails.cs create mode 100644 src/Altinn.Profile.Integrations/Entities/IPersonContactDetailsLookupResult.cs delete mode 100644 src/Altinn.Profile.Integrations/Entities/IUserContactInfo.cs create mode 100644 src/Altinn.Profile.Integrations/Entities/PersonContactDetails.cs create mode 100644 src/Altinn.Profile.Integrations/Entities/PersonContactDetailsLookupResult.cs delete mode 100644 src/Altinn.Profile.Integrations/Entities/UserContactInfo.cs delete mode 100644 src/Altinn.Profile.Integrations/Entities/UserContactInfoLookupResult.cs rename src/Altinn.Profile.Integrations/Mappings/{PersonToUserContactInfoProfile.cs => PersonContactDetailsProfile.cs} (62%) diff --git a/src/Altinn.Profile.Integrations/Entities/IContactInfoLookupResult.cs b/src/Altinn.Profile.Integrations/Entities/IContactInfoLookupResult.cs deleted file mode 100644 index 256acbd..0000000 --- a/src/Altinn.Profile.Integrations/Entities/IContactInfoLookupResult.cs +++ /dev/null @@ -1,21 +0,0 @@ -#nullable enable - -using System.Collections.Immutable; - -namespace Altinn.Profile.Integrations.Entities; - -/// -/// Defines the result of a user contact information lookup. -/// -public interface IContactInfoLookupResult -{ - /// - /// Gets a list of user contact information that was successfully matched during the lookup. - /// - ImmutableList? MatchedUserContact { get; } - - /// - /// Gets a list of national identity numbers that could not be matched with any user contact details. - /// - ImmutableList? UnmatchedNationalIdentityNumbers { get; } -} diff --git a/src/Altinn.Profile.Integrations/Entities/IPersonContactDetails.cs b/src/Altinn.Profile.Integrations/Entities/IPersonContactDetails.cs new file mode 100644 index 0000000..cb1e7db --- /dev/null +++ b/src/Altinn.Profile.Integrations/Entities/IPersonContactDetails.cs @@ -0,0 +1,34 @@ +#nullable enable + +namespace Altinn.Profile.Integrations.Entities; + +/// +/// Represents a person's contact details. +/// +public interface IPersonContactDetails +{ + /// + /// Gets the national identity number of the person. + /// + string NationalIdentityNumber { get; } + + /// + /// Gets a value indicating whether the person opts out of being contacted. + /// + bool? IsReserved { get; } + + /// + /// Gets the mobile phone number of the person. + /// + string? MobilePhoneNumber { get; } + + /// + /// Gets the email address of the person. + /// + string? EmailAddress { get; } + + /// + /// Gets the language code of the person, represented as an ISO 639-1 code. + /// + string? LanguageCode { get; } +} diff --git a/src/Altinn.Profile.Integrations/Entities/IPersonContactDetailsLookupResult.cs b/src/Altinn.Profile.Integrations/Entities/IPersonContactDetailsLookupResult.cs new file mode 100644 index 0000000..b717305 --- /dev/null +++ b/src/Altinn.Profile.Integrations/Entities/IPersonContactDetailsLookupResult.cs @@ -0,0 +1,27 @@ +#nullable enable + +using System.Collections.Immutable; + +namespace Altinn.Profile.Integrations.Entities; + +/// +/// Represents the result of a lookup operation for contact details. +/// +public interface IPersonContactDetailsLookupResult +{ + /// + /// Gets a list of national identity numbers that could not be matched with any person contact details. + /// + /// + /// An of containing the unmatched national identity numbers. + /// + ImmutableList? UnmatchedNationalIdentityNumbers { get; } + + /// + /// Gets a list of person contact details that were successfully matched during the lookup. + /// + /// + /// An of containing the matched person contact details. + /// + ImmutableList? MatchedPersonContactDetails { get; } +} diff --git a/src/Altinn.Profile.Integrations/Entities/IUserContactInfo.cs b/src/Altinn.Profile.Integrations/Entities/IUserContactInfo.cs deleted file mode 100644 index 42ddd45..0000000 --- a/src/Altinn.Profile.Integrations/Entities/IUserContactInfo.cs +++ /dev/null @@ -1,49 +0,0 @@ -#nullable enable - -namespace Altinn.Profile.Integrations.Entities; - -/// -/// Represents a user's contact information. -/// -public interface IUserContactInfo -{ - /// - /// Gets the national identity number of the user. - /// - /// - /// This is a unique identifier for the user. - /// - string NationalIdentityNumber { get; } - - /// - /// Gets a value indicating whether the user opts out of being contacted. - /// - /// - /// If true, the user has opted out of being contacted. If false, the user has not opted out. - /// - bool? IsReserved { get; } - - /// - /// Gets the mobile phone number of the user. - /// - /// - /// This is the user's primary contact number. - /// - string? MobilePhoneNumber { get; } - - /// - /// Gets the email address of the user. - /// - /// - /// This is the user's primary email address. - /// - string? EmailAddress { get; } - - /// - /// Gets the language code of the user. - /// - /// - /// This is the preferred language of the user, represented as an ISO 639-1 code. - /// - string? LanguageCode { get; } -} diff --git a/src/Altinn.Profile.Integrations/Entities/PersonContactDetails.cs b/src/Altinn.Profile.Integrations/Entities/PersonContactDetails.cs new file mode 100644 index 0000000..9de6fd9 --- /dev/null +++ b/src/Altinn.Profile.Integrations/Entities/PersonContactDetails.cs @@ -0,0 +1,34 @@ +#nullable enable + +namespace Altinn.Profile.Integrations.Entities; + +/// +/// Represents a person's contact details. +/// +public record PersonContactDetails : IPersonContactDetails +{ + /// + /// Gets the national identity number of the person. + /// + public required string NationalIdentityNumber { get; init; } + + /// + /// Gets a value indicating whether the person opts out of being contacted. + /// + public bool? IsReserved { get; init; } + + /// + /// Gets the mobile phone number of the person. + /// + public string? MobilePhoneNumber { get; init; } + + /// + /// Gets the email address of the person. + /// + public string? EmailAddress { get; init; } + + /// + /// Gets the language code of the person, represented as an ISO 639-1 code. + /// + public string? LanguageCode { get; init; } +} diff --git a/src/Altinn.Profile.Integrations/Entities/PersonContactDetailsLookupResult.cs b/src/Altinn.Profile.Integrations/Entities/PersonContactDetailsLookupResult.cs new file mode 100644 index 0000000..470e526 --- /dev/null +++ b/src/Altinn.Profile.Integrations/Entities/PersonContactDetailsLookupResult.cs @@ -0,0 +1,27 @@ +#nullable enable + +using System.Collections.Immutable; + +namespace Altinn.Profile.Integrations.Entities; + +/// +/// Represents the result of a lookup operation for contact details. +/// +public record PersonContactDetailsLookupResult : IPersonContactDetailsLookupResult +{ + /// + /// Gets a list of national identity numbers that could not be matched with any person contact details. + /// + /// + /// An of containing the unmatched national identity numbers. + /// + public ImmutableList? UnmatchedNationalIdentityNumbers { get; init; } + + /// + /// Gets a list of person contact details that were successfully matched during the lookup. + /// + /// + /// An of containing the matched person contact details. + /// + public ImmutableList? MatchedPersonContactDetails { get; init; } +} diff --git a/src/Altinn.Profile.Integrations/Entities/UserContactInfo.cs b/src/Altinn.Profile.Integrations/Entities/UserContactInfo.cs deleted file mode 100644 index 762de09..0000000 --- a/src/Altinn.Profile.Integrations/Entities/UserContactInfo.cs +++ /dev/null @@ -1,49 +0,0 @@ -#nullable enable - -namespace Altinn.Profile.Integrations.Entities; - -/// -/// Represents a user's contact information. -/// -public record UserContactInfo : IUserContactInfo -{ - /// - /// Gets the national identity number of the user. - /// - /// - /// This is a unique identifier for the user. - /// - public required string NationalIdentityNumber { get; init; } - - /// - /// Gets a value indicating whether the user opts out of being contacted. - /// - /// - /// If true, the user has opted out of being contacted. If false, the user has not opted out. - /// - public bool? IsReserved { get; init; } - - /// - /// Gets the mobile phone number of the user. - /// - /// - /// This is the user's primary contact number. - /// - public string? MobilePhoneNumber { get; init; } - - /// - /// Gets the email address of the user. - /// - /// - /// This is the user's primary email address. - /// - public string? EmailAddress { get; init; } - - /// - /// Gets the language code of the user. - /// - /// - /// This is the preferred language of the user, represented as an ISO 639-1 code. - /// - public string? LanguageCode { get; init; } -} diff --git a/src/Altinn.Profile.Integrations/Entities/UserContactInfoLookupResult.cs b/src/Altinn.Profile.Integrations/Entities/UserContactInfoLookupResult.cs deleted file mode 100644 index d124ff9..0000000 --- a/src/Altinn.Profile.Integrations/Entities/UserContactInfoLookupResult.cs +++ /dev/null @@ -1,21 +0,0 @@ -#nullable enable - -using System.Collections.Immutable; - -namespace Altinn.Profile.Integrations.Entities; - -/// -/// Represents the result of a user contact information lookup, containing matched and unmatched entries. -/// -public record UserContactInfoLookupResult : IContactInfoLookupResult -{ - /// - /// Gets a list of user contact information that was successfully matched during the lookup. - /// - public ImmutableList? MatchedUserContact { get; init; } - - /// - /// Gets a list of national identity numbers that could not be matched with any user contact details. - /// - public ImmutableList? UnmatchedNationalIdentityNumbers { get; init; } -} diff --git a/src/Altinn.Profile.Integrations/Mappings/PersonToUserContactInfoProfile.cs b/src/Altinn.Profile.Integrations/Mappings/PersonContactDetailsProfile.cs similarity index 62% rename from src/Altinn.Profile.Integrations/Mappings/PersonToUserContactInfoProfile.cs rename to src/Altinn.Profile.Integrations/Mappings/PersonContactDetailsProfile.cs index 466b647..64d7881 100644 --- a/src/Altinn.Profile.Integrations/Mappings/PersonToUserContactInfoProfile.cs +++ b/src/Altinn.Profile.Integrations/Mappings/PersonContactDetailsProfile.cs @@ -3,20 +3,19 @@ namespace Altinn.Profile.Integrations.Mappings; /// -/// AutoMapper profile for mapping between and . +/// AutoMapper profile for mapping between and . /// /// -/// This profile defines the mapping rules to convert a object into a instance. +/// This profile defines the mapping rules to convert a object into a instance. /// -public class PersonToUserContactInfoProfile : AutoMapper.Profile +public class PersonContactDetailsProfile : AutoMapper.Profile { /// - /// Initializes a new instance of the class - /// and configures the mappings. + /// Initializes a new instance of the class and configures the mappings. /// - public PersonToUserContactInfoProfile() + public PersonContactDetailsProfile() { - CreateMap() + CreateMap() .ForMember(dest => dest.IsReserved, opt => opt.MapFrom(src => src.Reservation)) .ForMember(dest => dest.EmailAddress, opt => opt.MapFrom(src => src.EmailAddress)) .ForMember(dest => dest.LanguageCode, opt => opt.MapFrom(src => src.LanguageCode)) diff --git a/src/Altinn.Profile.Integrations/Services/IPersonService.cs b/src/Altinn.Profile.Integrations/Services/IPersonService.cs index 0920e05..581892b 100644 --- a/src/Altinn.Profile.Integrations/Services/IPersonService.cs +++ b/src/Altinn.Profile.Integrations/Services/IPersonService.cs @@ -17,7 +17,7 @@ public interface IPersonService /// /// A task that represents the asynchronous operation. The task result contains the user's contact information, or null if not found. /// - Task GetUserContactInfoAsync(string nationalIdentityNumber); + Task GetUserContactInfoAsync(string nationalIdentityNumber); /// /// Asynchronously retrieves the contact information for multiple users based on their national identity numbers. @@ -26,5 +26,5 @@ public interface IPersonService /// /// A task that represents the asynchronous operation. The task result contains a collection of user contact information, or an empty collection if none are found. /// - Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers); + Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers); } diff --git a/src/Altinn.Profile.Integrations/Services/PersonService.cs b/src/Altinn.Profile.Integrations/Services/PersonService.cs index ee8376f..5e8db4c 100644 --- a/src/Altinn.Profile.Integrations/Services/PersonService.cs +++ b/src/Altinn.Profile.Integrations/Services/PersonService.cs @@ -38,7 +38,7 @@ public PersonService(IMapper mapper, IPersonRepository registerRepository, INati /// /// A task that represents the asynchronous operation. The task result contains the user's contact information, or null if not found. /// - public async Task GetUserContactInfoAsync(string nationalIdentityNumber) + public async Task GetUserContactInfoAsync(string nationalIdentityNumber) { if (!_nationalIdentityNumberChecker.IsValid(nationalIdentityNumber)) { @@ -46,7 +46,7 @@ public PersonService(IMapper mapper, IPersonRepository registerRepository, INati } var userContactInfoEntity = await _registerRepository.GetUserContactInfoAsync([nationalIdentityNumber]); - return _mapper.Map(userContactInfoEntity); + return _mapper.Map(userContactInfoEntity); } /// @@ -57,7 +57,7 @@ public PersonService(IMapper mapper, IPersonRepository registerRepository, INati /// A task that represents the asynchronous operation. The task result contains a collection of user contact information, or an empty collection if none are found. /// /// Thrown if is null. - public async Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers) + public async Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers) { ArgumentNullException.ThrowIfNull(nationalIdentityNumbers); @@ -65,14 +65,14 @@ public async Task> GetUserContactAsync(IE var usersContactInfo = await _registerRepository.GetUserContactInfoAsync(validnNtionalIdentityNumbers); - var matchedUserContact = usersContactInfo.Select(_mapper.Map); + var matchedUserContact = usersContactInfo.Select(_mapper.Map); var matchedNationalIdentityNumbers = new HashSet(usersContactInfo.Select(e => e.FnumberAk)); var unmatchedNationalIdentityNumbers = nationalIdentityNumbers.Where(e => !matchedNationalIdentityNumbers.Contains(e)); - return new UserContactInfoLookupResult + return new PersonContactDetailsLookupResult { - MatchedUserContact = matchedUserContact.ToImmutableList(), + MatchedPersonContactDetails = matchedUserContact.ToImmutableList(), UnmatchedNationalIdentityNumbers = unmatchedNationalIdentityNumbers.ToImmutableList() }; } diff --git a/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs b/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs index 38386f5..505a07c 100644 --- a/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs +++ b/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs @@ -55,37 +55,37 @@ public async Task> RetrieveAsync(UserCo /// /// Maps the person contact details to a . /// - /// The person contact details to map. + /// The person contact details to map. /// The mapped . - /// Thrown when is null. - private ContactDetails MapToContactDetails(IUserContactInfo personContactDetails) + /// Thrown when is null. + private ContactDetails MapToContactDetails(IPersonContactDetails contactDetails) { - ArgumentNullException.ThrowIfNull(personContactDetails); + ArgumentNullException.ThrowIfNull(contactDetails); return new ContactDetails { - Reservation = personContactDetails.IsReserved, - EmailAddress = personContactDetails.EmailAddress, - LanguageCode = personContactDetails.LanguageCode, - MobilePhoneNumber = personContactDetails.MobilePhoneNumber, - NationalIdentityNumber = personContactDetails.NationalIdentityNumber + Reservation = contactDetails.IsReserved, + EmailAddress = contactDetails.EmailAddress, + LanguageCode = contactDetails.LanguageCode, + MobilePhoneNumber = contactDetails.MobilePhoneNumber, + NationalIdentityNumber = contactDetails.NationalIdentityNumber }; } /// /// Maps the person contact details lookup result to a . /// - /// The lookup result containing the person contact details. + /// The lookup result containing the person contact details. /// /// A containing a if the mapping is successful, or false if the mapping fails. /// - /// Thrown when is null. - private Result MapToContactDetailsLookupResult(IContactInfoLookupResult personContactDetailsLookupResult) + /// Thrown when is null. + private Result MapToContactDetailsLookupResult(IPersonContactDetailsLookupResult lookupResult) { - ArgumentNullException.ThrowIfNull(personContactDetailsLookupResult); + ArgumentNullException.ThrowIfNull(lookupResult); - var matchedContactDetails = personContactDetailsLookupResult.MatchedUserContact?.Select(MapToContactDetails).ToImmutableList(); + var matchedContactDetails = lookupResult.MatchedPersonContactDetails?.Select(MapToContactDetails).ToImmutableList(); - return new ContactDetailsLookupResult(matchedContactDetails, personContactDetailsLookupResult?.UnmatchedNationalIdentityNumbers); + return new ContactDetailsLookupResult(matchedContactDetails, lookupResult?.UnmatchedNationalIdentityNumbers); } } From b224d146cdd9878b406a551882487a9e7f9ce291 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 12:21:14 +0200 Subject: [PATCH 55/98] Rename more classes --- .../Repositories/IPersonRepository.cs | 10 +++++----- .../Repositories/PersonRepository.cs | 19 ++++++++++++------- .../Services/PersonService.cs | 4 ++-- .../Register/RegisterRepositoryTests.cs | 10 +++++----- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/Altinn.Profile.Integrations/Repositories/IPersonRepository.cs b/src/Altinn.Profile.Integrations/Repositories/IPersonRepository.cs index 7be12fa..5a2a0f4 100644 --- a/src/Altinn.Profile.Integrations/Repositories/IPersonRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/IPersonRepository.cs @@ -8,16 +8,16 @@ namespace Altinn.Profile.Integrations.Repositories; /// -/// Repository for handling register data. +/// Defines a repository for handling person data operations. /// public interface IPersonRepository : IRepository { /// - /// Asynchronously retrieves the register data for multiple users by their national identity numbers. + /// Asynchronously retrieves the contact details for multiple persons by their national identity numbers. /// - /// The collection of national identity numbers. + /// A collection of national identity numbers to look up for. /// - /// A task that represents the asynchronous operation. The task result contains a collection of register data for the users. + /// A task that represents the asynchronous operation. The task result contains an of objects representing the contact details of the persons. /// - Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers); + Task> GetContactDetailsAsync(IEnumerable nationalIdentityNumbers); } diff --git a/src/Altinn.Profile.Integrations/Repositories/PersonRepository.cs b/src/Altinn.Profile.Integrations/Repositories/PersonRepository.cs index 8517cb1..fe9041e 100644 --- a/src/Altinn.Profile.Integrations/Repositories/PersonRepository.cs +++ b/src/Altinn.Profile.Integrations/Repositories/PersonRepository.cs @@ -10,7 +10,7 @@ namespace Altinn.Profile.Integrations.Repositories; /// -/// Repository for handling register data. +/// Defines a repository for handling person data operations. /// /// internal class PersonRepository : ProfileRepository, IPersonRepository @@ -28,19 +28,24 @@ public PersonRepository(ProfileDbContext context) } /// - /// Asynchronously retrieves the register data for multiple users by their national identity numbers. + /// Asynchronously retrieves the contact details for multiple persons by their national identity numbers. /// - /// The collection of national identity numbers. + /// A collection of national identity numbers to look up for. /// - /// A task that represents the asynchronous operation. The task result contains a collection of register data for the users. + /// A task that represents the asynchronous operation. The task result contains an of objects representing the contact details of the persons. /// /// Thrown when the is null. - public async Task> GetUserContactInfoAsync(IEnumerable nationalIdentityNumbers) + public async Task> GetContactDetailsAsync(IEnumerable nationalIdentityNumbers) { ArgumentNullException.ThrowIfNull(nationalIdentityNumbers); - var registers = await _context.People.Where(e => nationalIdentityNumbers.Contains(e.FnumberAk)).ToListAsync(); + if (!nationalIdentityNumbers.Any()) + { + return []; + } - return registers.ToImmutableList(); + var people = await _context.People.Where(e => nationalIdentityNumbers.Contains(e.FnumberAk)).ToListAsync(); + + return [.. people]; } } diff --git a/src/Altinn.Profile.Integrations/Services/PersonService.cs b/src/Altinn.Profile.Integrations/Services/PersonService.cs index 5e8db4c..f4bb4d6 100644 --- a/src/Altinn.Profile.Integrations/Services/PersonService.cs +++ b/src/Altinn.Profile.Integrations/Services/PersonService.cs @@ -45,7 +45,7 @@ public PersonService(IMapper mapper, IPersonRepository registerRepository, INati return null; } - var userContactInfoEntity = await _registerRepository.GetUserContactInfoAsync([nationalIdentityNumber]); + var userContactInfoEntity = await _registerRepository.GetContactDetailsAsync([nationalIdentityNumber]); return _mapper.Map(userContactInfoEntity); } @@ -63,7 +63,7 @@ public async Task> GetUserContac var (validnNtionalIdentityNumbers, _) = _nationalIdentityNumberChecker.Categorize(nationalIdentityNumbers); - var usersContactInfo = await _registerRepository.GetUserContactInfoAsync(validnNtionalIdentityNumbers); + var usersContactInfo = await _registerRepository.GetContactDetailsAsync(validnNtionalIdentityNumbers); var matchedUserContact = usersContactInfo.Select(_mapper.Map); diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs index c87a850..8495841 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs @@ -49,7 +49,7 @@ public void Dispose() public async Task GetUserContactInfoAsync_ReturnsContactInfo_WhenFound() { // Act - var result = await _registerRepository.GetUserContactInfoAsync(["17111933790"]); + var result = await _registerRepository.GetContactDetailsAsync(["17111933790"]); var actual = result.FirstOrDefault(e => e.FnumberAk == "17111933790"); var expected = _personContactAndReservationTestData.FirstOrDefault(e => e.FnumberAk == "17111933790"); @@ -64,7 +64,7 @@ public async Task GetUserContactInfoAsync_ReturnsCorrectResults_WhenValidAndInva { // Act var result = _personContactAndReservationTestData.Where(e => e.FnumberAk == "28026698350"); - var expected = await _registerRepository.GetUserContactInfoAsync(["28026698350", "nonexistent2"]); + var expected = await _registerRepository.GetContactDetailsAsync(["28026698350", "nonexistent2"]); // Assert invalid result Assert.Single(result); @@ -75,7 +75,7 @@ public async Task GetUserContactInfoAsync_ReturnsCorrectResults_WhenValidAndInva public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenNoneFound() { // Act - var result = await _registerRepository.GetUserContactInfoAsync(["nonexistent1", "nonexistent2"]); + var result = await _registerRepository.GetContactDetailsAsync(["nonexistent1", "nonexistent2"]); // Assert Assert.Empty(result); @@ -85,7 +85,7 @@ public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenNoneFound() public async Task GetUserContactInfoAsync_ReturnsMultipleContacts_WhenFound() { // Act - var result = await _registerRepository.GetUserContactInfoAsync(["24064316776", "11044314101"]); + var result = await _registerRepository.GetContactDetailsAsync(["24064316776", "11044314101"]); var expected = _personContactAndReservationTestData.Where(e => e.FnumberAk == "24064316776" || e.FnumberAk == "11044314101"); // Assert @@ -103,7 +103,7 @@ public async Task GetUserContactInfoAsync_ReturnsMultipleContacts_WhenFound() public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenNotFound() { // Act - var result = await _registerRepository.GetUserContactInfoAsync(["nonexistent", "11044314120"]); + var result = await _registerRepository.GetContactDetailsAsync(["nonexistent", "11044314120"]); // Assert Assert.Empty(result); From 0c5b8b222a1638ba121f6d69be596ac90d610253 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 12:53:05 +0200 Subject: [PATCH 56/98] Code refactoring --- .../INationalIdentityNumberChecker.cs | 9 ++++ .../Services/IPersonService.cs | 16 +++--- .../Services/NationalIdentityNumberChecker.cs | 24 +++++++-- .../Services/PersonService.cs | 49 ++++++++++--------- .../UseCases/ContactDetailsRetriever.cs | 2 +- .../UserContactDetailsRetrieverTests.cs | 2 +- 6 files changed, 67 insertions(+), 35 deletions(-) diff --git a/src/Altinn.Profile.Integrations/Services/INationalIdentityNumberChecker.cs b/src/Altinn.Profile.Integrations/Services/INationalIdentityNumberChecker.cs index f0f0a2b..413df2e 100644 --- a/src/Altinn.Profile.Integrations/Services/INationalIdentityNumberChecker.cs +++ b/src/Altinn.Profile.Integrations/Services/INationalIdentityNumberChecker.cs @@ -20,6 +20,15 @@ public interface INationalIdentityNumberChecker /// (IImmutableList Valid, IImmutableList Invalid) Categorize(IEnumerable nationalIdentityNumbers); + /// + /// Validates a collection of national identity numbers and returns the valid ones only. + /// + /// A collection of national identity numbers. + /// + /// An immutable list of valid national identity numbers. + /// + IImmutableList GetValid(IEnumerable nationalIdentityNumbers); + /// /// Checks the validity of a single national identity number. /// diff --git a/src/Altinn.Profile.Integrations/Services/IPersonService.cs b/src/Altinn.Profile.Integrations/Services/IPersonService.cs index 581892b..ad93f40 100644 --- a/src/Altinn.Profile.Integrations/Services/IPersonService.cs +++ b/src/Altinn.Profile.Integrations/Services/IPersonService.cs @@ -6,25 +6,25 @@ namespace Altinn.Profile.Integrations.Services; /// -/// Defines a service for handling operations related to user contact information. +/// Defines a service for handling operations related to person data. /// public interface IPersonService { /// - /// Asynchronously retrieves the contact information for a user based on their national identity number. + /// Asynchronously retrieves the contact details for a single person based on their national identity number. /// - /// The national identity number of the user. + /// The national identity number of the person. /// - /// A task that represents the asynchronous operation. The task result contains the user's contact information, or null if not found. + /// A task that represents the asynchronous operation. The task result contains the person's contact details, or null if not found. /// - Task GetUserContactInfoAsync(string nationalIdentityNumber); + Task GetContactDetailsAsync(string nationalIdentityNumber); /// - /// Asynchronously retrieves the contact information for multiple users based on their national identity numbers. + /// Asynchronously retrieves the contact details for multiple persons based on their national identity numbers. /// /// A collection of national identity numbers. /// - /// A task that represents the asynchronous operation. The task result contains a collection of user contact information, or an empty collection if none are found. + /// A task that represents the asynchronous operation. The task result contains a object, where represents the successful lookup result and indicates a failure. /// - Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers); + Task> GetContactDetailsAsync(IEnumerable nationalIdentityNumbers); } diff --git a/src/Altinn.Profile.Integrations/Services/NationalIdentityNumberChecker.cs b/src/Altinn.Profile.Integrations/Services/NationalIdentityNumberChecker.cs index e1e485a..816646d 100644 --- a/src/Altinn.Profile.Integrations/Services/NationalIdentityNumberChecker.cs +++ b/src/Altinn.Profile.Integrations/Services/NationalIdentityNumberChecker.cs @@ -20,12 +20,30 @@ public class NationalIdentityNumberChecker : INationalIdentityNumberChecker /// Invalid: An immutable list of invalid national identity numbers. /// /// + /// Thrown when is null. public (IImmutableList Valid, IImmutableList Invalid) Categorize(IEnumerable nationalIdentityNumbers) { - var validNnationalIdentityNumbers = nationalIdentityNumbers.Where(e => e.IsValidSocialSecurityNumber()).ToImmutableList(); - var invalidNnationalIdentityNumbers = nationalIdentityNumbers.Except(validNnationalIdentityNumbers).ToImmutableList(); + ArgumentNullException.ThrowIfNull(nationalIdentityNumbers); - return (validNnationalIdentityNumbers, invalidNnationalIdentityNumbers); + var validNationalIdentityNumbers = nationalIdentityNumbers.Where(IsValid).ToImmutableList(); + var invalidNationalIdentityNumbers = nationalIdentityNumbers.Except(validNationalIdentityNumbers).ToImmutableList(); + + return (validNationalIdentityNumbers, invalidNationalIdentityNumbers); + } + + /// + /// Validates a collection of national identity numbers and returns the valid ones only. + /// + /// A collection of national identity numbers. + /// + /// An immutable list of valid national identity numbers. + /// + /// Thrown when is null. + public IImmutableList GetValid(IEnumerable nationalIdentityNumbers) + { + ArgumentNullException.ThrowIfNull(nationalIdentityNumbers); + + return nationalIdentityNumbers.Where(IsValid).ToImmutableList(); } /// diff --git a/src/Altinn.Profile.Integrations/Services/PersonService.cs b/src/Altinn.Profile.Integrations/Services/PersonService.cs index f4bb4d6..21d2df0 100644 --- a/src/Altinn.Profile.Integrations/Services/PersonService.cs +++ b/src/Altinn.Profile.Integrations/Services/PersonService.cs @@ -1,4 +1,6 @@ -using System.Collections.Immutable; +#nullable enable + +using System.Collections.Immutable; using Altinn.Profile.Core; using Altinn.Profile.Integrations.Entities; @@ -9,70 +11,73 @@ namespace Altinn.Profile.Integrations.Services; /// -/// Service for handling operations related to user registration and contact points. +/// Provides a service for handling operations related to person data. /// public class PersonService : IPersonService { private readonly IMapper _mapper; - private readonly IPersonRepository _registerRepository; + private readonly IPersonRepository _personRepository; private readonly INationalIdentityNumberChecker _nationalIdentityNumberChecker; /// /// Initializes a new instance of the class. /// /// The mapper used for object mapping. - /// The repository used for accessing register data. + /// The repository used for accessing the person data. /// The service used for checking the validity of national identity numbers. - /// Thrown if , , or is null. - public PersonService(IMapper mapper, IPersonRepository registerRepository, INationalIdentityNumberChecker nationalIdentityNumberChecker) + /// + /// Thrown if , , or is null. + /// + public PersonService(IMapper mapper, IPersonRepository personRepository, INationalIdentityNumberChecker nationalIdentityNumberChecker) { _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); - _registerRepository = registerRepository ?? throw new ArgumentNullException(nameof(registerRepository)); + _personRepository = personRepository ?? throw new ArgumentNullException(nameof(personRepository)); _nationalIdentityNumberChecker = nationalIdentityNumberChecker ?? throw new ArgumentNullException(nameof(nationalIdentityNumberChecker)); } /// - /// Asynchronously retrieves the contact information for a user based on their national identity number. + /// Asynchronously retrieves the contact details for a single person based on their national identity number. /// - /// The national identity number of the user. + /// The national identity number of the person. /// - /// A task that represents the asynchronous operation. The task result contains the user's contact information, or null if not found. + /// A task that represents the asynchronous operation. The task result contains the person's contact details, or null if not found. /// - public async Task GetUserContactInfoAsync(string nationalIdentityNumber) + public async Task GetContactDetailsAsync(string nationalIdentityNumber) { if (!_nationalIdentityNumberChecker.IsValid(nationalIdentityNumber)) { return null; } - var userContactInfoEntity = await _registerRepository.GetContactDetailsAsync([nationalIdentityNumber]); - return _mapper.Map(userContactInfoEntity); + var personContactDetails = await _personRepository.GetContactDetailsAsync([nationalIdentityNumber]); + return _mapper.Map(personContactDetails.FirstOrDefault()); } /// - /// Asynchronously retrieves the contact information for multiple users based on their national identity numbers. + /// Asynchronously retrieves the contact details for multiple persons based on their national identity numbers. /// /// A collection of national identity numbers. /// - /// A task that represents the asynchronous operation. The task result contains a collection of user contact information, or an empty collection if none are found. + /// A task that represents the asynchronous operation. The task result contains a object, where represents the successful lookup result and indicates a failure. /// /// Thrown if is null. - public async Task> GetUserContactAsync(IEnumerable nationalIdentityNumbers) + public async Task> GetContactDetailsAsync(IEnumerable nationalIdentityNumbers) { ArgumentNullException.ThrowIfNull(nationalIdentityNumbers); - var (validnNtionalIdentityNumbers, _) = _nationalIdentityNumberChecker.Categorize(nationalIdentityNumbers); + var validNationalIdentityNumbers = _nationalIdentityNumberChecker.GetValid(nationalIdentityNumbers); + + var matchedContactDetails = await _personRepository.GetContactDetailsAsync(validNationalIdentityNumbers); - var usersContactInfo = await _registerRepository.GetContactDetailsAsync(validnNtionalIdentityNumbers); + var matchedNationalIdentityNumbers = new HashSet(matchedContactDetails.Select(e => e.FnumberAk)); - var matchedUserContact = usersContactInfo.Select(_mapper.Map); + var unmatchedNationalIdentityNumbers = matchedNationalIdentityNumbers.Where(e => !nationalIdentityNumbers.Contains(e)); - var matchedNationalIdentityNumbers = new HashSet(usersContactInfo.Select(e => e.FnumberAk)); - var unmatchedNationalIdentityNumbers = nationalIdentityNumbers.Where(e => !matchedNationalIdentityNumbers.Contains(e)); + var matchedPersonContactDetails = matchedContactDetails.Select(_mapper.Map); return new PersonContactDetailsLookupResult { - MatchedPersonContactDetails = matchedUserContact.ToImmutableList(), + MatchedPersonContactDetails = matchedPersonContactDetails.ToImmutableList(), UnmatchedNationalIdentityNumbers = unmatchedNationalIdentityNumbers.ToImmutableList() }; } diff --git a/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs b/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs index 505a07c..26c35dd 100644 --- a/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs +++ b/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs @@ -45,7 +45,7 @@ public async Task> RetrieveAsync(UserCo return false; } - var contactDetails = await _personService.GetUserContactAsync(lookupCriteria.NationalIdentityNumbers); + var contactDetails = await _personService.GetContactDetailsAsync(lookupCriteria.NationalIdentityNumbers); return contactDetails.Match( MapToContactDetailsLookupResult, diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/UserContactDetailsRetrieverTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/UserContactDetailsRetrieverTests.cs index ae8e0cc..8f3ee25 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/UserContactDetailsRetrieverTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/UserContactDetailsRetrieverTests.cs @@ -55,7 +55,7 @@ public async Task RetrieveAsync_ReturnsFalse_WhenNoContactDetailsFound() NationalIdentityNumbers = new List { "08119043698" } }; - _mockRegisterService.Setup(s => s.GetUserContactAsync(lookupCriteria.NationalIdentityNumbers)).ReturnsAsync(false); + _mockRegisterService.Setup(s => s.GetContactDetailsAsync(lookupCriteria.NationalIdentityNumbers)).ReturnsAsync(false); // Act var result = await _retriever.RetrieveAsync(lookupCriteria); From 2036783d0d1e5a6f4fc6589a2669cc7e56ddda52 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 13:03:24 +0200 Subject: [PATCH 57/98] Remove an unnecessary check for null --- src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs b/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs index 26c35dd..122e97b 100644 --- a/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs +++ b/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs @@ -40,7 +40,7 @@ public async Task> RetrieveAsync(UserCo { ArgumentNullException.ThrowIfNull(lookupCriteria); - if (lookupCriteria?.NationalIdentityNumbers == null || lookupCriteria.NationalIdentityNumbers.Count == 0) + if (lookupCriteria.NationalIdentityNumbers == null || lookupCriteria.NationalIdentityNumbers.Count == 0) { return false; } From ecb80aa035b613958e184ebda00a9db027d6ae14 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 13:04:13 +0200 Subject: [PATCH 58/98] Remove an unnecessary check for null --- src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs b/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs index 122e97b..f352262 100644 --- a/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs +++ b/src/Altinn.Profile/UseCases/ContactDetailsRetriever.cs @@ -86,6 +86,6 @@ private Result MapToContactDetailsLookupResult var matchedContactDetails = lookupResult.MatchedPersonContactDetails?.Select(MapToContactDetails).ToImmutableList(); - return new ContactDetailsLookupResult(matchedContactDetails, lookupResult?.UnmatchedNationalIdentityNumbers); + return new ContactDetailsLookupResult(matchedContactDetails, lookupResult.UnmatchedNationalIdentityNumbers); } } From 104b44610433e858a44ce22adf8526fe1fbc389b Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 13:12:42 +0200 Subject: [PATCH 59/98] Add a simple unit test to test the public endpoint --- .../ContactDetailsControllerTests.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs new file mode 100644 index 0000000..df04bf3 --- /dev/null +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs @@ -0,0 +1,56 @@ +using System.Threading.Tasks; + +using Altinn.Profile.Controllers; +using Altinn.Profile.Models; +using Altinn.Profile.UseCases; + +using Microsoft.AspNetCore.Mvc; + +using Moq; + +using Xunit; + +namespace Altinn.Profile.Tests.IntegrationTests.API.Controllers; + +public class ContactDetailsControllerTests +{ + private readonly Mock _mockContactDetailsRetriever; + private readonly ContactDetailsController _controller; + + public ContactDetailsControllerTests() + { + _mockContactDetailsRetriever = new Mock(); + _controller = new ContactDetailsController(_mockContactDetailsRetriever.Object); + } + + [Fact] + public async Task PostLookup_ReturnsOkResult_WhenSuccessful() + { + // Arrange + var request = new UserContactPointLookup + { + NationalIdentityNumbers = ["27038893837"] + }; + + var contactDetails = new ContactDetails + { + LanguageCode = "nb", + Reservation = false, + MobilePhoneNumber = "12345678", + EmailAddress = "test@example.com", + NationalIdentityNumber = "27038893837", + }; + + var result = new ContactDetailsLookupResult(matchedContactDetails: [contactDetails], unmatchedNationalIdentityNumbers: null); + + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)).ReturnsAsync(result); + + // Act + var response = await _controller.PostLookup(request); + + // Assert + var resultOk = Assert.IsType(response.Result); + var returnValue = Assert.IsType(resultOk.Value); + Assert.Equal(result, returnValue); + } +} From 30f5ceaf29a1b17d4f8d2f204a6039ca32213f40 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 14:20:16 +0200 Subject: [PATCH 60/98] Add a number of unit tests to test the public endpoint --- .../Controllers/ContactDetailsController.cs | 38 +++++- .../ContactDetailsControllerTests.cs | 125 ++++++++++++++++-- 2 files changed, 150 insertions(+), 13 deletions(-) diff --git a/src/Altinn.Profile/Controllers/ContactDetailsController.cs b/src/Altinn.Profile/Controllers/ContactDetailsController.cs index 3520069..704cfbd 100644 --- a/src/Altinn.Profile/Controllers/ContactDetailsController.cs +++ b/src/Altinn.Profile/Controllers/ContactDetailsController.cs @@ -46,10 +46,40 @@ public ContactDetailsController(IContactDetailsRetriever contactDetailsRetriever [ProducesResponseType(typeof(ContactDetailsLookupResult), StatusCodes.Status200OK)] public async Task> PostLookup([FromBody] UserContactPointLookup request) { - var result = await _contactDetailsRetriever.RetrieveAsync(request); + try + { + if (request == null) + { + return BadRequest(); + } - return result.Match>( - success => Ok(success), - failure => Problem("Unable to retrieve contact details.")); + if (request.NationalIdentityNumbers == null) + { + return BadRequest(); + } + + if (request.NationalIdentityNumbers.Count == 0) + { + return BadRequest(); + } + + var result = await _contactDetailsRetriever.RetrieveAsync(request); + + return result.Match>( + success => + { + if (success.MatchedContactDetails.Count != 0) + { + return Ok(success); + } + + return NotFound(); + }, + noMatch => NotFound()); + } + catch + { + return Problem("An unexpected error occurred."); + } } } diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs index df04bf3..29e11ad 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs @@ -1,9 +1,12 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; using Altinn.Profile.Controllers; using Altinn.Profile.Models; using Altinn.Profile.UseCases; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Moq; @@ -14,8 +17,8 @@ namespace Altinn.Profile.Tests.IntegrationTests.API.Controllers; public class ContactDetailsControllerTests { - private readonly Mock _mockContactDetailsRetriever; private readonly ContactDetailsController _controller; + private readonly Mock _mockContactDetailsRetriever; public ContactDetailsControllerTests() { @@ -24,7 +27,106 @@ public ContactDetailsControllerTests() } [Fact] - public async Task PostLookup_ReturnsOkResult_WhenSuccessful() + public async Task PostLookup_ReturnsBadRequestResult_WhenRequestIsInvalid() + { + // Arrange + var invalidRequest = new UserContactPointLookup + { + NationalIdentityNumbers = [] + }; + + // Act + var response = await _controller.PostLookup(invalidRequest); + + // Assert + var badRequestResult = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status400BadRequest, badRequestResult.StatusCode); + } + + [Fact] + public async Task PostLookup_ReturnsInternalServerErrorResult_WhenExceptionOccurs() + { + // Arrange + var request = new UserContactPointLookup + { + NationalIdentityNumbers = ["27038893837"] + }; + + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) + .ThrowsAsync(new System.Exception("Test exception")); + + // Act + var response = await _controller.PostLookup(request); + + // Assert + var problemResult = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status500InternalServerError, problemResult.StatusCode); + } + + [Fact] + public async Task PostLookup_ReturnsMixedResults_WhenOneNumberMatchesAndOneDoesNot() + { + // Arrange + var request = new UserContactPointLookup + { + NationalIdentityNumbers = ["10060339738", "16051327393"] + }; + + var contactDetails = new ContactDetails + { + LanguageCode = "nb", + Reservation = false, + MobilePhoneNumber = "12345678", + EmailAddress = "test@example.com", + NationalIdentityNumber = "10060339738" + }; + + var lookupResult = new ContactDetailsLookupResult( + matchedContactDetails: [contactDetails], + unmatchedNationalIdentityNumbers: ["16051327393"]); + + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) + .ReturnsAsync(lookupResult); + + // Act + var response = await _controller.PostLookup(request); + + // Assert + var result = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status200OK, result.StatusCode); + + var returnValue = Assert.IsType(result.Value); + Assert.Equal(lookupResult, returnValue); + Assert.Single(returnValue.MatchedContactDetails); + Assert.Single(returnValue.UnmatchedNationalIdentityNumbers); + } + + [Fact] + public async Task PostLookup_ReturnsNotFoundResult_WhenNoContactDetailsFound() + { + // Arrange + var request = new UserContactPointLookup + { + NationalIdentityNumbers = ["30083542175"] + }; + + var lookupResult = new ContactDetailsLookupResult( + matchedContactDetails: [], + unmatchedNationalIdentityNumbers: [request.NationalIdentityNumbers[0]]); + + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) + .ReturnsAsync(lookupResult); + + // Act + var response = await _controller.PostLookup(request); + + // Assert + var notFoundResult = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status404NotFound, notFoundResult.StatusCode); + } + + [Fact] + public async Task PostLookup_ReturnsOkResult_WhenRequestIsValid() { // Arrange var request = new UserContactPointLookup @@ -38,19 +140,24 @@ public async Task PostLookup_ReturnsOkResult_WhenSuccessful() Reservation = false, MobilePhoneNumber = "12345678", EmailAddress = "test@example.com", - NationalIdentityNumber = "27038893837", + NationalIdentityNumber = "27038893837" }; - var result = new ContactDetailsLookupResult(matchedContactDetails: [contactDetails], unmatchedNationalIdentityNumbers: null); + var lookupResult = new ContactDetailsLookupResult( + matchedContactDetails: [contactDetails], + unmatchedNationalIdentityNumbers: []); - _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)).ReturnsAsync(result); + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) + .ReturnsAsync(lookupResult); // Act var response = await _controller.PostLookup(request); // Assert - var resultOk = Assert.IsType(response.Result); - var returnValue = Assert.IsType(resultOk.Value); - Assert.Equal(result, returnValue); + var result = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status200OK, result.StatusCode); + + var returnValue = Assert.IsType(result.Value); + Assert.Equal(lookupResult, returnValue); } } From 6c235499614adf8f0b6c9b9531747ef564a9a9af Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 14:37:34 +0200 Subject: [PATCH 61/98] Inject a logger to record information whenever an exception occurs --- .../Controllers/ContactDetailsController.cs | 41 ++++++++----------- .../ContactDetailsControllerTests.cs | 22 +++++++--- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/Altinn.Profile/Controllers/ContactDetailsController.cs b/src/Altinn.Profile/Controllers/ContactDetailsController.cs index 704cfbd..948d9fd 100644 --- a/src/Altinn.Profile/Controllers/ContactDetailsController.cs +++ b/src/Altinn.Profile/Controllers/ContactDetailsController.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; namespace Altinn.Profile.Controllers; @@ -20,15 +21,20 @@ namespace Altinn.Profile.Controllers; [Route("profile/api/v1/contact/details")] public class ContactDetailsController : ControllerBase { + private readonly ILogger _logger; private readonly IContactDetailsRetriever _contactDetailsRetriever; /// /// Initializes a new instance of the class. /// + /// The logger instance used for logging. /// The use case for retrieving the contact details. - /// Thrown when the is null. - public ContactDetailsController(IContactDetailsRetriever contactDetailsRetriever) + /// + /// Thrown when the or is null. + /// + public ContactDetailsController(ILogger logger, IContactDetailsRetriever contactDetailsRetriever) { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _contactDetailsRetriever = contactDetailsRetriever ?? throw new ArgumentNullException(nameof(contactDetailsRetriever)); } @@ -46,39 +52,26 @@ public ContactDetailsController(IContactDetailsRetriever contactDetailsRetriever [ProducesResponseType(typeof(ContactDetailsLookupResult), StatusCodes.Status200OK)] public async Task> PostLookup([FromBody] UserContactPointLookup request) { - try + if (request?.NationalIdentityNumbers == null || request.NationalIdentityNumbers.Count == 0) { - if (request == null) - { - return BadRequest(); - } - - if (request.NationalIdentityNumbers == null) - { - return BadRequest(); - } - - if (request.NationalIdentityNumbers.Count == 0) - { - return BadRequest(); - } + return BadRequest("National identity numbers cannot be null or empty."); + } + try + { var result = await _contactDetailsRetriever.RetrieveAsync(request); return result.Match>( success => { - if (success.MatchedContactDetails.Count != 0) - { - return Ok(success); - } - - return NotFound(); + return success.MatchedContactDetails.Count > 0 ? Ok(success) : NotFound(); }, noMatch => NotFound()); } - catch + catch (Exception ex) { + _logger.LogError(ex, "An error occurred while retrieving contact details."); + return Problem("An unexpected error occurred."); } } diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs index 29e11ad..8391d69 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Collections.Immutable; +using System; using System.Threading.Tasks; using Altinn.Profile.Controllers; @@ -8,6 +7,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Moq; @@ -18,12 +18,14 @@ namespace Altinn.Profile.Tests.IntegrationTests.API.Controllers; public class ContactDetailsControllerTests { private readonly ContactDetailsController _controller; + private readonly Mock> _loggerMock; private readonly Mock _mockContactDetailsRetriever; public ContactDetailsControllerTests() { + _loggerMock = new Mock>(); _mockContactDetailsRetriever = new Mock(); - _controller = new ContactDetailsController(_mockContactDetailsRetriever.Object); + _controller = new ContactDetailsController(_loggerMock.Object, _mockContactDetailsRetriever.Object); } [Fact] @@ -44,7 +46,7 @@ public async Task PostLookup_ReturnsBadRequestResult_WhenRequestIsInvalid() } [Fact] - public async Task PostLookup_ReturnsInternalServerErrorResult_WhenExceptionOccurs() + public async Task PostLookup_ReturnsInternalServerErrorResult_LogError_WhenExceptionOccurs() { // Arrange var request = new UserContactPointLookup @@ -53,14 +55,24 @@ public async Task PostLookup_ReturnsInternalServerErrorResult_WhenExceptionOccur }; _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) - .ThrowsAsync(new System.Exception("Test exception")); + .ThrowsAsync(new Exception("Test exception")); // Act var response = await _controller.PostLookup(request); // Assert var problemResult = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status500InternalServerError, problemResult.StatusCode); + + _loggerMock.Verify( + x => x.Log( + LogLevel.Error, + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("An error occurred while retrieving contact details.")), + It.IsAny(), + It.Is>((v, t) => true)), + Times.Once); } [Fact] From e1fd35063e1d9571beff27654afff67d18e2cf64 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 14:56:32 +0200 Subject: [PATCH 62/98] Rename the unit tests --- .../Controllers/ContactDetailsController.cs | 2 +- .../ContactDetailsControllerTests.cs | 27 +++++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/Altinn.Profile/Controllers/ContactDetailsController.cs b/src/Altinn.Profile/Controllers/ContactDetailsController.cs index 948d9fd..d72b537 100644 --- a/src/Altinn.Profile/Controllers/ContactDetailsController.cs +++ b/src/Altinn.Profile/Controllers/ContactDetailsController.cs @@ -64,7 +64,7 @@ public async Task> PostLookup([FromBody return result.Match>( success => { - return success.MatchedContactDetails.Count > 0 ? Ok(success) : NotFound(); + return success?.MatchedContactDetails?.Count > 0 ? Ok(success) : NotFound(); }, noMatch => NotFound()); } diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs index 8391d69..c152b57 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs @@ -29,7 +29,24 @@ public ContactDetailsControllerTests() } [Fact] - public async Task PostLookup_ReturnsBadRequestResult_WhenRequestIsInvalid() + public async Task PostLookup_ReturnsBadRequest_WhenNationalIdentityNumbersContainInvalidFormat() + { + // Arrange + var invalidRequest = new UserContactPointLookup + { + NationalIdentityNumbers = ["invalid_format"] + }; + + // Act + var response = await _controller.PostLookup(invalidRequest); + + // Assert + var notFoundResult = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status404NotFound, notFoundResult.StatusCode); + } + + [Fact] + public async Task PostLookup_ReturnsBadRequest_WhenRequestIsInvalid() { // Arrange var invalidRequest = new UserContactPointLookup @@ -41,12 +58,12 @@ public async Task PostLookup_ReturnsBadRequestResult_WhenRequestIsInvalid() var response = await _controller.PostLookup(invalidRequest); // Assert - var badRequestResult = Assert.IsType(response.Result); + var badRequestResult = Assert.IsType(response.Result); Assert.Equal(StatusCodes.Status400BadRequest, badRequestResult.StatusCode); } [Fact] - public async Task PostLookup_ReturnsInternalServerErrorResult_LogError_WhenExceptionOccurs() + public async Task PostLookup_ReturnsInternalServerError_AndLogsError_WhenExceptionOccurs() { // Arrange var request = new UserContactPointLookup @@ -114,7 +131,7 @@ public async Task PostLookup_ReturnsMixedResults_WhenOneNumberMatchesAndOneDoesN } [Fact] - public async Task PostLookup_ReturnsNotFoundResult_WhenNoContactDetailsFound() + public async Task PostLookup_ReturnsNotFound_WhenNoContactDetailsFound() { // Arrange var request = new UserContactPointLookup @@ -138,7 +155,7 @@ public async Task PostLookup_ReturnsNotFoundResult_WhenNoContactDetailsFound() } [Fact] - public async Task PostLookup_ReturnsOkResult_WhenRequestIsValid() + public async Task PostLookup_ReturnsOk_WhenRequestIsValid() { // Arrange var request = new UserContactPointLookup From 571398934def2a176e32a75d6aefa3927827535a Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 15:11:02 +0200 Subject: [PATCH 63/98] Check the error message --- .../API/Controllers/ContactDetailsControllerTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs index c152b57..eb20de4 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs @@ -43,6 +43,7 @@ public async Task PostLookup_ReturnsBadRequest_WhenNationalIdentityNumbersContai // Assert var notFoundResult = Assert.IsType(response.Result); Assert.Equal(StatusCodes.Status404NotFound, notFoundResult.StatusCode); + Assert.Null(response.Value); } [Fact] @@ -60,6 +61,8 @@ public async Task PostLookup_ReturnsBadRequest_WhenRequestIsInvalid() // Assert var badRequestResult = Assert.IsType(response.Result); Assert.Equal(StatusCodes.Status400BadRequest, badRequestResult.StatusCode); + Assert.Equal("National identity numbers cannot be null or empty.", badRequestResult.Value); + Assert.Null(response.Value); } [Fact] From d7970b65e1409cdcf1c735b7955658e360c11348 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 15:17:43 +0200 Subject: [PATCH 64/98] Implement a single unit test to ensure that the controller does not block unnecessarily by mocking long-running tasks. --- .../ContactDetailsControllerTests.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs index eb20de4..ea0792e 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Immutable; using System.Threading.Tasks; using Altinn.Profile.Controllers; @@ -28,6 +29,46 @@ public ContactDetailsControllerTests() _controller = new ContactDetailsController(_loggerMock.Object, _mockContactDetailsRetriever.Object); } + [Fact] + public async Task PostLookup_DoesNotBlock_WhenServiceCallIsLongRunning() + { + // Arrange + var request = new UserContactPointLookup + { + NationalIdentityNumbers = ["02038112735"] + }; + + var contactDetails = new ContactDetails + { + LanguageCode = "nb", + Reservation = false, + MobilePhoneNumber = "12345678", + EmailAddress = "test@example.com", + NationalIdentityNumber = "02038112735" + }; + + var lookupResult = new ContactDetailsLookupResult( + matchedContactDetails: [contactDetails], + unmatchedNationalIdentityNumbers: []); + + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) + .ReturnsAsync(() => + { + Task.Delay(5000).Wait(); // Simulate long-running task + return lookupResult; + }); + + // Act + var response = await _controller.PostLookup(request); + + // Assert + var result = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status200OK, result.StatusCode); + + var returnValue = Assert.IsType(result.Value); + Assert.Equal(lookupResult, returnValue); + } + [Fact] public async Task PostLookup_ReturnsBadRequest_WhenNationalIdentityNumbersContainInvalidFormat() { From 38640d411fa74365b37cbe17e6792defaca42842 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 16:33:23 +0200 Subject: [PATCH 65/98] Implement unit tests to verify the functionality of the class responsible for validating national identity numbers --- .../NationalIdentityNumberCheckerTests.cs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 test/Altinn.Profile.Tests/Profile.Integrations/NationalIdentityNumberCheckerTests.cs diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/NationalIdentityNumberCheckerTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/NationalIdentityNumberCheckerTests.cs new file mode 100644 index 0000000..5306f1d --- /dev/null +++ b/test/Altinn.Profile.Tests/Profile.Integrations/NationalIdentityNumberCheckerTests.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; + +using Altinn.Profile.Integrations.Services; + +using Xunit; + +namespace Altinn.Profile.Tests.Profile.Integrations; + +public class NationalIdentityNumberCheckerTests +{ + private readonly NationalIdentityNumberChecker _checker; + + public NationalIdentityNumberCheckerTests() + { + _checker = new NationalIdentityNumberChecker(); + } + + [Fact] + public void Categorize_NullInput_ThrowsArgumentNullException() + { + Assert.Throws(() => _checker.Categorize(null)); + } + + [Fact] + public void Categorize_ValidAndInvalidNumbers_ReturnsCorrectCategorization() + { + var input = new List { "26050711071", "invalid_number", "06010190515" }; + var (valid, invalid) = _checker.Categorize(input); + + Assert.Single(invalid); + Assert.Equal(2, valid.Count); + Assert.Contains("26050711071", valid); + Assert.Contains("06010190515", valid); + Assert.Contains("invalid_number", invalid); + } + + [Fact] + public void GetValid_NullInput_ThrowsArgumentNullException() + { + Assert.Throws(() => _checker.GetValid(null)); + } + + [Fact] + public void GetValid_ValidAndInvalidNumbers_ReturnsOnlyValidNumbers() + { + var input = new List { "05112908325", "031IN0918959", "03110918959" }; + var result = _checker.GetValid(input); + + Assert.Equal(2, result.Count); + Assert.Contains("05112908325", result); + Assert.Contains("03110918959", result); + } + + [Fact] + public void IsValid_ValidNumber_ReturnsTrue() + { + var result = _checker.IsValid("08033201398"); + Assert.True(result); + } + + [Fact] + public void IsValid_InvalidNumber_ReturnsFalse() + { + var result = _checker.IsValid("080332Q1398"); + Assert.False(result); + } +} From 89ba7b9be7140a0d36d94c682989501f1c94cb68 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 16:49:19 +0200 Subject: [PATCH 66/98] Implement unit tests to test retrieval of contact details --- .../Register/PersonServiceTests.cs | 123 ++++++++++++++++++ .../Register/RegisterServiceTests.cs | 24 ---- 2 files changed, 123 insertions(+), 24 deletions(-) create mode 100644 test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs delete mode 100644 test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs new file mode 100644 index 0000000..2105a7b --- /dev/null +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs @@ -0,0 +1,123 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; + +using Altinn.Profile.Integrations.Entities; +using Altinn.Profile.Integrations.Repositories; +using Altinn.Profile.Integrations.Services; + +using AutoMapper; + +using Moq; + +using Xunit; + +namespace Altinn.Profile.Tests.Profile.Integrations; + +public class PersonServiceTests +{ + private readonly Mock _mapperMock; + private readonly Mock _personRepositoryMock; + private readonly Mock _nationalIdentityNumberCheckerMock; + private readonly PersonService _personService; + + public PersonServiceTests() + { + _mapperMock = new Mock(); + _personRepositoryMock = new Mock(); + _nationalIdentityNumberCheckerMock = new Mock(); + _personService = new PersonService(_mapperMock.Object, _personRepositoryMock.Object, _nationalIdentityNumberCheckerMock.Object); + } + + [Fact] + public async Task GetContactDetailsAsync_InvalidNationalIdentityNumber_ReturnsNull() + { + var invalidNIN = "invalid_number"; + + _nationalIdentityNumberCheckerMock + .Setup(x => x.IsValid(invalidNIN)) + .Returns(false); + + var result = await _personService.GetContactDetailsAsync(invalidNIN); + + Assert.Null(result); + _personRepositoryMock.Verify(x => x.GetContactDetailsAsync(It.IsAny>()), Times.Never); + } + + [Fact] + public async Task GetContactDetailsAsync_ValidNationalIdentityNumber_ReturnsContactDetails() + { + var validNIN = "12345678901"; + var mockPerson = new Person { FnumberAk = validNIN }; + var personList = new List { mockPerson }.ToImmutableList(); + var mappedContactDetail = new Mock(); + + _nationalIdentityNumberCheckerMock + .Setup(x => x.IsValid(validNIN)) + .Returns(true); + _personRepositoryMock + .Setup(x => x.GetContactDetailsAsync(It.IsAny>())) + .ReturnsAsync(personList); + + _mapperMock + .Setup(x => x.Map(It.IsAny())) + .Returns(mappedContactDetail.Object); + + var result = await _personService.GetContactDetailsAsync(validNIN); + + Assert.NotNull(result); + Assert.Equal(mappedContactDetail.Object, result); + } + + [Fact] + public async Task GetContactDetailsAsync_MultipleNationalIdentityNumbers_ReturnsCorrectResult() + { + var nationalIdentityNumbers = new List { "12028193007", "01091235338", "invalid_number" }; + var validNationalIdentityNumbers = new List { "12028193007", "01091235338" }; + + var mockPerson1 = new Person { FnumberAk = "12028193007" }; + var mockPerson2 = new Person { FnumberAk = "01091235338" }; + + var personList = new List { mockPerson1, mockPerson2 }.ToImmutableList(); + + var mappedContactDetail1 = new Mock(); + var mappedContactDetail2 = new Mock(); + + _nationalIdentityNumberCheckerMock + .Setup(x => x.GetValid(nationalIdentityNumbers)) + .Returns(validNationalIdentityNumbers.ToImmutableList()); + _personRepositoryMock + .Setup(x => x.GetContactDetailsAsync(validNationalIdentityNumbers)) + .ReturnsAsync(personList); + + _mapperMock.Setup(x => x.Map(mockPerson1)) + .Returns(mappedContactDetail1.Object); + _mapperMock.Setup(x => x.Map(mockPerson2)) + .Returns(mappedContactDetail2.Object); + + var result = await _personService.GetContactDetailsAsync(nationalIdentityNumbers); + IEnumerable? unmatchedNationalIdentityNumbers = []; + IEnumerable? matchedPersonContactDetails = []; + + result.Match( + success => + { + matchedPersonContactDetails = success.MatchedPersonContactDetails; + unmatchedNationalIdentityNumbers = success.UnmatchedNationalIdentityNumbers; + }, + failure => + { + matchedPersonContactDetails = null; + }); + + Assert.Equal(2, matchedPersonContactDetails.Count()); + + Assert.Contains(matchedPersonContactDetails, detail => detail == mappedContactDetail1.Object); + Assert.Contains(matchedPersonContactDetails, detail => detail == mappedContactDetail2.Object); + Assert.Empty(unmatchedNationalIdentityNumbers); + } +} diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs deleted file mode 100644 index 31df335..0000000 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterServiceTests.cs +++ /dev/null @@ -1,24 +0,0 @@ -#nullable enable - -using Altinn.Profile.Integrations.Repositories; -using Altinn.Profile.Integrations.Services; - -using AutoMapper; - -using Moq; - -namespace Altinn.Profile.Tests.Profile.Integrations; - -public class RegisterServiceTests -{ - private readonly Mock _mockRegisterRepository; - private readonly Mock _mockNationalIdentityNumberChecker; - private readonly Mock _mockMapper; - - public RegisterServiceTests() - { - _mockMapper = new Mock(); - _mockRegisterRepository = new Mock(); - _mockNationalIdentityNumberChecker = new Mock(); - } -} From 86babbd596def7f5f5d1307630d0cd90443f24e1 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 16:59:11 +0200 Subject: [PATCH 67/98] Update the internal endpoint to produce the same result as the external one --- .../ContactDetailsInternalController.cs | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs b/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs index 28b56a0..da8fff9 100644 --- a/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs +++ b/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; namespace Altinn.Profile.Controllers; @@ -20,15 +21,18 @@ namespace Altinn.Profile.Controllers; [Route("profile/api/v1/internal/contact/details")] public class ContactDetailsInternalController : ControllerBase { + private readonly ILogger _logger; private readonly IContactDetailsRetriever _contactDetailsRetriever; /// /// Initializes a new instance of the class. /// + /// The logger instance used for logging. /// The use case for retrieving the contact details. /// Thrown when the is null. - public ContactDetailsInternalController(IContactDetailsRetriever contactDetailsRetriever) + public ContactDetailsInternalController(ILogger logger, IContactDetailsRetriever contactDetailsRetriever) { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _contactDetailsRetriever = contactDetailsRetriever ?? throw new ArgumentNullException(nameof(contactDetailsRetriever)); } @@ -47,10 +51,27 @@ public ContactDetailsInternalController(IContactDetailsRetriever contactDetailsR [ProducesResponseType(typeof(ContactDetailsLookupResult), StatusCodes.Status200OK)] public async Task> PostLookup([FromBody] UserContactPointLookup request) { - var result = await _contactDetailsRetriever.RetrieveAsync(request); + if (request?.NationalIdentityNumbers == null || request.NationalIdentityNumbers.Count == 0) + { + return BadRequest("National identity numbers cannot be null or empty."); + } - return result.Match>( - success => Ok(success), - failure => Problem("Unable to retrieve contact details.")); + try + { + var result = await _contactDetailsRetriever.RetrieveAsync(request); + + return result.Match>( + success => + { + return success?.MatchedContactDetails?.Count > 0 ? Ok(success) : NotFound(); + }, + noMatch => NotFound()); + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred while retrieving contact details."); + + return Problem("An unexpected error occurred."); + } } } From 39993440f50ae5c513547d21e323e5e9a45f3634 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 16:59:47 +0200 Subject: [PATCH 68/98] Fix a typo --- .../Controllers/ContactDetailsInternalController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs b/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs index da8fff9..c2b6de3 100644 --- a/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs +++ b/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs @@ -21,7 +21,7 @@ namespace Altinn.Profile.Controllers; [Route("profile/api/v1/internal/contact/details")] public class ContactDetailsInternalController : ControllerBase { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IContactDetailsRetriever _contactDetailsRetriever; /// @@ -30,7 +30,7 @@ public class ContactDetailsInternalController : ControllerBase /// The logger instance used for logging. /// The use case for retrieving the contact details. /// Thrown when the is null. - public ContactDetailsInternalController(ILogger logger, IContactDetailsRetriever contactDetailsRetriever) + public ContactDetailsInternalController(ILogger logger, IContactDetailsRetriever contactDetailsRetriever) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _contactDetailsRetriever = contactDetailsRetriever ?? throw new ArgumentNullException(nameof(contactDetailsRetriever)); From 53106215214d1c8133cb063a5633f75593ce30d5 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 17:04:15 +0200 Subject: [PATCH 69/98] Add three new unit tests --- .../ContactDetailsControllerTests.cs | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs index ea0792e..5d39b94 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Immutable; using System.Threading.Tasks; using Altinn.Profile.Controllers; @@ -29,6 +28,40 @@ public ContactDetailsControllerTests() _controller = new ContactDetailsController(_loggerMock.Object, _mockContactDetailsRetriever.Object); } + [Fact] + public void Constructor_NullLogger_ThrowsArgumentNullException() + { + // Arrange + var contactDetailsRetrieverMock = new Mock(); + + // Act & Assert + Assert.Throws(() => new ContactDetailsController(null, contactDetailsRetrieverMock.Object)); + } + + [Fact] + public void Constructor_NullContactDetailsRetriever_ThrowsArgumentNullException() + { + // Arrange + var loggerMock = new Mock>(); + + // Act & Assert + Assert.Throws(() => new ContactDetailsController(loggerMock.Object, null)); + } + + [Fact] + public void Constructor_ValidParameters_InitializesCorrectly() + { + // Arrange + var loggerMock = new Mock>(); + var contactDetailsRetrieverMock = new Mock(); + + // Act + var controller = new ContactDetailsController(loggerMock.Object, contactDetailsRetrieverMock.Object); + + // Assert + Assert.NotNull(controller); + } + [Fact] public async Task PostLookup_DoesNotBlock_WhenServiceCallIsLongRunning() { From 3df44e6bbdd7ba6ff9372c1d1f5a66074a26659c Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 17:11:32 +0200 Subject: [PATCH 70/98] Copy some unit tests from the external controller to the internal one --- .../ContactDetailsInternalControllerTests.cs | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs new file mode 100644 index 0000000..66dd46e --- /dev/null +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs @@ -0,0 +1,102 @@ +using System; +using System.Threading.Tasks; + +using Altinn.Profile.Controllers; +using Altinn.Profile.Models; +using Altinn.Profile.UseCases; + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +using Moq; + +using Xunit; + +namespace Altinn.Profile.Tests.IntegrationTests.API.Controllers; + +public class ContactDetailsInternalControllerTests +{ + private readonly ContactDetailsInternalController _controller; + private readonly Mock> _loggerMock; + private readonly Mock _mockContactDetailsRetriever; + + public ContactDetailsInternalControllerTests() + { + _loggerMock = new Mock>(); + _mockContactDetailsRetriever = new Mock(); + _controller = new ContactDetailsInternalController(_loggerMock.Object, _mockContactDetailsRetriever.Object); + } + + [Fact] + public void Constructor_NullLogger_ThrowsArgumentNullException() + { + // Arrange + var contactDetailsRetrieverMock = new Mock(); + + // Act & Assert + Assert.Throws(() => new ContactDetailsInternalController(null, contactDetailsRetrieverMock.Object)); + } + + [Fact] + public void Constructor_NullContactDetailsRetriever_ThrowsArgumentNullException() + { + // Arrange + var loggerMock = new Mock>(); + + // Act & Assert + Assert.Throws(() => new ContactDetailsInternalController(loggerMock.Object, null)); + } + + [Fact] + public void Constructor_ValidParameters_InitializesCorrectly() + { + // Arrange + var loggerMock = new Mock>(); + var contactDetailsRetrieverMock = new Mock(); + + // Act + var controller = new ContactDetailsInternalController(loggerMock.Object, contactDetailsRetrieverMock.Object); + + // Assert + Assert.NotNull(controller); + } + + [Fact] + public async Task PostLookup_ReturnsMixedResults_WhenOneNumberMatchesAndOneDoesNot() + { + // Arrange + var request = new UserContactPointLookup + { + NationalIdentityNumbers = ["10060339738", "16051327393"] + }; + + var contactDetails = new ContactDetails + { + LanguageCode = "nb", + Reservation = false, + MobilePhoneNumber = "12345678", + EmailAddress = "test@example.com", + NationalIdentityNumber = "10060339738" + }; + + var lookupResult = new ContactDetailsLookupResult( + matchedContactDetails: [contactDetails], + unmatchedNationalIdentityNumbers: ["16051327393"]); + + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) + .ReturnsAsync(lookupResult); + + // Act + var response = await _controller.PostLookup(request); + + // Assert + var result = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status200OK, result.StatusCode); + + var returnValue = Assert.IsType(result.Value); + Assert.Equal(lookupResult, returnValue); + Assert.Single(returnValue.MatchedContactDetails); + Assert.Single(returnValue.UnmatchedNationalIdentityNumbers); + } +} From a1c093823afd81493425982fa251a43af138d5f3 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 17:12:19 +0200 Subject: [PATCH 71/98] Change the national identity number --- .../Controllers/ContactDetailsInternalControllerTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs index 66dd46e..e0d145d 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs @@ -68,7 +68,7 @@ public async Task PostLookup_ReturnsMixedResults_WhenOneNumberMatchesAndOneDoesN // Arrange var request = new UserContactPointLookup { - NationalIdentityNumbers = ["10060339738", "16051327393"] + NationalIdentityNumbers = ["05025308508", "08110270527"] }; var contactDetails = new ContactDetails @@ -77,12 +77,12 @@ public async Task PostLookup_ReturnsMixedResults_WhenOneNumberMatchesAndOneDoesN Reservation = false, MobilePhoneNumber = "12345678", EmailAddress = "test@example.com", - NationalIdentityNumber = "10060339738" + NationalIdentityNumber = "05025308508" }; var lookupResult = new ContactDetailsLookupResult( matchedContactDetails: [contactDetails], - unmatchedNationalIdentityNumbers: ["16051327393"]); + unmatchedNationalIdentityNumbers: ["08110270527"]); _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) .ReturnsAsync(lookupResult); From 72e15e0f0b181494c1d2c0c32248c07f7bbf7359 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 17:18:31 +0200 Subject: [PATCH 72/98] Try to avoid duplicates --- .../Controllers/ContactDetailsController.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Altinn.Profile/Controllers/ContactDetailsController.cs b/src/Altinn.Profile/Controllers/ContactDetailsController.cs index d72b537..56ae926 100644 --- a/src/Altinn.Profile/Controllers/ContactDetailsController.cs +++ b/src/Altinn.Profile/Controllers/ContactDetailsController.cs @@ -41,7 +41,7 @@ public ContactDetailsController(ILogger logger, IConta /// /// Retrieves the contact details for persons based on their national identity numbers. /// - /// A collection of national identity numbers. + /// A collection of national identity numbers. /// /// A task that represents the asynchronous operation, containing a response with persons' contact details. /// Returns a with status 200 OK if successful. @@ -50,23 +50,23 @@ public ContactDetailsController(ILogger logger, IConta [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ContactDetailsLookupResult), StatusCodes.Status200OK)] - public async Task> PostLookup([FromBody] UserContactPointLookup request) + public async Task> PostLookup([FromBody] UserContactPointLookup lookupCriteria) { - if (request?.NationalIdentityNumbers == null || request.NationalIdentityNumbers.Count == 0) + if (lookupCriteria?.NationalIdentityNumbers == null || lookupCriteria.NationalIdentityNumbers.Count == 0) { return BadRequest("National identity numbers cannot be null or empty."); } try { - var result = await _contactDetailsRetriever.RetrieveAsync(request); + var lookupResult = await _contactDetailsRetriever.RetrieveAsync(lookupCriteria); - return result.Match>( - success => + return lookupResult.Match>( + successResponse => { - return success?.MatchedContactDetails?.Count > 0 ? Ok(success) : NotFound(); + return successResponse?.MatchedContactDetails?.Count > 0 ? Ok(successResponse) : NotFound(); }, - noMatch => NotFound()); + failedResponse => NotFound()); } catch (Exception ex) { From 23411a1690a059c6fbb45a390873e36497be5015 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Tue, 8 Oct 2024 17:26:06 +0200 Subject: [PATCH 73/98] Check model validity --- src/Altinn.Profile/Controllers/ContactDetailsController.cs | 5 +++++ .../Controllers/ContactDetailsInternalController.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/Altinn.Profile/Controllers/ContactDetailsController.cs b/src/Altinn.Profile/Controllers/ContactDetailsController.cs index 56ae926..15bf647 100644 --- a/src/Altinn.Profile/Controllers/ContactDetailsController.cs +++ b/src/Altinn.Profile/Controllers/ContactDetailsController.cs @@ -52,6 +52,11 @@ public ContactDetailsController(ILogger logger, IConta [ProducesResponseType(typeof(ContactDetailsLookupResult), StatusCodes.Status200OK)] public async Task> PostLookup([FromBody] UserContactPointLookup lookupCriteria) { + if (ModelState.IsValid) + { + return BadRequest(ModelState); + } + if (lookupCriteria?.NationalIdentityNumbers == null || lookupCriteria.NationalIdentityNumbers.Count == 0) { return BadRequest("National identity numbers cannot be null or empty."); diff --git a/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs b/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs index c2b6de3..3dea06f 100644 --- a/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs +++ b/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs @@ -51,6 +51,11 @@ public ContactDetailsInternalController(ILogger> PostLookup([FromBody] UserContactPointLookup request) { + if (ModelState.IsValid) + { + return BadRequest(ModelState); + } + if (request?.NationalIdentityNumbers == null || request.NationalIdentityNumbers.Count == 0) { return BadRequest("National identity numbers cannot be null or empty."); From b3c9ff63f6604f75f2e58b9aa09ffa3d811d475f Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 09:57:02 +0200 Subject: [PATCH 74/98] Implement two unit tests to verify the outcome when the data model is invalid. --- .../Controllers/ContactDetailsController.cs | 2 +- .../ContactDetailsInternalController.cs | 2 +- .../ContactDetailsControllerTests.cs | 19 +++++++++++++++++++ .../ContactDetailsInternalControllerTests.cs | 19 +++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Altinn.Profile/Controllers/ContactDetailsController.cs b/src/Altinn.Profile/Controllers/ContactDetailsController.cs index 15bf647..8a5e235 100644 --- a/src/Altinn.Profile/Controllers/ContactDetailsController.cs +++ b/src/Altinn.Profile/Controllers/ContactDetailsController.cs @@ -52,7 +52,7 @@ public ContactDetailsController(ILogger logger, IConta [ProducesResponseType(typeof(ContactDetailsLookupResult), StatusCodes.Status200OK)] public async Task> PostLookup([FromBody] UserContactPointLookup lookupCriteria) { - if (ModelState.IsValid) + if (!ModelState.IsValid) { return BadRequest(ModelState); } diff --git a/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs b/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs index 3dea06f..1209689 100644 --- a/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs +++ b/src/Altinn.Profile/Controllers/ContactDetailsInternalController.cs @@ -51,7 +51,7 @@ public ContactDetailsInternalController(ILogger> PostLookup([FromBody] UserContactPointLookup request) { - if (ModelState.IsValid) + if (!ModelState.IsValid) { return BadRequest(ModelState); } diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs index 5d39b94..fef9e9f 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs @@ -102,6 +102,25 @@ public async Task PostLookup_DoesNotBlock_WhenServiceCallIsLongRunning() Assert.Equal(lookupResult, returnValue); } + [Fact] + public async Task PostLookup_ReturnsBadRequest_WhenModelStateIsInvalid() + { + // Arrange + var invalidRequest = new UserContactPointLookup + { + NationalIdentityNumbers = ["14078112078"] + }; + + _controller.ModelState.AddModelError("InvalidKey", "Invalid error message"); + + // Act + var response = await _controller.PostLookup(invalidRequest); + + // Assert + var badRequestResult = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status400BadRequest, badRequestResult.StatusCode); + } + [Fact] public async Task PostLookup_ReturnsBadRequest_WhenNationalIdentityNumbersContainInvalidFormat() { diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs index e0d145d..ae4ac78 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs @@ -62,6 +62,25 @@ public void Constructor_ValidParameters_InitializesCorrectly() Assert.NotNull(controller); } + [Fact] + public async Task PostLookup_ReturnsBadRequest_WhenModelStateIsInvalid() + { + // Arrange + var request = new UserContactPointLookup + { + NationalIdentityNumbers = ["17092037169"] + }; + + _controller.ModelState.AddModelError("TestError", "Invalid model!"); + + // Act + var response = await _controller.PostLookup(request); + + // Assert + var badRequestResult = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status400BadRequest, badRequestResult.StatusCode); + } + [Fact] public async Task PostLookup_ReturnsMixedResults_WhenOneNumberMatchesAndOneDoesNot() { From 92d2d99083673907d707e6bc831ddc14b0f618b6 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 10:13:01 +0200 Subject: [PATCH 75/98] Rename the unit tests --- .../ContactDetailsControllerTests.cs | 134 +++++++++--------- .../ContactDetailsInternalControllerTests.cs | 20 +-- 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs index fef9e9f..004508b 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs @@ -29,27 +29,27 @@ public ContactDetailsControllerTests() } [Fact] - public void Constructor_NullLogger_ThrowsArgumentNullException() + public void Constructor_WithNullContactDetailsRetriever_ThrowsArgumentNullException() { // Arrange - var contactDetailsRetrieverMock = new Mock(); + var loggerMock = new Mock>(); // Act & Assert - Assert.Throws(() => new ContactDetailsController(null, contactDetailsRetrieverMock.Object)); + Assert.Throws(() => new ContactDetailsController(loggerMock.Object, null)); } [Fact] - public void Constructor_NullContactDetailsRetriever_ThrowsArgumentNullException() + public void Constructor_WithNullLogger_ThrowsArgumentNullException() { // Arrange - var loggerMock = new Mock>(); + var contactDetailsRetrieverMock = new Mock(); // Act & Assert - Assert.Throws(() => new ContactDetailsController(loggerMock.Object, null)); + Assert.Throws(() => new ContactDetailsController(null, contactDetailsRetrieverMock.Object)); } [Fact] - public void Constructor_ValidParameters_InitializesCorrectly() + public void Constructor_WithValidParameters_InitializesCorrectly() { // Arrange var loggerMock = new Mock>(); @@ -63,7 +63,61 @@ public void Constructor_ValidParameters_InitializesCorrectly() } [Fact] - public async Task PostLookup_DoesNotBlock_WhenServiceCallIsLongRunning() + public async Task PostLookup_WhenExceptionOccurs_ReturnsInternalServerError_And_LogsError() + { + // Arrange + var request = new UserContactPointLookup + { + NationalIdentityNumbers = ["27038893837"] + }; + + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) + .ThrowsAsync(new Exception("Test exception")); + + // Act + var response = await _controller.PostLookup(request); + + // Assert + var problemResult = Assert.IsType(response.Result); + + Assert.Equal(StatusCodes.Status500InternalServerError, problemResult.StatusCode); + + _loggerMock.Verify( + x => x.Log( + LogLevel.Error, + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("An error occurred while retrieving contact details.")), + It.IsAny(), + It.Is>((v, t) => true)), + Times.Once); + } + + [Fact] + public async Task PostLookup_WhenNoContactDetailsFound_ReturnsNotFound() + { + // Arrange + var request = new UserContactPointLookup + { + NationalIdentityNumbers = ["30083542175"] + }; + + var lookupResult = new ContactDetailsLookupResult( + matchedContactDetails: [], + unmatchedNationalIdentityNumbers: [request.NationalIdentityNumbers[0]]); + + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) + .ReturnsAsync(lookupResult); + + // Act + var response = await _controller.PostLookup(request); + + // Assert + var notFoundResult = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status404NotFound, notFoundResult.StatusCode); + } + + [Fact] + public async Task PostLookup_WhenServiceCallIsLongRunning_DoesNotBlock() { // Arrange var request = new UserContactPointLookup @@ -103,7 +157,7 @@ public async Task PostLookup_DoesNotBlock_WhenServiceCallIsLongRunning() } [Fact] - public async Task PostLookup_ReturnsBadRequest_WhenModelStateIsInvalid() + public async Task PostLookup_WithInvalidModelState_ReturnsBadRequest() { // Arrange var invalidRequest = new UserContactPointLookup @@ -122,7 +176,7 @@ public async Task PostLookup_ReturnsBadRequest_WhenModelStateIsInvalid() } [Fact] - public async Task PostLookup_ReturnsBadRequest_WhenNationalIdentityNumbersContainInvalidFormat() + public async Task PostLookup_WithInvalidSingleNationalIdentityNumber_ReturnsBadRequest() { // Arrange var invalidRequest = new UserContactPointLookup @@ -140,7 +194,7 @@ public async Task PostLookup_ReturnsBadRequest_WhenNationalIdentityNumbersContai } [Fact] - public async Task PostLookup_ReturnsBadRequest_WhenRequestIsInvalid() + public async Task PostLookup_WithNoNationalIdentityNumbers_ReturnsBadRequest() { // Arrange var invalidRequest = new UserContactPointLookup @@ -159,37 +213,7 @@ public async Task PostLookup_ReturnsBadRequest_WhenRequestIsInvalid() } [Fact] - public async Task PostLookup_ReturnsInternalServerError_AndLogsError_WhenExceptionOccurs() - { - // Arrange - var request = new UserContactPointLookup - { - NationalIdentityNumbers = ["27038893837"] - }; - - _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) - .ThrowsAsync(new Exception("Test exception")); - - // Act - var response = await _controller.PostLookup(request); - - // Assert - var problemResult = Assert.IsType(response.Result); - - Assert.Equal(StatusCodes.Status500InternalServerError, problemResult.StatusCode); - - _loggerMock.Verify( - x => x.Log( - LogLevel.Error, - It.IsAny(), - It.Is((v, t) => v.ToString().Contains("An error occurred while retrieving contact details.")), - It.IsAny(), - It.Is>((v, t) => true)), - Times.Once); - } - - [Fact] - public async Task PostLookup_ReturnsMixedResults_WhenOneNumberMatchesAndOneDoesNot() + public async Task PostLookup_WithMixedNationalIdentityNumbers_ReturnsMixedResults() { // Arrange var request = new UserContactPointLookup @@ -227,31 +251,7 @@ public async Task PostLookup_ReturnsMixedResults_WhenOneNumberMatchesAndOneDoesN } [Fact] - public async Task PostLookup_ReturnsNotFound_WhenNoContactDetailsFound() - { - // Arrange - var request = new UserContactPointLookup - { - NationalIdentityNumbers = ["30083542175"] - }; - - var lookupResult = new ContactDetailsLookupResult( - matchedContactDetails: [], - unmatchedNationalIdentityNumbers: [request.NationalIdentityNumbers[0]]); - - _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) - .ReturnsAsync(lookupResult); - - // Act - var response = await _controller.PostLookup(request); - - // Assert - var notFoundResult = Assert.IsType(response.Result); - Assert.Equal(StatusCodes.Status404NotFound, notFoundResult.StatusCode); - } - - [Fact] - public async Task PostLookup_ReturnsOk_WhenRequestIsValid() + public async Task PostLookup_WithValidRequest_ReturnsOk() { // Arrange var request = new UserContactPointLookup diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs index ae4ac78..81daaeb 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs @@ -29,27 +29,27 @@ public ContactDetailsInternalControllerTests() } [Fact] - public void Constructor_NullLogger_ThrowsArgumentNullException() + public void Constructor_WithNullContactDetailsRetriever_ThrowsArgumentNullException() { // Arrange - var contactDetailsRetrieverMock = new Mock(); + var loggerMock = new Mock>(); // Act & Assert - Assert.Throws(() => new ContactDetailsInternalController(null, contactDetailsRetrieverMock.Object)); + Assert.Throws(() => new ContactDetailsInternalController(loggerMock.Object, null)); } [Fact] - public void Constructor_NullContactDetailsRetriever_ThrowsArgumentNullException() + public void Constructor_WithNullLogger_ThrowsArgumentNullException() { // Arrange - var loggerMock = new Mock>(); + var contactDetailsRetrieverMock = new Mock(); // Act & Assert - Assert.Throws(() => new ContactDetailsInternalController(loggerMock.Object, null)); + Assert.Throws(() => new ContactDetailsInternalController(null, contactDetailsRetrieverMock.Object)); } [Fact] - public void Constructor_ValidParameters_InitializesCorrectly() + public void Constructor_WithValidParameters_InitializesCorrectly() { // Arrange var loggerMock = new Mock>(); @@ -63,7 +63,7 @@ public void Constructor_ValidParameters_InitializesCorrectly() } [Fact] - public async Task PostLookup_ReturnsBadRequest_WhenModelStateIsInvalid() + public async Task PostLookup_WithInvalidModelState_ReturnsBadRequest() { // Arrange var request = new UserContactPointLookup @@ -71,7 +71,7 @@ public async Task PostLookup_ReturnsBadRequest_WhenModelStateIsInvalid() NationalIdentityNumbers = ["17092037169"] }; - _controller.ModelState.AddModelError("TestError", "Invalid model!"); + _controller.ModelState.AddModelError("TestError", "Invalid data model"); // Act var response = await _controller.PostLookup(request); @@ -82,7 +82,7 @@ public async Task PostLookup_ReturnsBadRequest_WhenModelStateIsInvalid() } [Fact] - public async Task PostLookup_ReturnsMixedResults_WhenOneNumberMatchesAndOneDoesNot() + public async Task PostLookup_WithMixedNationalIdentityNumbers_ReturnsMixedResults() { // Arrange var request = new UserContactPointLookup From c40cf778f058f46dceddac380d2aae13ffd18c15 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 10:26:29 +0200 Subject: [PATCH 76/98] Update the unit tests to check the returned values --- .../ContactDetailsControllerTests.cs | 30 +++++++++++++++++++ .../ContactDetailsInternalControllerTests.cs | 12 ++++++++ 2 files changed, 42 insertions(+) diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs index 004508b..8fcb9de 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using Altinn.Profile.Controllers; @@ -154,6 +155,15 @@ public async Task PostLookup_WhenServiceCallIsLongRunning_DoesNotBlock() var returnValue = Assert.IsType(result.Value); Assert.Equal(lookupResult, returnValue); + Assert.Single(returnValue.MatchedContactDetails); + + var matchedContactDetails = returnValue.MatchedContactDetails.FirstOrDefault(); + Assert.NotNull(matchedContactDetails); + Assert.Equal(contactDetails.Reservation, matchedContactDetails.Reservation); + Assert.Equal(contactDetails.EmailAddress, matchedContactDetails.EmailAddress); + Assert.Equal(contactDetails.LanguageCode, matchedContactDetails.LanguageCode); + Assert.Equal(contactDetails.MobilePhoneNumber, matchedContactDetails.MobilePhoneNumber); + Assert.Equal(contactDetails.NationalIdentityNumber, matchedContactDetails.NationalIdentityNumber); } [Fact] @@ -248,6 +258,17 @@ public async Task PostLookup_WithMixedNationalIdentityNumbers_ReturnsMixedResult Assert.Equal(lookupResult, returnValue); Assert.Single(returnValue.MatchedContactDetails); Assert.Single(returnValue.UnmatchedNationalIdentityNumbers); + + var matchedContactDetails = returnValue.MatchedContactDetails.FirstOrDefault(); + Assert.NotNull(matchedContactDetails); + Assert.Equal(contactDetails.Reservation, matchedContactDetails.Reservation); + Assert.Equal(contactDetails.EmailAddress, matchedContactDetails.EmailAddress); + Assert.Equal(contactDetails.LanguageCode, matchedContactDetails.LanguageCode); + Assert.Equal(contactDetails.MobilePhoneNumber, matchedContactDetails.MobilePhoneNumber); + Assert.Equal(contactDetails.NationalIdentityNumber, matchedContactDetails.NationalIdentityNumber); + + var unmatchedNationalIdentityNumber = returnValue.UnmatchedNationalIdentityNumbers.FirstOrDefault(); + Assert.Equal("16051327393", unmatchedNationalIdentityNumber); } [Fact] @@ -284,5 +305,14 @@ public async Task PostLookup_WithValidRequest_ReturnsOk() var returnValue = Assert.IsType(result.Value); Assert.Equal(lookupResult, returnValue); + Assert.Single(returnValue.MatchedContactDetails); + + var matchedContactDetails = returnValue.MatchedContactDetails.FirstOrDefault(); + Assert.NotNull(matchedContactDetails); + Assert.Equal(contactDetails.Reservation, matchedContactDetails.Reservation); + Assert.Equal(contactDetails.EmailAddress, matchedContactDetails.EmailAddress); + Assert.Equal(contactDetails.LanguageCode, matchedContactDetails.LanguageCode); + Assert.Equal(contactDetails.MobilePhoneNumber, matchedContactDetails.MobilePhoneNumber); + Assert.Equal(contactDetails.NationalIdentityNumber, matchedContactDetails.NationalIdentityNumber); } } diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs index 81daaeb..d9670c1 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using Altinn.Profile.Controllers; @@ -117,5 +118,16 @@ public async Task PostLookup_WithMixedNationalIdentityNumbers_ReturnsMixedResult Assert.Equal(lookupResult, returnValue); Assert.Single(returnValue.MatchedContactDetails); Assert.Single(returnValue.UnmatchedNationalIdentityNumbers); + + var matchedContactDetails = returnValue.MatchedContactDetails.FirstOrDefault(); + Assert.NotNull(matchedContactDetails); + Assert.Equal(contactDetails.Reservation, matchedContactDetails.Reservation); + Assert.Equal(contactDetails.EmailAddress, matchedContactDetails.EmailAddress); + Assert.Equal(contactDetails.LanguageCode, matchedContactDetails.LanguageCode); + Assert.Equal(contactDetails.MobilePhoneNumber, matchedContactDetails.MobilePhoneNumber); + Assert.Equal(contactDetails.NationalIdentityNumber, matchedContactDetails.NationalIdentityNumber); + + var unmatchedNationalIdentityNumber = returnValue.UnmatchedNationalIdentityNumbers.FirstOrDefault(); + Assert.Equal("08110270527", unmatchedNationalIdentityNumber); } } From 9d5baded460ba18bf2136b85a1610885ff4fb0e3 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 13:14:09 +0200 Subject: [PATCH 77/98] Increase the unit tests to cover valid and invalid national identity numbers, retrieving contact details, handling empty results, and managing repository exceptions gracefully in the PersonService --- .../Services/PersonService.cs | 6 +- .../Register/PersonServiceTests.cs | 334 +++++++++++++++--- 2 files changed, 294 insertions(+), 46 deletions(-) diff --git a/src/Altinn.Profile.Integrations/Services/PersonService.cs b/src/Altinn.Profile.Integrations/Services/PersonService.cs index 21d2df0..0c2fd21 100644 --- a/src/Altinn.Profile.Integrations/Services/PersonService.cs +++ b/src/Altinn.Profile.Integrations/Services/PersonService.cs @@ -69,11 +69,11 @@ public async Task> GetContactDet var matchedContactDetails = await _personRepository.GetContactDetailsAsync(validNationalIdentityNumbers); - var matchedNationalIdentityNumbers = new HashSet(matchedContactDetails.Select(e => e.FnumberAk)); + var matchedNationalIdentityNumbers = matchedContactDetails != null ? new HashSet(matchedContactDetails.Select(e => e.FnumberAk)) : []; - var unmatchedNationalIdentityNumbers = matchedNationalIdentityNumbers.Where(e => !nationalIdentityNumbers.Contains(e)); + var unmatchedNationalIdentityNumbers = nationalIdentityNumbers.Where(e => !matchedNationalIdentityNumbers.Contains(e)); - var matchedPersonContactDetails = matchedContactDetails.Select(_mapper.Map); + var matchedPersonContactDetails = matchedContactDetails != null ? matchedContactDetails.Select(_mapper.Map) : []; return new PersonContactDetailsLookupResult { diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs index 2105a7b..755fa01 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs @@ -23,6 +23,7 @@ public class PersonServiceTests private readonly Mock _mapperMock; private readonly Mock _personRepositoryMock; private readonly Mock _nationalIdentityNumberCheckerMock; + private readonly PersonService _personService; public PersonServiceTests() @@ -34,72 +35,303 @@ public PersonServiceTests() } [Fact] - public async Task GetContactDetailsAsync_InvalidNationalIdentityNumber_ReturnsNull() + public async Task GetContactDetailsAsync_WhenAllNumbersValidAndAllContactsFound_ReturnsAllContacts() { - var invalidNIN = "invalid_number"; + // Arrange + var nationalIdentityNumbers = new List { "17092037169", "17033112912" }; + + var firstRandomPerson = new Person + { + LanguageCode = "nb", + FnumberAk = "17092037169", + MailboxAddress = "1234 Test St", + MobilePhoneNumber = "+4791234567", + EmailAddress = "test@example.com", + X509Certificate = "certificate_data" + }; + + var secondRandomPerson = new Person + { + LanguageCode = "nb", + FnumberAk = "17033112912", + MailboxAddress = "1234 Test St", + MobilePhoneNumber = "+4791234567", + EmailAddress = "test@example.com", + X509Certificate = "certificate_data" + }; + + var personList = new List { firstRandomPerson, secondRandomPerson }.ToImmutableList(); + + var firstMappedContactDetails = new Mock(); + firstMappedContactDetails.SetupGet(x => x.IsReserved).Returns(firstRandomPerson.Reservation); + firstMappedContactDetails.SetupGet(x => x.EmailAddress).Returns(firstRandomPerson.EmailAddress); + firstMappedContactDetails.SetupGet(x => x.LanguageCode).Returns(firstRandomPerson.LanguageCode); + firstMappedContactDetails.SetupGet(x => x.NationalIdentityNumber).Returns(firstRandomPerson.FnumberAk); + firstMappedContactDetails.SetupGet(x => x.MobilePhoneNumber).Returns(firstRandomPerson.MobilePhoneNumber); + + _mapperMock.Setup(x => x.Map(firstRandomPerson)) + .Returns(firstMappedContactDetails.Object); + + var secondMappedContactDetails = new Mock(); + secondMappedContactDetails.SetupGet(x => x.IsReserved).Returns(secondRandomPerson.Reservation); + secondMappedContactDetails.SetupGet(x => x.EmailAddress).Returns(secondRandomPerson.EmailAddress); + secondMappedContactDetails.SetupGet(x => x.LanguageCode).Returns(secondRandomPerson.LanguageCode); + secondMappedContactDetails.SetupGet(x => x.NationalIdentityNumber).Returns(secondRandomPerson.FnumberAk); + secondMappedContactDetails.SetupGet(x => x.MobilePhoneNumber).Returns(secondRandomPerson.MobilePhoneNumber); + + _mapperMock.Setup(x => x.Map(secondRandomPerson)) + .Returns(secondMappedContactDetails.Object); + + _personRepositoryMock + .Setup(x => x.GetContactDetailsAsync(nationalIdentityNumbers)) + .ReturnsAsync(personList); _nationalIdentityNumberCheckerMock - .Setup(x => x.IsValid(invalidNIN)) + .Setup(x => x.GetValid(nationalIdentityNumbers)) + .Returns(nationalIdentityNumbers.ToImmutableList()); + + // Act + var result = await _personService.GetContactDetailsAsync(nationalIdentityNumbers); + + // Assert + IEnumerable? unmatchedNationalIdentityNumbers = []; + IEnumerable? matchedPersonContactDetails = []; + + result.Match( + success => + { + matchedPersonContactDetails = success.MatchedPersonContactDetails; + unmatchedNationalIdentityNumbers = success.UnmatchedNationalIdentityNumbers; + }, + failure => + { + matchedPersonContactDetails = null; + }); + + Assert.Equal(2, matchedPersonContactDetails.Count()); + + Assert.Contains(matchedPersonContactDetails, detail => detail == firstMappedContactDetails.Object); + var firstContactDetails = matchedPersonContactDetails.FirstOrDefault(detail => detail.NationalIdentityNumber == firstRandomPerson.FnumberAk); + + Assert.NotNull(firstContactDetails); + Assert.Equal(firstRandomPerson.Reservation, firstContactDetails.IsReserved); + Assert.Equal(firstRandomPerson.EmailAddress, firstContactDetails.EmailAddress); + Assert.Equal(firstRandomPerson.LanguageCode, firstContactDetails.LanguageCode); + Assert.Equal(firstRandomPerson.FnumberAk, firstContactDetails.NationalIdentityNumber); + Assert.Equal(firstRandomPerson.MobilePhoneNumber, firstContactDetails.MobilePhoneNumber); + + Assert.Contains(matchedPersonContactDetails, detail => detail == secondMappedContactDetails.Object); + var secondContactDetails = matchedPersonContactDetails.FirstOrDefault(detail => detail.NationalIdentityNumber == secondRandomPerson.FnumberAk); + Assert.NotNull(secondContactDetails); + Assert.Equal(secondRandomPerson.Reservation, secondContactDetails.IsReserved); + Assert.Equal(secondRandomPerson.EmailAddress, secondContactDetails.EmailAddress); + Assert.Equal(secondRandomPerson.LanguageCode, secondContactDetails.LanguageCode); + Assert.Equal(secondRandomPerson.FnumberAk, secondContactDetails.NationalIdentityNumber); + Assert.Equal(secondRandomPerson.MobilePhoneNumber, secondContactDetails.MobilePhoneNumber); + + Assert.Empty(unmatchedNationalIdentityNumbers); + } + + [Fact] + public async Task GetContactDetailsAsync_WhenMultipleNationalIdentityNumbersAreProvided_ReturnsCorrectResult() + { + // Arrange + var validNationalIdentityNumbers = new List { "12028193007", "01091235338" }; + var nationalIdentityNumbers = new List { "12028193007", "01091235338", "invalid_number" }; + + var firstRandomPerson = new Person + { + LanguageCode = "nb", + FnumberAk = "12028193007", + MailboxAddress = "1234 Test St", + MobilePhoneNumber = "+4791234567", + EmailAddress = "test@example.com", + X509Certificate = "certificate_data" + }; + + var secondRandomPerson = new Person + { + LanguageCode = "nb", + FnumberAk = "01091235338", + MailboxAddress = "1234 Test St", + MobilePhoneNumber = "+4791234567", + EmailAddress = "test@example.com", + X509Certificate = "certificate_data" + }; + + var personList = new List { firstRandomPerson, secondRandomPerson }.ToImmutableList(); + + var firstMappedContactDetails = new Mock(); + firstMappedContactDetails.SetupGet(x => x.IsReserved).Returns(firstRandomPerson.Reservation); + firstMappedContactDetails.SetupGet(x => x.EmailAddress).Returns(firstRandomPerson.EmailAddress); + firstMappedContactDetails.SetupGet(x => x.LanguageCode).Returns(firstRandomPerson.LanguageCode); + firstMappedContactDetails.SetupGet(x => x.NationalIdentityNumber).Returns(firstRandomPerson.FnumberAk); + firstMappedContactDetails.SetupGet(x => x.MobilePhoneNumber).Returns(firstRandomPerson.MobilePhoneNumber); + + _mapperMock.Setup(x => x.Map(firstRandomPerson)) + .Returns(firstMappedContactDetails.Object); + + var secondMappedContactDetails = new Mock(); + secondMappedContactDetails.SetupGet(x => x.IsReserved).Returns(secondRandomPerson.Reservation); + secondMappedContactDetails.SetupGet(x => x.EmailAddress).Returns(secondRandomPerson.EmailAddress); + secondMappedContactDetails.SetupGet(x => x.LanguageCode).Returns(secondRandomPerson.LanguageCode); + secondMappedContactDetails.SetupGet(x => x.NationalIdentityNumber).Returns(secondRandomPerson.FnumberAk); + secondMappedContactDetails.SetupGet(x => x.MobilePhoneNumber).Returns(secondRandomPerson.MobilePhoneNumber); + + _mapperMock.Setup(x => x.Map(secondRandomPerson)) + .Returns(secondMappedContactDetails.Object); + + _personRepositoryMock + .Setup(x => x.GetContactDetailsAsync(validNationalIdentityNumbers)) + .ReturnsAsync(personList); + + _nationalIdentityNumberCheckerMock + .Setup(x => x.GetValid(nationalIdentityNumbers)) + .Returns(validNationalIdentityNumbers.ToImmutableList()); + + // Act + var result = await _personService.GetContactDetailsAsync(nationalIdentityNumbers); + + // Assert + IEnumerable? unmatchedNationalIdentityNumbers = []; + IEnumerable? matchedPersonContactDetails = []; + + result.Match( + success => + { + matchedPersonContactDetails = success.MatchedPersonContactDetails; + unmatchedNationalIdentityNumbers = success.UnmatchedNationalIdentityNumbers; + }, + failure => + { + matchedPersonContactDetails = null; + }); + + Assert.Equal(2, matchedPersonContactDetails.Count()); + + Assert.Contains(matchedPersonContactDetails, detail => detail == firstMappedContactDetails.Object); + var firstContactDetails = matchedPersonContactDetails.FirstOrDefault(detail => detail.NationalIdentityNumber == firstRandomPerson.FnumberAk); + + Assert.NotNull(firstContactDetails); + Assert.Equal(firstRandomPerson.Reservation, firstContactDetails.IsReserved); + Assert.Equal(firstRandomPerson.EmailAddress, firstContactDetails.EmailAddress); + Assert.Equal(firstRandomPerson.LanguageCode, firstContactDetails.LanguageCode); + Assert.Equal(firstRandomPerson.FnumberAk, firstContactDetails.NationalIdentityNumber); + Assert.Equal(firstRandomPerson.MobilePhoneNumber, firstContactDetails.MobilePhoneNumber); + + Assert.Contains(matchedPersonContactDetails, detail => detail == secondMappedContactDetails.Object); + var secondContactDetails = matchedPersonContactDetails.FirstOrDefault(detail => detail.NationalIdentityNumber == secondRandomPerson.FnumberAk); + Assert.NotNull(secondContactDetails); + Assert.Equal(secondRandomPerson.Reservation, secondContactDetails.IsReserved); + Assert.Equal(secondRandomPerson.EmailAddress, secondContactDetails.EmailAddress); + Assert.Equal(secondRandomPerson.LanguageCode, secondContactDetails.LanguageCode); + Assert.Equal(secondRandomPerson.FnumberAk, secondContactDetails.NationalIdentityNumber); + Assert.Equal(secondRandomPerson.MobilePhoneNumber, secondContactDetails.MobilePhoneNumber); + + Assert.Single(unmatchedNationalIdentityNumbers); + Assert.Contains("invalid_number", unmatchedNationalIdentityNumbers); + } + + [Fact] + public async Task GetContactDetailsAsync_WhenNationalIdentityNumberIsInvalid_ReturnsNull() + { + // Arrange + var nationalIdentityNumber = "invalid_number"; + + _nationalIdentityNumberCheckerMock + .Setup(x => x.IsValid(nationalIdentityNumber)) .Returns(false); - var result = await _personService.GetContactDetailsAsync(invalidNIN); + // Act + var result = await _personService.GetContactDetailsAsync(nationalIdentityNumber); + // Assert Assert.Null(result); _personRepositoryMock.Verify(x => x.GetContactDetailsAsync(It.IsAny>()), Times.Never); } [Fact] - public async Task GetContactDetailsAsync_ValidNationalIdentityNumber_ReturnsContactDetails() + public async Task GetContactDetailsAsync_WhenNationalIdentityNumberIsValid_ReturnsContactDetails() { - var validNIN = "12345678901"; - var mockPerson = new Person { FnumberAk = validNIN }; - var personList = new List { mockPerson }.ToImmutableList(); - var mappedContactDetail = new Mock(); + // Arrange + var nationalIdentityNumber = "23080188641"; + var randomPerson = new Person + { + LanguageCode = "nb", + MailboxAddress = "1234 Test St", + MobilePhoneNumber = "+4791234567", + EmailAddress = "test@example.com", + FnumberAk = nationalIdentityNumber, + X509Certificate = "certificate_data" + }; + var randomPersons = new List { randomPerson }.ToImmutableList(); - _nationalIdentityNumberCheckerMock - .Setup(x => x.IsValid(validNIN)) - .Returns(true); - _personRepositoryMock - .Setup(x => x.GetContactDetailsAsync(It.IsAny>())) - .ReturnsAsync(personList); + var personContactDetails = new Mock(); + personContactDetails.SetupGet(x => x.IsReserved).Returns(randomPerson.Reservation); + personContactDetails.SetupGet(x => x.EmailAddress).Returns(randomPerson.EmailAddress); + personContactDetails.SetupGet(x => x.LanguageCode).Returns(randomPerson.LanguageCode); + personContactDetails.SetupGet(x => x.NationalIdentityNumber).Returns(nationalIdentityNumber); + personContactDetails.SetupGet(x => x.MobilePhoneNumber).Returns(randomPerson.MobilePhoneNumber); _mapperMock - .Setup(x => x.Map(It.IsAny())) - .Returns(mappedContactDetail.Object); + .Setup(x => x.Map(randomPerson)) + .Returns(personContactDetails.Object); + + _personRepositoryMock + .Setup(x => x.GetContactDetailsAsync(It.Is>(e => e.Contains(nationalIdentityNumber)))) + .ReturnsAsync(randomPersons); + + _nationalIdentityNumberCheckerMock + .Setup(x => x.IsValid(nationalIdentityNumber)) + .Returns(true); - var result = await _personService.GetContactDetailsAsync(validNIN); + // Act + var result = await _personService.GetContactDetailsAsync(nationalIdentityNumber); + // Assert Assert.NotNull(result); - Assert.Equal(mappedContactDetail.Object, result); + Assert.Equal(personContactDetails.Object.IsReserved, result.IsReserved); + Assert.Equal(personContactDetails.Object.EmailAddress, result.EmailAddress); + Assert.Equal(personContactDetails.Object.LanguageCode, result.LanguageCode); + Assert.Equal(personContactDetails.Object.MobilePhoneNumber, result.MobilePhoneNumber); + Assert.Equal(personContactDetails.Object.NationalIdentityNumber, result.NationalIdentityNumber); } [Fact] - public async Task GetContactDetailsAsync_MultipleNationalIdentityNumbers_ReturnsCorrectResult() + public async Task GetContactDetailsAsync_WhenNationalIdentityNumberIsValidAndNoContactFound_ReturnsNull() { - var nationalIdentityNumbers = new List { "12028193007", "01091235338", "invalid_number" }; - var validNationalIdentityNumbers = new List { "12028193007", "01091235338" }; + // Arrange + var nationalIdentityNumber = "23080188641"; + + _nationalIdentityNumberCheckerMock + .Setup(x => x.IsValid(nationalIdentityNumber)) + .Returns(true); + + _personRepositoryMock + .Setup(x => x.GetContactDetailsAsync(It.IsAny>())) + .ReturnsAsync(ImmutableList.Empty); - var mockPerson1 = new Person { FnumberAk = "12028193007" }; - var mockPerson2 = new Person { FnumberAk = "01091235338" }; + // Act + var result = await _personService.GetContactDetailsAsync(nationalIdentityNumber); - var personList = new List { mockPerson1, mockPerson2 }.ToImmutableList(); + // Assert + Assert.Null(result); + } - var mappedContactDetail1 = new Mock(); - var mappedContactDetail2 = new Mock(); + [Fact] + public async Task GetContactDetailsAsync_WhenNoValidNumbersProvided_ReturnsEmptyResult() + { + // Arrange + var nationalIdentityNumbers = new List { "invalid1", "invalid2" }; _nationalIdentityNumberCheckerMock .Setup(x => x.GetValid(nationalIdentityNumbers)) - .Returns(validNationalIdentityNumbers.ToImmutableList()); - _personRepositoryMock - .Setup(x => x.GetContactDetailsAsync(validNationalIdentityNumbers)) - .ReturnsAsync(personList); - - _mapperMock.Setup(x => x.Map(mockPerson1)) - .Returns(mappedContactDetail1.Object); - _mapperMock.Setup(x => x.Map(mockPerson2)) - .Returns(mappedContactDetail2.Object); + .Returns(nationalIdentityNumbers.ToImmutableList()); + // Act var result = await _personService.GetContactDetailsAsync(nationalIdentityNumbers); + + // Assert IEnumerable? unmatchedNationalIdentityNumbers = []; IEnumerable? matchedPersonContactDetails = []; @@ -110,14 +342,30 @@ public async Task GetContactDetailsAsync_MultipleNationalIdentityNumbers_Returns unmatchedNationalIdentityNumbers = success.UnmatchedNationalIdentityNumbers; }, failure => - { - matchedPersonContactDetails = null; - }); + { + matchedPersonContactDetails = null; + }); - Assert.Equal(2, matchedPersonContactDetails.Count()); + Assert.NotNull(matchedPersonContactDetails); + Assert.Empty(matchedPersonContactDetails); - Assert.Contains(matchedPersonContactDetails, detail => detail == mappedContactDetail1.Object); - Assert.Contains(matchedPersonContactDetails, detail => detail == mappedContactDetail2.Object); - Assert.Empty(unmatchedNationalIdentityNumbers); + Assert.NotNull(unmatchedNationalIdentityNumbers); + Assert.Equal(2, unmatchedNationalIdentityNumbers.Count()); + } + + [Fact] + public async Task GetContactDetailsAsync_WhenRepositoryThrowsException_HandlesGracefully() + { + // Arrange + var nationalIdentityNumber = "26050711071"; + _personRepositoryMock + .Setup(repo => repo.GetByIdAsync(It.IsAny())) + .ThrowsAsync(new Exception("Repository failure")); + + // Act + var result = await _personService.GetContactDetailsAsync(nationalIdentityNumber); + + // Assert + Assert.Null(result); } } From a48bf37d143cd207dbe11a41c764c43e789a5f51 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 13:20:49 +0200 Subject: [PATCH 78/98] Use different types --- .../Register/PersonServiceTests.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs index 755fa01..5fffd7b 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs @@ -161,14 +161,14 @@ public async Task GetContactDetailsAsync_WhenMultipleNationalIdentityNumbersAreP var personList = new List { firstRandomPerson, secondRandomPerson }.ToImmutableList(); - var firstMappedContactDetails = new Mock(); + var firstMappedContactDetails = new Mock(); firstMappedContactDetails.SetupGet(x => x.IsReserved).Returns(firstRandomPerson.Reservation); firstMappedContactDetails.SetupGet(x => x.EmailAddress).Returns(firstRandomPerson.EmailAddress); firstMappedContactDetails.SetupGet(x => x.LanguageCode).Returns(firstRandomPerson.LanguageCode); firstMappedContactDetails.SetupGet(x => x.NationalIdentityNumber).Returns(firstRandomPerson.FnumberAk); firstMappedContactDetails.SetupGet(x => x.MobilePhoneNumber).Returns(firstRandomPerson.MobilePhoneNumber); - _mapperMock.Setup(x => x.Map(firstRandomPerson)) + _mapperMock.Setup(x => x.Map(firstRandomPerson)) .Returns(firstMappedContactDetails.Object); var secondMappedContactDetails = new Mock(); @@ -209,7 +209,6 @@ public async Task GetContactDetailsAsync_WhenMultipleNationalIdentityNumbersAreP Assert.Equal(2, matchedPersonContactDetails.Count()); - Assert.Contains(matchedPersonContactDetails, detail => detail == firstMappedContactDetails.Object); var firstContactDetails = matchedPersonContactDetails.FirstOrDefault(detail => detail.NationalIdentityNumber == firstRandomPerson.FnumberAk); Assert.NotNull(firstContactDetails); @@ -266,7 +265,7 @@ public async Task GetContactDetailsAsync_WhenNationalIdentityNumberIsValid_Retur }; var randomPersons = new List { randomPerson }.ToImmutableList(); - var personContactDetails = new Mock(); + var personContactDetails = new Mock(); personContactDetails.SetupGet(x => x.IsReserved).Returns(randomPerson.Reservation); personContactDetails.SetupGet(x => x.EmailAddress).Returns(randomPerson.EmailAddress); personContactDetails.SetupGet(x => x.LanguageCode).Returns(randomPerson.LanguageCode); @@ -274,7 +273,7 @@ public async Task GetContactDetailsAsync_WhenNationalIdentityNumberIsValid_Retur personContactDetails.SetupGet(x => x.MobilePhoneNumber).Returns(randomPerson.MobilePhoneNumber); _mapperMock - .Setup(x => x.Map(randomPerson)) + .Setup(x => x.Map(randomPerson)) .Returns(personContactDetails.Object); _personRepositoryMock @@ -309,7 +308,7 @@ public async Task GetContactDetailsAsync_WhenNationalIdentityNumberIsValidAndNoC _personRepositoryMock .Setup(x => x.GetContactDetailsAsync(It.IsAny>())) - .ReturnsAsync(ImmutableList.Empty); + .ReturnsAsync([]); // Act var result = await _personService.GetContactDetailsAsync(nationalIdentityNumber); From 3e11984853dbed999a2cbf7b9879b224a85bc16e Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 13:26:28 +0200 Subject: [PATCH 79/98] Fix an issue introduced in the previous commit --- .../Profile.Integrations/Register/PersonServiceTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs index 5fffd7b..d4f93c1 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs @@ -161,14 +161,14 @@ public async Task GetContactDetailsAsync_WhenMultipleNationalIdentityNumbersAreP var personList = new List { firstRandomPerson, secondRandomPerson }.ToImmutableList(); - var firstMappedContactDetails = new Mock(); + var firstMappedContactDetails = new Mock(); firstMappedContactDetails.SetupGet(x => x.IsReserved).Returns(firstRandomPerson.Reservation); firstMappedContactDetails.SetupGet(x => x.EmailAddress).Returns(firstRandomPerson.EmailAddress); firstMappedContactDetails.SetupGet(x => x.LanguageCode).Returns(firstRandomPerson.LanguageCode); firstMappedContactDetails.SetupGet(x => x.NationalIdentityNumber).Returns(firstRandomPerson.FnumberAk); firstMappedContactDetails.SetupGet(x => x.MobilePhoneNumber).Returns(firstRandomPerson.MobilePhoneNumber); - _mapperMock.Setup(x => x.Map(firstRandomPerson)) + _mapperMock.Setup(x => x.Map(firstRandomPerson)) .Returns(firstMappedContactDetails.Object); var secondMappedContactDetails = new Mock(); @@ -265,7 +265,7 @@ public async Task GetContactDetailsAsync_WhenNationalIdentityNumberIsValid_Retur }; var randomPersons = new List { randomPerson }.ToImmutableList(); - var personContactDetails = new Mock(); + var personContactDetails = new Mock(); personContactDetails.SetupGet(x => x.IsReserved).Returns(randomPerson.Reservation); personContactDetails.SetupGet(x => x.EmailAddress).Returns(randomPerson.EmailAddress); personContactDetails.SetupGet(x => x.LanguageCode).Returns(randomPerson.LanguageCode); @@ -273,7 +273,7 @@ public async Task GetContactDetailsAsync_WhenNationalIdentityNumberIsValid_Retur personContactDetails.SetupGet(x => x.MobilePhoneNumber).Returns(randomPerson.MobilePhoneNumber); _mapperMock - .Setup(x => x.Map(randomPerson)) + .Setup(x => x.Map(randomPerson)) .Returns(personContactDetails.Object); _personRepositoryMock From d486bf395328fdeedc34fe62e8dd704b63d385c8 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 14:05:07 +0200 Subject: [PATCH 80/98] Add the Reservation flag to the test data --- .../Profile.Integrations/Register/PersonServiceTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs index d4f93c1..292275b 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs @@ -42,6 +42,7 @@ public async Task GetContactDetailsAsync_WhenAllNumbersValidAndAllContactsFound_ var firstRandomPerson = new Person { + Reservation = false, LanguageCode = "nb", FnumberAk = "17092037169", MailboxAddress = "1234 Test St", @@ -52,6 +53,7 @@ public async Task GetContactDetailsAsync_WhenAllNumbersValidAndAllContactsFound_ var secondRandomPerson = new Person { + Reservation = true, LanguageCode = "nb", FnumberAk = "17033112912", MailboxAddress = "1234 Test St", @@ -141,6 +143,7 @@ public async Task GetContactDetailsAsync_WhenMultipleNationalIdentityNumbersAreP var firstRandomPerson = new Person { + Reservation = false, LanguageCode = "nb", FnumberAk = "12028193007", MailboxAddress = "1234 Test St", @@ -151,6 +154,7 @@ public async Task GetContactDetailsAsync_WhenMultipleNationalIdentityNumbersAreP var secondRandomPerson = new Person { + Reservation = true, LanguageCode = "nb", FnumberAk = "01091235338", MailboxAddress = "1234 Test St", @@ -256,6 +260,7 @@ public async Task GetContactDetailsAsync_WhenNationalIdentityNumberIsValid_Retur var nationalIdentityNumber = "23080188641"; var randomPerson = new Person { + Reservation = false, LanguageCode = "nb", MailboxAddress = "1234 Test St", MobilePhoneNumber = "+4791234567", From 7e6f569c94fb6a6d1da57d54c52d2243c42b6387 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 14:11:24 +0200 Subject: [PATCH 81/98] Renamed a file --- .../{RegisterRepositoryTests.cs => PersonRepositoryTests.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename test/Altinn.Profile.Tests/Profile.Integrations/Register/{RegisterRepositoryTests.cs => PersonRepositoryTests.cs} (97%) diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonRepositoryTests.cs similarity index 97% rename from test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs rename to test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonRepositoryTests.cs index 8495841..d11d59e 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Register/RegisterRepositoryTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonRepositoryTests.cs @@ -17,13 +17,13 @@ namespace Altinn.Profile.Tests.Profile.Integrations; /// /// Contains unit tests for the class. /// -public class RegisterRepositoryTests : IDisposable +public class PersonRepositoryTests : IDisposable { private readonly ProfileDbContext _context; private readonly PersonRepository _registerRepository; private readonly List _personContactAndReservationTestData; - public RegisterRepositoryTests() + public PersonRepositoryTests() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) From d607540e8ed784623a5885a40239509a57bc9f83 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 14:34:32 +0200 Subject: [PATCH 82/98] Add a unit test to test retrieving contact details --- .../UserContactDetailsRetrieverTests.cs | 86 ++++++++++++++++--- 1 file changed, 75 insertions(+), 11 deletions(-) diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/UserContactDetailsRetrieverTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/UserContactDetailsRetrieverTests.cs index 8f3ee25..9b52d21 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/UserContactDetailsRetrieverTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/UserContactDetailsRetrieverTests.cs @@ -1,9 +1,10 @@ -using System; +#nullable enable + +using System; using System.Collections.Generic; -using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; -using Altinn.Profile.Core; using Altinn.Profile.Integrations.Entities; using Altinn.Profile.Integrations.Services; using Altinn.Profile.Models; @@ -17,24 +18,24 @@ namespace Altinn.Profile.Tests.Profile.Integrations; public class UserContactDetailsRetrieverTests { - private readonly Mock _mockRegisterService; private readonly ContactDetailsRetriever _retriever; + private readonly Mock _mockPersonService; public UserContactDetailsRetrieverTests() { - _mockRegisterService = new Mock(); - _retriever = new ContactDetailsRetriever(_mockRegisterService.Object); + _mockPersonService = new Mock(); + _retriever = new ContactDetailsRetriever(_mockPersonService.Object); } [Fact] - public async Task RetrieveAsync_ThrowsArgumentNullException_WhenLookupCriteriaIsNull() + public async Task RetrieveAsync_WhenLookupCriteriaIsNull_ThrowsArgumentNullException() { // Act & Assert await Assert.ThrowsAsync(() => _retriever.RetrieveAsync(null)); } [Fact] - public async Task RetrieveAsync_ReturnsFalse_WhenNationalIdentityNumbersIsEmpty() + public async Task RetrieveAsync_WhenNationalIdentityNumbersIsEmpty_ReturnsFalse() { // Arrange var lookupCriteria = new UserContactPointLookup { NationalIdentityNumbers = [] }; @@ -43,24 +44,87 @@ public async Task RetrieveAsync_ReturnsFalse_WhenNationalIdentityNumbersIsEmpty( var result = await _retriever.RetrieveAsync(lookupCriteria); // Assert + Assert.True(result.IsError); Assert.False(result.IsSuccess); } [Fact] - public async Task RetrieveAsync_ReturnsFalse_WhenNoContactDetailsFound() + public async Task RetrieveAsync_WhenNoContactDetailsFound_ReturnsFalse() { // Arrange var lookupCriteria = new UserContactPointLookup { - NationalIdentityNumbers = new List { "08119043698" } + NationalIdentityNumbers = ["08119043698"] }; - _mockRegisterService.Setup(s => s.GetContactDetailsAsync(lookupCriteria.NationalIdentityNumbers)).ReturnsAsync(false); + _mockPersonService.Setup(s => s.GetContactDetailsAsync(lookupCriteria.NationalIdentityNumbers)).ReturnsAsync(false); // Act var result = await _retriever.RetrieveAsync(lookupCriteria); // Assert + Assert.True(result.IsError); Assert.False(result.IsSuccess); } + + [Fact] + public async Task RetrieveAsync_WhenValidNationalIdentityNumbers_ReturnsExpectedContactDetailsLookupResult() + { + // Arrange + var lookupCriteria = new UserContactPointLookup + { + NationalIdentityNumbers = ["08053414843"] + }; + + var personContactDetails = new PersonContactDetails + { + IsReserved = false, + LanguageCode = "en", + MobilePhoneNumber = "1234567890", + EmailAddress = "test@example.com", + NationalIdentityNumber = "08053414843" + }; + + var lookupResult = new PersonContactDetailsLookupResult + { + UnmatchedNationalIdentityNumbers = [], + MatchedPersonContactDetails = [personContactDetails] + }; + + _mockPersonService + .Setup(e => e.GetContactDetailsAsync(lookupCriteria.NationalIdentityNumbers)) + .ReturnsAsync(lookupResult); + + // Act + var result = await _retriever.RetrieveAsync(lookupCriteria); + + // Assert + Assert.True(result.IsSuccess); + IEnumerable? unmatchedNationalIdentityNumbers = []; + IEnumerable? matchedPersonContactDetails = []; + + result.Match( + success => + { + matchedPersonContactDetails = success.MatchedContactDetails; + unmatchedNationalIdentityNumbers = success.UnmatchedNationalIdentityNumbers; + }, + failure => + { + matchedPersonContactDetails = null; + }); + + Assert.NotNull(matchedPersonContactDetails); + Assert.Single(matchedPersonContactDetails); + + var matchPersonContactDetails = matchedPersonContactDetails.FirstOrDefault(); + Assert.NotNull(matchPersonContactDetails); + Assert.Equal(personContactDetails.IsReserved, matchPersonContactDetails.Reservation); + Assert.Equal(personContactDetails.LanguageCode, matchPersonContactDetails.LanguageCode); + Assert.Equal(personContactDetails.MobilePhoneNumber, matchPersonContactDetails.MobilePhoneNumber); + Assert.Equal(personContactDetails.EmailAddress, matchPersonContactDetails.EmailAddress); + Assert.Equal(personContactDetails.NationalIdentityNumber, matchPersonContactDetails.NationalIdentityNumber); + + Assert.Empty(unmatchedNationalIdentityNumbers); + } } From 4a96d156854194f6aded5dc4a814423a315f273b Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 14:59:40 +0200 Subject: [PATCH 83/98] Code refactoring --- .../ContactDetailsControllerTests.cs | 224 +++++++++--------- 1 file changed, 109 insertions(+), 115 deletions(-) diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs index 8fcb9de..ef8ebe0 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsControllerTests.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading.Tasks; using Altinn.Profile.Controllers; @@ -67,20 +66,14 @@ public void Constructor_WithValidParameters_InitializesCorrectly() public async Task PostLookup_WhenExceptionOccurs_ReturnsInternalServerError_And_LogsError() { // Arrange - var request = new UserContactPointLookup - { - NationalIdentityNumbers = ["27038893837"] - }; - - _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) - .ThrowsAsync(new Exception("Test exception")); + var request = new UserContactPointLookup { NationalIdentityNumbers = { "27038893837" } }; + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)).ThrowsAsync(new Exception("Test exception")); // Act var response = await _controller.PostLookup(request); // Assert var problemResult = Assert.IsType(response.Result); - Assert.Equal(StatusCodes.Status500InternalServerError, problemResult.StatusCode); _loggerMock.Verify( @@ -89,62 +82,41 @@ public async Task PostLookup_WhenExceptionOccurs_ReturnsInternalServerError_And_ It.IsAny(), It.Is((v, t) => v.ToString().Contains("An error occurred while retrieving contact details.")), It.IsAny(), - It.Is>((v, t) => true)), + It.IsAny>()), Times.Once); } [Fact] - public async Task PostLookup_WhenNoContactDetailsFound_ReturnsNotFound() + public async Task PostLookup_WhenLookupCriteriaIsNull_ReturnsBadRequest() { // Arrange - var request = new UserContactPointLookup - { - NationalIdentityNumbers = ["30083542175"] - }; - - var lookupResult = new ContactDetailsLookupResult( - matchedContactDetails: [], - unmatchedNationalIdentityNumbers: [request.NationalIdentityNumbers[0]]); - - _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) - .ReturnsAsync(lookupResult); + UserContactPointLookup request = null; // Act var response = await _controller.PostLookup(request); // Assert - var notFoundResult = Assert.IsType(response.Result); - Assert.Equal(StatusCodes.Status404NotFound, notFoundResult.StatusCode); + var badRequestResult = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status400BadRequest, badRequestResult.StatusCode); + Assert.Equal("National identity numbers cannot be null or empty.", badRequestResult.Value); } [Fact] - public async Task PostLookup_WhenServiceCallIsLongRunning_DoesNotBlock() + public async Task PostLookup_WhenMatchedContactDetailsAreFound_ReturnsOk() { // Arrange - var request = new UserContactPointLookup - { - NationalIdentityNumbers = ["02038112735"] - }; - + var request = new UserContactPointLookup { NationalIdentityNumbers = { "05053423096" } }; var contactDetails = new ContactDetails { - LanguageCode = "nb", + LanguageCode = "en", Reservation = false, - MobilePhoneNumber = "12345678", - EmailAddress = "test@example.com", - NationalIdentityNumber = "02038112735" + MobilePhoneNumber = "98765432", + EmailAddress = "user@example.com", + NationalIdentityNumber = "05053423096" }; + var lookupResult = new ContactDetailsLookupResult(matchedContactDetails: [contactDetails], unmatchedNationalIdentityNumbers: []); - var lookupResult = new ContactDetailsLookupResult( - matchedContactDetails: [contactDetails], - unmatchedNationalIdentityNumbers: []); - - _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) - .ReturnsAsync(() => - { - Task.Delay(5000).Wait(); // Simulate long-running task - return lookupResult; - }); + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)).ReturnsAsync(lookupResult); // Act var response = await _controller.PostLookup(request); @@ -154,63 +126,96 @@ public async Task PostLookup_WhenServiceCallIsLongRunning_DoesNotBlock() Assert.Equal(StatusCodes.Status200OK, result.StatusCode); var returnValue = Assert.IsType(result.Value); - Assert.Equal(lookupResult, returnValue); + Assert.NotNull(returnValue); Assert.Single(returnValue.MatchedContactDetails); - - var matchedContactDetails = returnValue.MatchedContactDetails.FirstOrDefault(); - Assert.NotNull(matchedContactDetails); - Assert.Equal(contactDetails.Reservation, matchedContactDetails.Reservation); - Assert.Equal(contactDetails.EmailAddress, matchedContactDetails.EmailAddress); - Assert.Equal(contactDetails.LanguageCode, matchedContactDetails.LanguageCode); - Assert.Equal(contactDetails.MobilePhoneNumber, matchedContactDetails.MobilePhoneNumber); - Assert.Equal(contactDetails.NationalIdentityNumber, matchedContactDetails.NationalIdentityNumber); + Assert.Empty(returnValue.UnmatchedNationalIdentityNumbers); } [Fact] - public async Task PostLookup_WithInvalidModelState_ReturnsBadRequest() + public async Task PostLookup_WhenNationalIdentityNumbersIsNull_ReturnsBadRequest() { // Arrange - var invalidRequest = new UserContactPointLookup - { - NationalIdentityNumbers = ["14078112078"] - }; - - _controller.ModelState.AddModelError("InvalidKey", "Invalid error message"); + var request = new UserContactPointLookup { NationalIdentityNumbers = null }; // Act - var response = await _controller.PostLookup(invalidRequest); + var response = await _controller.PostLookup(request); // Assert var badRequestResult = Assert.IsType(response.Result); Assert.Equal(StatusCodes.Status400BadRequest, badRequestResult.StatusCode); + Assert.Equal("National identity numbers cannot be null or empty.", badRequestResult.Value); } [Fact] - public async Task PostLookup_WithInvalidSingleNationalIdentityNumber_ReturnsBadRequest() + public async Task PostLookup_WhenNoContactDetailsFound_ReturnsNotFound() { // Arrange - var invalidRequest = new UserContactPointLookup - { - NationalIdentityNumbers = ["invalid_format"] - }; + var request = new UserContactPointLookup { NationalIdentityNumbers = { "30083542175" } }; + var lookupResult = new ContactDetailsLookupResult(matchedContactDetails: [], unmatchedNationalIdentityNumbers: ["30083542175"]); + + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)).ReturnsAsync(lookupResult); // Act - var response = await _controller.PostLookup(invalidRequest); + var response = await _controller.PostLookup(request); // Assert var notFoundResult = Assert.IsType(response.Result); Assert.Equal(StatusCodes.Status404NotFound, notFoundResult.StatusCode); - Assert.Null(response.Value); } [Fact] - public async Task PostLookup_WithNoNationalIdentityNumbers_ReturnsBadRequest() + public async Task PostLookup_WhenNoMatchedContactDetailsAreFound_ReturnsNotFound() { // Arrange - var invalidRequest = new UserContactPointLookup + var request = new UserContactPointLookup { NationalIdentityNumbers = { "99020312345" } }; + var lookupResult = new ContactDetailsLookupResult(matchedContactDetails: [], unmatchedNationalIdentityNumbers: ["99020312345"]); + + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)).ReturnsAsync(lookupResult); + + // Act + var response = await _controller.PostLookup(request); + + // Assert + var notFoundResult = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status404NotFound, notFoundResult.StatusCode); + } + + [Fact] + public async Task PostLookup_WhenServiceCallIsLongRunning_DoesNotBlock() + { + // Arrange + var request = new UserContactPointLookup { NationalIdentityNumbers = { "02038112735" } }; + var contactDetails = new ContactDetails { - NationalIdentityNumbers = [] + LanguageCode = "nb", + Reservation = false, + MobilePhoneNumber = "12345678", + EmailAddress = "test@example.com", + NationalIdentityNumber = "02038112735" }; + var lookupResult = new ContactDetailsLookupResult(matchedContactDetails: [contactDetails], unmatchedNationalIdentityNumbers: []); + + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)).ReturnsAsync(() => + { + Task.Delay(5000).Wait(); // Simulate long-running task + return lookupResult; + }); + + // Act + var response = await _controller.PostLookup(request); + + // Assert + var result = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status200OK, result.StatusCode); + Assert.Equal(lookupResult, Assert.IsType(result.Value)); + } + + [Fact] + public async Task PostLookup_WithInvalidModelState_ReturnsBadRequest() + { + // Arrange + var invalidRequest = new UserContactPointLookup { NationalIdentityNumbers = { "14078112078" } }; + _controller.ModelState.AddModelError("InvalidKey", "Invalid error message"); // Act var response = await _controller.PostLookup(invalidRequest); @@ -218,19 +223,27 @@ public async Task PostLookup_WithNoNationalIdentityNumbers_ReturnsBadRequest() // Assert var badRequestResult = Assert.IsType(response.Result); Assert.Equal(StatusCodes.Status400BadRequest, badRequestResult.StatusCode); - Assert.Equal("National identity numbers cannot be null or empty.", badRequestResult.Value); - Assert.Null(response.Value); } [Fact] - public async Task PostLookup_WithMixedNationalIdentityNumbers_ReturnsMixedResults() + public async Task PostLookup_WithInvalidSingleNationalIdentityNumber_ReturnsBadRequest() { // Arrange - var request = new UserContactPointLookup - { - NationalIdentityNumbers = ["10060339738", "16051327393"] - }; + var invalidRequest = new UserContactPointLookup { NationalIdentityNumbers = { "invalid_format" } }; + // Act + var response = await _controller.PostLookup(invalidRequest); + + // Assert + var notFoundResult = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status404NotFound, notFoundResult.StatusCode); + } + + [Fact] + public async Task PostLookup_WithMixedNationalIdentityNumbers_ReturnsMixedResults() + { + // Arrange + var request = new UserContactPointLookup { NationalIdentityNumbers = { "10060339738", "16051327393" } }; var contactDetails = new ContactDetails { LanguageCode = "nb", @@ -239,13 +252,9 @@ public async Task PostLookup_WithMixedNationalIdentityNumbers_ReturnsMixedResult EmailAddress = "test@example.com", NationalIdentityNumber = "10060339738" }; + var lookupResult = new ContactDetailsLookupResult(matchedContactDetails: [contactDetails], unmatchedNationalIdentityNumbers: ["16051327393"]); - var lookupResult = new ContactDetailsLookupResult( - matchedContactDetails: [contactDetails], - unmatchedNationalIdentityNumbers: ["16051327393"]); - - _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) - .ReturnsAsync(lookupResult); + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)).ReturnsAsync(lookupResult); // Act var response = await _controller.PostLookup(request); @@ -253,33 +262,32 @@ public async Task PostLookup_WithMixedNationalIdentityNumbers_ReturnsMixedResult // Assert var result = Assert.IsType(response.Result); Assert.Equal(StatusCodes.Status200OK, result.StatusCode); - var returnValue = Assert.IsType(result.Value); Assert.Equal(lookupResult, returnValue); Assert.Single(returnValue.MatchedContactDetails); Assert.Single(returnValue.UnmatchedNationalIdentityNumbers); + } + + [Fact] + public async Task PostLookup_WithNoNationalIdentityNumbers_ReturnsBadRequest() + { + // Arrange + var invalidRequest = new UserContactPointLookup { NationalIdentityNumbers = { } }; - var matchedContactDetails = returnValue.MatchedContactDetails.FirstOrDefault(); - Assert.NotNull(matchedContactDetails); - Assert.Equal(contactDetails.Reservation, matchedContactDetails.Reservation); - Assert.Equal(contactDetails.EmailAddress, matchedContactDetails.EmailAddress); - Assert.Equal(contactDetails.LanguageCode, matchedContactDetails.LanguageCode); - Assert.Equal(contactDetails.MobilePhoneNumber, matchedContactDetails.MobilePhoneNumber); - Assert.Equal(contactDetails.NationalIdentityNumber, matchedContactDetails.NationalIdentityNumber); + // Act + var response = await _controller.PostLookup(invalidRequest); - var unmatchedNationalIdentityNumber = returnValue.UnmatchedNationalIdentityNumbers.FirstOrDefault(); - Assert.Equal("16051327393", unmatchedNationalIdentityNumber); + // Assert + var badRequestResult = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status400BadRequest, badRequestResult.StatusCode); + Assert.Equal("National identity numbers cannot be null or empty.", badRequestResult.Value); } [Fact] public async Task PostLookup_WithValidRequest_ReturnsOk() { // Arrange - var request = new UserContactPointLookup - { - NationalIdentityNumbers = ["27038893837"] - }; - + var request = new UserContactPointLookup { NationalIdentityNumbers = { "27038893837" } }; var contactDetails = new ContactDetails { LanguageCode = "nb", @@ -288,13 +296,9 @@ public async Task PostLookup_WithValidRequest_ReturnsOk() EmailAddress = "test@example.com", NationalIdentityNumber = "27038893837" }; + var lookupResult = new ContactDetailsLookupResult(matchedContactDetails: [contactDetails], unmatchedNationalIdentityNumbers: []); - var lookupResult = new ContactDetailsLookupResult( - matchedContactDetails: [contactDetails], - unmatchedNationalIdentityNumbers: []); - - _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) - .ReturnsAsync(lookupResult); + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)).ReturnsAsync(lookupResult); // Act var response = await _controller.PostLookup(request); @@ -302,17 +306,7 @@ public async Task PostLookup_WithValidRequest_ReturnsOk() // Assert var result = Assert.IsType(response.Result); Assert.Equal(StatusCodes.Status200OK, result.StatusCode); - var returnValue = Assert.IsType(result.Value); Assert.Equal(lookupResult, returnValue); - Assert.Single(returnValue.MatchedContactDetails); - - var matchedContactDetails = returnValue.MatchedContactDetails.FirstOrDefault(); - Assert.NotNull(matchedContactDetails); - Assert.Equal(contactDetails.Reservation, matchedContactDetails.Reservation); - Assert.Equal(contactDetails.EmailAddress, matchedContactDetails.EmailAddress); - Assert.Equal(contactDetails.LanguageCode, matchedContactDetails.LanguageCode); - Assert.Equal(contactDetails.MobilePhoneNumber, matchedContactDetails.MobilePhoneNumber); - Assert.Equal(contactDetails.NationalIdentityNumber, matchedContactDetails.NationalIdentityNumber); } } From de8f50649d8c9e491d3473f46475a3230e7bdc3a Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 15:19:30 +0200 Subject: [PATCH 84/98] Code refactoring --- .../ContactDetailsInternalControllerTests.cs | 108 ++++++++++++------ 1 file changed, 72 insertions(+), 36 deletions(-) diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs index d9670c1..333c84b 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs @@ -32,60 +32,75 @@ public ContactDetailsInternalControllerTests() [Fact] public void Constructor_WithNullContactDetailsRetriever_ThrowsArgumentNullException() { - // Arrange - var loggerMock = new Mock>(); - - // Act & Assert - Assert.Throws(() => new ContactDetailsInternalController(loggerMock.Object, null)); + Assert.Throws(() => new ContactDetailsInternalController(_loggerMock.Object, null)); } [Fact] public void Constructor_WithNullLogger_ThrowsArgumentNullException() { - // Arrange var contactDetailsRetrieverMock = new Mock(); - - // Act & Assert Assert.Throws(() => new ContactDetailsInternalController(null, contactDetailsRetrieverMock.Object)); } [Fact] public void Constructor_WithValidParameters_InitializesCorrectly() { - // Arrange - var loggerMock = new Mock>(); var contactDetailsRetrieverMock = new Mock(); + var controller = new ContactDetailsInternalController(_loggerMock.Object, contactDetailsRetrieverMock.Object); + Assert.NotNull(controller); + } - // Act - var controller = new ContactDetailsInternalController(loggerMock.Object, contactDetailsRetrieverMock.Object); + [Fact] + public async Task PostLookup_WhenRetrievalThrowsException_LogsErrorAndReturnsProblemResult() + { + var request = new UserContactPointLookup + { + NationalIdentityNumbers = ["05025308508"] + }; - // Assert - Assert.NotNull(controller); + _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) + .ThrowsAsync(new Exception("Some error occurred")); + + var response = await _controller.PostLookup(request); + + _loggerMock.Verify( + x => x.Log( + LogLevel.Error, + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("An error occurred while retrieving contact details.")), + It.IsAny(), + It.IsAny>()), + Times.Once); + + var problemResult = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status500InternalServerError, problemResult.StatusCode); + Assert.Equal("An unexpected error occurred", Convert.ToString(((ProblemDetails)problemResult.Value).Detail)); + } + + [Fact] + public async Task PostLookup_WithEmptyNationalIdentityNumbers_ReturnsBadRequest() + { + var request = new UserContactPointLookup { NationalIdentityNumbers = [] }; + var response = await _controller.PostLookup(request); + AssertBadRequest(response, "National identity numbers cannot be null or empty."); } [Fact] public async Task PostLookup_WithInvalidModelState_ReturnsBadRequest() { - // Arrange var request = new UserContactPointLookup { NationalIdentityNumbers = ["17092037169"] }; - _controller.ModelState.AddModelError("TestError", "Invalid data model"); - // Act var response = await _controller.PostLookup(request); - - // Assert - var badRequestResult = Assert.IsType(response.Result); - Assert.Equal(StatusCodes.Status400BadRequest, badRequestResult.StatusCode); + AssertBadRequest(response); } [Fact] public async Task PostLookup_WithMixedNationalIdentityNumbers_ReturnsMixedResults() { - // Arrange var request = new UserContactPointLookup { NationalIdentityNumbers = ["05025308508", "08110270527"] @@ -107,27 +122,48 @@ public async Task PostLookup_WithMixedNationalIdentityNumbers_ReturnsMixedResult _mockContactDetailsRetriever.Setup(x => x.RetrieveAsync(request)) .ReturnsAsync(lookupResult); - // Act var response = await _controller.PostLookup(request); - // Assert var result = Assert.IsType(response.Result); Assert.Equal(StatusCodes.Status200OK, result.StatusCode); var returnValue = Assert.IsType(result.Value); - Assert.Equal(lookupResult, returnValue); - Assert.Single(returnValue.MatchedContactDetails); - Assert.Single(returnValue.UnmatchedNationalIdentityNumbers); + AssertContactDetailsLookupResult(lookupResult, returnValue); + } + + [Fact] + public async Task PostLookup_WithNullNationalIdentityNumbers_ReturnsBadRequest() + { + var request = new UserContactPointLookup { NationalIdentityNumbers = null }; + var response = await _controller.PostLookup(request); + AssertBadRequest(response, "National identity numbers cannot be null or empty."); + } + + private static void AssertBadRequest(ActionResult response, string expectedMessage = null) + { + var badRequestResult = Assert.IsType(response.Result); + Assert.Equal(StatusCodes.Status400BadRequest, badRequestResult.StatusCode); + if (expectedMessage != null) + { + Assert.Equal(expectedMessage, badRequestResult.Value); + } + } + + private static void AssertContactDetailsLookupResult(ContactDetailsLookupResult expected, ContactDetailsLookupResult actual) + { + Assert.Equal(expected, actual); + Assert.Single(actual.MatchedContactDetails); + Assert.Single(actual.UnmatchedNationalIdentityNumbers); - var matchedContactDetails = returnValue.MatchedContactDetails.FirstOrDefault(); + var matchedContactDetails = actual.MatchedContactDetails.FirstOrDefault(); Assert.NotNull(matchedContactDetails); - Assert.Equal(contactDetails.Reservation, matchedContactDetails.Reservation); - Assert.Equal(contactDetails.EmailAddress, matchedContactDetails.EmailAddress); - Assert.Equal(contactDetails.LanguageCode, matchedContactDetails.LanguageCode); - Assert.Equal(contactDetails.MobilePhoneNumber, matchedContactDetails.MobilePhoneNumber); - Assert.Equal(contactDetails.NationalIdentityNumber, matchedContactDetails.NationalIdentityNumber); - - var unmatchedNationalIdentityNumber = returnValue.UnmatchedNationalIdentityNumbers.FirstOrDefault(); - Assert.Equal("08110270527", unmatchedNationalIdentityNumber); + Assert.Equal(expected.MatchedContactDetails.First().Reservation, matchedContactDetails.Reservation); + Assert.Equal(expected.MatchedContactDetails.First().EmailAddress, matchedContactDetails.EmailAddress); + Assert.Equal(expected.MatchedContactDetails.First().LanguageCode, matchedContactDetails.LanguageCode); + Assert.Equal(expected.MatchedContactDetails.First().MobilePhoneNumber, matchedContactDetails.MobilePhoneNumber); + Assert.Equal(expected.MatchedContactDetails.First().NationalIdentityNumber, matchedContactDetails.NationalIdentityNumber); + + var unmatchedNationalIdentityNumber = actual.UnmatchedNationalIdentityNumbers.FirstOrDefault(); + Assert.Equal(expected.UnmatchedNationalIdentityNumbers.First(), unmatchedNationalIdentityNumber); } } From 7a51015fe5e7325170b0234d056cba9b66a7907d Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 15:20:08 +0200 Subject: [PATCH 85/98] Add a missing dot --- .../API/Controllers/ContactDetailsInternalControllerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs index 333c84b..b4490f8 100644 --- a/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs +++ b/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ContactDetailsInternalControllerTests.cs @@ -74,7 +74,7 @@ public async Task PostLookup_WhenRetrievalThrowsException_LogsErrorAndReturnsPro var problemResult = Assert.IsType(response.Result); Assert.Equal(StatusCodes.Status500InternalServerError, problemResult.StatusCode); - Assert.Equal("An unexpected error occurred", Convert.ToString(((ProblemDetails)problemResult.Value).Detail)); + Assert.Equal("An unexpected error occurred.", Convert.ToString(((ProblemDetails)problemResult.Value).Detail)); } [Fact] From c29f65ab572d4d43d9c98faf35d84854858785d1 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 15:22:47 +0200 Subject: [PATCH 86/98] Rename a folder --- .../{Register => Person}/PersonRepositoryTests.cs | 0 .../{Register => Person}/PersonServiceTests.cs | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename test/Altinn.Profile.Tests/Profile.Integrations/{Register => Person}/PersonRepositoryTests.cs (100%) rename test/Altinn.Profile.Tests/Profile.Integrations/{Register => Person}/PersonServiceTests.cs (100%) diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonRepositoryTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Person/PersonRepositoryTests.cs similarity index 100% rename from test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonRepositoryTests.cs rename to test/Altinn.Profile.Tests/Profile.Integrations/Person/PersonRepositoryTests.cs diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Person/PersonServiceTests.cs similarity index 100% rename from test/Altinn.Profile.Tests/Profile.Integrations/Register/PersonServiceTests.cs rename to test/Altinn.Profile.Tests/Profile.Integrations/Person/PersonServiceTests.cs From b59622559bfba134779522acea61488d673f4ef3 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 15:34:56 +0200 Subject: [PATCH 87/98] Code refactoring --- .../Extensions/StringExtensionsTests.cs | 149 +++++++++--------- 1 file changed, 73 insertions(+), 76 deletions(-) diff --git a/test/Altinn.Profile.Tests/Profile.Core/Extensions/StringExtensionsTests.cs b/test/Altinn.Profile.Tests/Profile.Core/Extensions/StringExtensionsTests.cs index cbb7df4..04fa75b 100644 --- a/test/Altinn.Profile.Tests/Profile.Core/Extensions/StringExtensionsTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Core/Extensions/StringExtensionsTests.cs @@ -1,25 +1,13 @@ -using Altinn.Profile.Core.Extensions; +using System.Reflection; + +using Altinn.Profile.Core.Extensions; + using Xunit; namespace Altinn.Profile.Tests.Core.Extensions; public class StringExtensionsTests { - [Fact] - public void IsDigitsOnly_InvalidInput_ReturnsFalse() - { - Assert.False("123A456".IsDigitsOnly()); - Assert.False("123 456".IsDigitsOnly()); - Assert.False(string.Empty.IsDigitsOnly()); - Assert.False(((string)null).IsDigitsOnly()); - } - - [Fact] - public void IsDigitsOnly_ValidDigits_ReturnsTrue() - { - Assert.True("1234567890".IsDigitsOnly()); - } - [Theory] [InlineData("", false)] [InlineData(null, false)] @@ -29,94 +17,103 @@ public void IsDigitsOnly_ValidDigits_ReturnsTrue() [InlineData("1234567890", true)] public void IsDigitsOnly_VariousInputs_ReturnsExpected(string input, bool expected) { + // Act var result = input.IsDigitsOnly(); + + // Assert Assert.Equal(expected, result); } - [Fact] - public void IsValidSocialSecurityNumber_CacheWorks() + [Theory] + [InlineData(null, null)] + [InlineData("", "")] + [InlineData(" ", "")] + [InlineData("NoSpaces", "NoSpaces")] + [InlineData(" Hello World ", "HelloWorld")] + public void RemoveWhitespace_VariousInputs_ReturnsExpected(string input, string expected) { - var socialSecurityNumber = "08119043698"; - var firstCheck = socialSecurityNumber.IsValidSocialSecurityNumber(); - var secondCheck = socialSecurityNumber.IsValidSocialSecurityNumber(); // Should hit cache + // Act + var result = input.RemoveWhitespace(); - Assert.True(firstCheck); - Assert.True(secondCheck); + // Assert + Assert.Equal(expected, result); } [Theory] - [InlineData("12345678900")] - [InlineData("98765432100")] - [InlineData("11111111111")] - public void IsValidSocialSecurityNumber_InvalidNumbers_ReturnsFalse(string number) + [InlineData(null, false)] + [InlineData("", false)] + [InlineData("12345", false)] + [InlineData("12345678900", false)] + [InlineData("98765432100", false)] + [InlineData("08119043698", true)] + [InlineData("23017126016", true)] + [InlineData("04045325356", true)] + public void IsValidSocialSecurityNumber_ValidatesCorrectly(string socialSecurityNumber, bool expected) { - var result = number.IsValidSocialSecurityNumber(); - Assert.False(result); - } + // Act + var result = socialSecurityNumber.IsValidSocialSecurityNumber(); - [Theory] - [InlineData("")] - [InlineData(" ")] - [InlineData(null)] - [InlineData("12345")] - [InlineData("0811a043698")] - public void IsValidSocialSecurityNumber_InvalidFormat_ReturnsFalse(string number) - { - var result = number.IsValidSocialSecurityNumber(); - Assert.False(result); + // Assert + Assert.Equal(expected, result); } - [Theory] - [InlineData("08119043698")] - [InlineData("11111111111")] - public void IsValidSocialSecurityNumber_NoControlDigits(string socialSecurityNumber) + [Fact] + public void IsValidSocialSecurityNumber_CachedResult_UsesCache() { - var result = socialSecurityNumber.IsValidSocialSecurityNumber(false); - Assert.True(result || !result); - } + // Arrange + var ssn = "08119043698"; - [Theory] - [InlineData("08119043698")] - [InlineData("23017126016")] - [InlineData("03087937150")] - public void IsValidSocialSecurityNumber_ValidNumbers_ReturnsTrue(string number) - { - var result = number.IsValidSocialSecurityNumber(); - Assert.True(result); - } + // Act + var firstCheck = ssn.IsValidSocialSecurityNumber(); // First call + var secondCheck = ssn.IsValidSocialSecurityNumber(); // Should be cached - [Theory] - [InlineData("08119043698", true)] - [InlineData("12345678901", false)] - public void IsValidSocialSecurityNumber_WithControlDigits(string socialSecurityNumber, bool expected) - { - var result = socialSecurityNumber.IsValidSocialSecurityNumber(true); - Assert.Equal(expected, result); + // Assert + Assert.True(firstCheck); + Assert.True(secondCheck); // Cached result should match } [Fact] - public void RemoveWhitespace_EmptyOrNullInput_ReturnsInput() + public void IsValidSocialSecurityNumber_InvalidFormat_ReturnsFalse() { - Assert.Null(((string)null).RemoveWhitespace()); - Assert.Equal(string.Empty, " ".RemoveWhitespace()); + // Arrange + var invalidSsn = "0811a043698"; + + // Act + var result = invalidSsn.IsValidSocialSecurityNumber(); + + // Assert + Assert.False(result); } [Fact] - public void RemoveWhitespace_ValidInput_RemovesWhitespace() + public void CalculateControlDigits_WorksCorrectly() { - var result = " Hello World ".RemoveWhitespace(); - Assert.Equal("HelloWorld", result); + // Arrange + var firstNineDigits = "081190436"; + var expectedControlDigits = "98"; // Known correct control digits for this SSN + + // Act + var method = typeof(StringExtensions).GetMethod("CalculateControlDigits", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + var result = method.Invoke(null, [firstNineDigits]); + + // Assert + Assert.Equal(expectedControlDigits, result); } [Theory] - [InlineData("", "")] - [InlineData(" ", "")] - [InlineData(null, null)] - [InlineData("NoSpaces", "NoSpaces")] - [InlineData(" Hello World ", "HelloWorld")] - public void RemoveWhitespace_VariousInputs_ReturnsExpected(string input, string expected) + [InlineData("11113432278", 7)] + [InlineData("08114231372", 7)] + public void CalculateControlDigit_WorksCorrectly(string socialSecurityNumber, int expected) { - var result = input.RemoveWhitespace(); + // Arrange + var firstNineDigits = socialSecurityNumber[..9]; + var method = typeof(StringExtensions).GetMethod("CalculateControlDigit", BindingFlags.NonPublic | BindingFlags.Static); + var weightsFirst = new[] { 3, 7, 6, 1, 8, 9, 4, 5, 2 }; + + // Act + var result = method.Invoke(null, [firstNineDigits, weightsFirst]); + + // Assert Assert.Equal(expected, result); } } From 88b03e0068ad34e9a1de5fbb29adb3f649dfaa53 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 15:41:16 +0200 Subject: [PATCH 88/98] Cover more use cases --- .../Extensions/StringExtensionsTests.cs | 122 +++++++++++------- 1 file changed, 77 insertions(+), 45 deletions(-) diff --git a/test/Altinn.Profile.Tests/Profile.Core/Extensions/StringExtensionsTests.cs b/test/Altinn.Profile.Tests/Profile.Core/Extensions/StringExtensionsTests.cs index 04fa75b..30a9c70 100644 --- a/test/Altinn.Profile.Tests/Profile.Core/Extensions/StringExtensionsTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Core/Extensions/StringExtensionsTests.cs @@ -8,53 +8,55 @@ namespace Altinn.Profile.Tests.Core.Extensions; public class StringExtensionsTests { - [Theory] - [InlineData("", false)] - [InlineData(null, false)] - [InlineData("12345", true)] - [InlineData("123A456", false)] - [InlineData("123 456", false)] - [InlineData("1234567890", true)] - public void IsDigitsOnly_VariousInputs_ReturnsExpected(string input, bool expected) + [Fact] + public void CalculateControlDigits_WorksCorrectly() { + // Arrange + var firstNineDigits = "081190436"; + var expectedControlDigits = "98"; // Known correct control digits for this SSN + // Act - var result = input.IsDigitsOnly(); + var method = typeof(StringExtensions).GetMethod("CalculateControlDigits", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + var result = method.Invoke(null, [firstNineDigits]); // Assert - Assert.Equal(expected, result); + Assert.Equal(expectedControlDigits, result); } [Theory] - [InlineData(null, null)] - [InlineData("", "")] - [InlineData(" ", "")] - [InlineData("NoSpaces", "NoSpaces")] - [InlineData(" Hello World ", "HelloWorld")] - public void RemoveWhitespace_VariousInputs_ReturnsExpected(string input, string expected) + [InlineData("11113432278", 7)] + [InlineData("08114231372", 7)] + public void CalculateControlDigit_WorksCorrectly(string socialSecurityNumber, int expected) { + // Arrange + var firstNineDigits = socialSecurityNumber[..9]; + var method = typeof(StringExtensions).GetMethod("CalculateControlDigit", BindingFlags.NonPublic | BindingFlags.Static); + var weightsFirst = new[] { 3, 7, 6, 1, 8, 9, 4, 5, 2 }; + // Act - var result = input.RemoveWhitespace(); + var result = method.Invoke(null, [firstNineDigits, weightsFirst]); // Assert Assert.Equal(expected, result); } - [Theory] - [InlineData(null, false)] - [InlineData("", false)] - [InlineData("12345", false)] - [InlineData("12345678900", false)] - [InlineData("98765432100", false)] - [InlineData("08119043698", true)] - [InlineData("23017126016", true)] - [InlineData("04045325356", true)] - public void IsValidSocialSecurityNumber_ValidatesCorrectly(string socialSecurityNumber, bool expected) + [Fact] + public void IsValidSocialSecurityNumber_CacheReturnsCachedValue() { + // Assign + var ssn = "08119043698"; // Valid SSN + var expectedFirstValidation = true; // Expected result for first validation + // Act - var result = socialSecurityNumber.IsValidSocialSecurityNumber(); + // First check: This will validate the SSN and store the result in the cache. + var firstCheck = ssn.IsValidSocialSecurityNumber(); + + // Second check: This should return the cached result. + var secondCheck = ssn.IsValidSocialSecurityNumber(); // Assert - Assert.Equal(expected, result); + Assert.Equal(expectedFirstValidation, firstCheck); // Verify first validation result + Assert.Equal(expectedFirstValidation, secondCheck); // Verify cached result is returned } [Fact] @@ -85,33 +87,63 @@ public void IsValidSocialSecurityNumber_InvalidFormat_ReturnsFalse() Assert.False(result); } - [Fact] - public void CalculateControlDigits_WorksCorrectly() + [Theory] + [InlineData("08119", false)] + [InlineData("081190A3698", false)] + [InlineData("081190 3698", false)] + public void IsValidSocialSecurityNumber_InvalidIndividualNumberPart_ReturnsFalse(string socialSecurityNumber, bool expected) { - // Arrange - var firstNineDigits = "081190436"; - var expectedControlDigits = "98"; // Known correct control digits for this SSN + // Act + var result = socialSecurityNumber.IsValidSocialSecurityNumber(); + + // Assert + Assert.Equal(expected, result); + } + [Theory] + [InlineData(null, false)] + [InlineData("", false)] + [InlineData("12345", false)] + [InlineData("12345678900", false)] + [InlineData("98765432100", false)] + [InlineData("08119043698", true)] + [InlineData("23017126016", true)] + [InlineData("04045325356", true)] + public void IsValidSocialSecurityNumber_ValidatesCorrectly(string socialSecurityNumber, bool expected) + { // Act - var method = typeof(StringExtensions).GetMethod("CalculateControlDigits", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); - var result = method.Invoke(null, [firstNineDigits]); + var result = socialSecurityNumber.IsValidSocialSecurityNumber(); // Assert - Assert.Equal(expectedControlDigits, result); + Assert.Equal(expected, result); } [Theory] - [InlineData("11113432278", 7)] - [InlineData("08114231372", 7)] - public void CalculateControlDigit_WorksCorrectly(string socialSecurityNumber, int expected) + [InlineData("", false)] + [InlineData(null, false)] + [InlineData("12345", true)] + [InlineData("123A456", false)] + [InlineData("123 456", false)] + [InlineData("1234567890", true)] + public void IsDigitsOnly_VariousInputs_ReturnsExpected(string input, bool expected) { - // Arrange - var firstNineDigits = socialSecurityNumber[..9]; - var method = typeof(StringExtensions).GetMethod("CalculateControlDigit", BindingFlags.NonPublic | BindingFlags.Static); - var weightsFirst = new[] { 3, 7, 6, 1, 8, 9, 4, 5, 2 }; + // Act + var result = input.IsDigitsOnly(); + // Assert + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(null, null)] + [InlineData("", "")] + [InlineData(" ", "")] + [InlineData("NoSpaces", "NoSpaces")] + [InlineData(" Hello World ", "HelloWorld")] + public void RemoveWhitespace_VariousInputs_ReturnsExpected(string input, string expected) + { // Act - var result = method.Invoke(null, [firstNineDigits, weightsFirst]); + var result = input.RemoveWhitespace(); // Assert Assert.Equal(expected, result); From 2891cc7933ada731a149aaad48ddc22825e6aa94 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 16:30:05 +0200 Subject: [PATCH 89/98] Increase covered use cases --- .../Person/PersonRepositoryTests.cs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Person/PersonRepositoryTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Person/PersonRepositoryTests.cs index d11d59e..999b121 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Person/PersonRepositoryTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Person/PersonRepositoryTests.cs @@ -22,7 +22,7 @@ public class PersonRepositoryTests : IDisposable private readonly ProfileDbContext _context; private readonly PersonRepository _registerRepository; private readonly List _personContactAndReservationTestData; - + public PersonRepositoryTests() { var options = new DbContextOptionsBuilder() @@ -32,7 +32,7 @@ public PersonRepositoryTests() _context = new ProfileDbContext(options); _registerRepository = new PersonRepository(_context); - _personContactAndReservationTestData = [.. PersonTestData.GetContactAndReservationTestData()]; + _personContactAndReservationTestData = new List(PersonTestData.GetContactAndReservationTestData()); _context.People.AddRange(_personContactAndReservationTestData); _context.SaveChanges(); @@ -81,12 +81,23 @@ public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenNoneFound() Assert.Empty(result); } + [Fact] + public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenNotFound() + { + // Act + var result = await _registerRepository.GetContactDetailsAsync(["nonexistent", "11044314120"]); + + // Assert + Assert.Empty(result); + } + [Fact] public async Task GetUserContactInfoAsync_ReturnsMultipleContacts_WhenFound() { // Act var result = await _registerRepository.GetContactDetailsAsync(["24064316776", "11044314101"]); - var expected = _personContactAndReservationTestData.Where(e => e.FnumberAk == "24064316776" || e.FnumberAk == "11044314101"); + var expected = _personContactAndReservationTestData + .Where(e => e.FnumberAk == "24064316776" || e.FnumberAk == "11044314101"); // Assert Assert.Equal(2, result.Count); @@ -100,10 +111,10 @@ public async Task GetUserContactInfoAsync_ReturnsMultipleContacts_WhenFound() } [Fact] - public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenNotFound() + public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenNoNationalIdentityNumbersProvided() { // Act - var result = await _registerRepository.GetContactDetailsAsync(["nonexistent", "11044314120"]); + var result = await _registerRepository.GetContactDetailsAsync([]); // Assert Assert.Empty(result); From f58eb6454fc84ea9cab64cd68557434d99d2a635 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 16:34:13 +0200 Subject: [PATCH 90/98] Follow the same naming pattern --- .../Person/PersonRepositoryTests.cs | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/Person/PersonRepositoryTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/Person/PersonRepositoryTests.cs index 999b121..c1d1684 100644 --- a/test/Altinn.Profile.Tests/Profile.Integrations/Person/PersonRepositoryTests.cs +++ b/test/Altinn.Profile.Tests/Profile.Integrations/Person/PersonRepositoryTests.cs @@ -46,7 +46,7 @@ public void Dispose() } [Fact] - public async Task GetUserContactInfoAsync_ReturnsContactInfo_WhenFound() + public async Task GetContactDetailsAsync_WhenFound_ReturnsContactInfo() { // Act var result = await _registerRepository.GetContactDetailsAsync(["17111933790"]); @@ -60,64 +60,64 @@ public async Task GetUserContactInfoAsync_ReturnsContactInfo_WhenFound() } [Fact] - public async Task GetUserContactInfoAsync_ReturnsCorrectResults_WhenValidAndInvalidNumbers() + public async Task GetContactDetailsAsync_WhenMultipleContactsFound_ReturnsMultipleContacts() { // Act - var result = _personContactAndReservationTestData.Where(e => e.FnumberAk == "28026698350"); - var expected = await _registerRepository.GetContactDetailsAsync(["28026698350", "nonexistent2"]); + var result = await _registerRepository.GetContactDetailsAsync(["24064316776", "11044314101"]); + var expected = _personContactAndReservationTestData + .Where(e => e.FnumberAk == "24064316776" || e.FnumberAk == "11044314101"); - // Assert invalid result - Assert.Single(result); - AssertRegisterProperties(expected.FirstOrDefault(), result.FirstOrDefault()); + // Assert + Assert.Equal(2, result.Count); + + foreach (var register in result) + { + var foundRegister = expected.FirstOrDefault(r => r.FnumberAk == register.FnumberAk); + Assert.NotNull(foundRegister); + AssertRegisterProperties(register, foundRegister); + } } [Fact] - public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenNoneFound() + public async Task GetContactDetailsAsync_WhenNoNationalIdentityNumbersProvided_ReturnsEmpty() { // Act - var result = await _registerRepository.GetContactDetailsAsync(["nonexistent1", "nonexistent2"]); + var result = await _registerRepository.GetContactDetailsAsync([]); // Assert Assert.Empty(result); } [Fact] - public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenNotFound() + public async Task GetContactDetailsAsync_WhenNoneFound_ReturnsEmpty() { // Act - var result = await _registerRepository.GetContactDetailsAsync(["nonexistent", "11044314120"]); + var result = await _registerRepository.GetContactDetailsAsync(["nonexistent1", "nonexistent2"]); // Assert Assert.Empty(result); } [Fact] - public async Task GetUserContactInfoAsync_ReturnsMultipleContacts_WhenFound() + public async Task GetContactDetailsAsync_WhenNotFound_ReturnsEmpty() { // Act - var result = await _registerRepository.GetContactDetailsAsync(["24064316776", "11044314101"]); - var expected = _personContactAndReservationTestData - .Where(e => e.FnumberAk == "24064316776" || e.FnumberAk == "11044314101"); + var result = await _registerRepository.GetContactDetailsAsync(["nonexistent", "11044314120"]); // Assert - Assert.Equal(2, result.Count); - - foreach (var register in result) - { - var foundRegister = expected.FirstOrDefault(r => r.FnumberAk == register.FnumberAk); - Assert.NotNull(foundRegister); - AssertRegisterProperties(register, foundRegister); - } + Assert.Empty(result); } [Fact] - public async Task GetUserContactInfoAsync_ReturnsEmpty_WhenNoNationalIdentityNumbersProvided() + public async Task GetContactDetailsAsync_WhenValidAndInvalidNumbers_ReturnsCorrectResults() { // Act - var result = await _registerRepository.GetContactDetailsAsync([]); + var result = _personContactAndReservationTestData.Where(e => e.FnumberAk == "28026698350"); + var expected = await _registerRepository.GetContactDetailsAsync(["28026698350", "nonexistent2"]); - // Assert - Assert.Empty(result); + // Assert invalid result + Assert.Single(result); + AssertRegisterProperties(expected.FirstOrDefault(), result.FirstOrDefault()); } private static void AssertRegisterProperties(Person expected, Person actual) From 4227cbfd8fa9e77d56a50045c935b98344967b6c Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 16:43:49 +0200 Subject: [PATCH 91/98] Implement a number of simple unit test to test the mapping profile --- .../PersonContactDetailsProfileTests.cs | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 test/Altinn.Profile.Tests/Profile.Integrations/PersonContactDetailsProfileTests.cs diff --git a/test/Altinn.Profile.Tests/Profile.Integrations/PersonContactDetailsProfileTests.cs b/test/Altinn.Profile.Tests/Profile.Integrations/PersonContactDetailsProfileTests.cs new file mode 100644 index 0000000..d9bc8ce --- /dev/null +++ b/test/Altinn.Profile.Tests/Profile.Integrations/PersonContactDetailsProfileTests.cs @@ -0,0 +1,127 @@ +using Altinn.Profile.Integrations.Entities; +using Altinn.Profile.Integrations.Mappings; + +using AutoMapper; + +using Xunit; + +namespace Altinn.Profile.Tests.Profile.Integrations; + +public class PersonContactDetailsProfileTests +{ + private readonly IMapper _mapper; + + public PersonContactDetailsProfileTests() + { + var config = new MapperConfiguration(cfg => cfg.AddProfile()); + _mapper = config.CreateMapper(); + } + + [Fact] + public void Map_DifferentValues_CreatesCorrectMappings() + { + // Arrange + var person = new Person + { + Reservation = true, + LanguageCode = "no", + FnumberAk = "24021633239", + MobilePhoneNumber = "9876543210", + EmailAddress = "test@example.com", + }; + + // Act + var result = _mapper.Map(person); + + // Assert + Assert.True(result.IsReserved); + Assert.Equal(person.EmailAddress, result.EmailAddress); + Assert.Equal(person.LanguageCode, result.LanguageCode); + Assert.Equal(person.FnumberAk, result.NationalIdentityNumber); + Assert.Equal(person.MobilePhoneNumber, result.MobilePhoneNumber); + } + + [Fact] + public void Map_NullPerson_ReturnsNullPersonContactDetails() + { + // Arrange + Person person = null; + + // Act + var result = _mapper.Map(person); + + // Assert + Assert.Null(result); + } + + [Fact] + public void Map_OptionalProperties_WhenMissing_ReturnsDefaults() + { + // Arrange + var person = new Person + { + Reservation = false, + FnumberAk = "06082705358" + }; + + // Act + var result = _mapper.Map(person); + + // Assert + Assert.Null(result.LanguageCode); + Assert.Null(result.EmailAddress); + Assert.Null(result.MobilePhoneNumber); + Assert.Equal("06082705358", result.NationalIdentityNumber); + } + + [Fact] + public void Map_ReservationFalse_SetsIsReservedToFalse() + { + // Arrange + var person = new Person { Reservation = false }; + + // Act + var result = _mapper.Map(person); + + // Assert + Assert.False(result.IsReserved); + } + + [Fact] + public void Map_ReservationTrue_SetsIsReservedToTrue() + { + // Arrange + var person = new Person { Reservation = true }; + + // Act + var result = _mapper.Map(person); + + // Assert + Assert.True(result.IsReserved); + } + + [Fact] + public void Map_ValidPerson_ReturnsCorrectPersonContactDetails() + { + // Arrange + var person = new Person + { + Reservation = false, + LanguageCode = "en", + FnumberAk = "17080227000", + MobilePhoneNumber = "1234567890", + EmailAddress = "test@example.com" + }; + + // Act + var result = _mapper.Map(person); + + // Assert + Assert.NotNull(result); + Assert.False(result.IsReserved); + Assert.Equal(person.EmailAddress, result.EmailAddress); + Assert.Equal(person.LanguageCode, result.LanguageCode); + Assert.Equal(person.FnumberAk, result.NationalIdentityNumber); + Assert.Equal(person.MobilePhoneNumber, result.MobilePhoneNumber); + } +} From 98813fff6169c84de9be67ea4a1152547311e106 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 16:51:41 +0200 Subject: [PATCH 92/98] Delete the DROP DATABASE IF EXISTS statement --- src/Altinn.Profile.Integrations/Migration/profiledb.sql | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Altinn.Profile.Integrations/Migration/profiledb.sql b/src/Altinn.Profile.Integrations/Migration/profiledb.sql index b94539a..fe8e838 100644 --- a/src/Altinn.Profile.Integrations/Migration/profiledb.sql +++ b/src/Altinn.Profile.Integrations/Migration/profiledb.sql @@ -1,7 +1,4 @@ --- Drop the database if it exists -DROP DATABASE IF EXISTS profiledb; - --- Create the database +-- Create the database CREATE DATABASE profiledb WITH OWNER = postgres From 30dbaa8e6c371bf8b9f644049c9df4497c70120a Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 16:52:36 +0200 Subject: [PATCH 93/98] Delete an index that could be used to search for person data by a supplier identifier --- src/Altinn.Profile.Integrations/Migration/profiledb.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Altinn.Profile.Integrations/Migration/profiledb.sql b/src/Altinn.Profile.Integrations/Migration/profiledb.sql index fe8e838..4ce76c2 100644 --- a/src/Altinn.Profile.Integrations/Migration/profiledb.sql +++ b/src/Altinn.Profile.Integrations/Migration/profiledb.sql @@ -47,5 +47,4 @@ CREATE TABLE IF NOT EXISTS contact_and_reservation.person ( ); -- Indexes for performance -CREATE INDEX idx_mailbox_supplier_id_fk ON contact_and_reservation.person (mailbox_supplier_id_fk); CREATE INDEX idx_fnumber_ak ON contact_and_reservation.person (fnumber_ak); From 13de400596f80918f898732b5aa263a1c88b8aa9 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 16:54:26 +0200 Subject: [PATCH 94/98] Add statements to grant two users, we have, access to the schema --- src/Altinn.Profile.Integrations/Migration/profiledb.sql | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Altinn.Profile.Integrations/Migration/profiledb.sql b/src/Altinn.Profile.Integrations/Migration/profiledb.sql index 4ce76c2..27b08b7 100644 --- a/src/Altinn.Profile.Integrations/Migration/profiledb.sql +++ b/src/Altinn.Profile.Integrations/Migration/profiledb.sql @@ -13,6 +13,10 @@ CREATE DATABASE profiledb -- Create schema if it doesn't exist CREATE SCHEMA IF NOT EXISTS contact_and_reservation; +-- Grant access to the schema +GRANT ALL ON SCHEMA contact_and_reservation TO platform_profile_admin; +GRANT USAGE ON SCHEMA contact_and_reservation TO platform_profile; + -- Create table MailboxSupplier CREATE TABLE IF NOT EXISTS contact_and_reservation.mailbox_supplier ( mailbox_supplier_id INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY, From 74979bbff1fbcef415987740c59bc54a3cfd4147 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 17:01:52 +0200 Subject: [PATCH 95/98] Remove the database creation statement --- .../Migration/profiledb.sql | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Altinn.Profile.Integrations/Migration/profiledb.sql b/src/Altinn.Profile.Integrations/Migration/profiledb.sql index 27b08b7..c2382ae 100644 --- a/src/Altinn.Profile.Integrations/Migration/profiledb.sql +++ b/src/Altinn.Profile.Integrations/Migration/profiledb.sql @@ -1,16 +1,4 @@ --- Create the database -CREATE DATABASE profiledb - WITH - OWNER = postgres - ENCODING = 'UTF8' - LC_COLLATE = 'Norwegian_Norway.1252' - LC_CTYPE = 'Norwegian_Norway.1252' - LOCALE_PROVIDER = 'libc' - TABLESPACE = pg_default - CONNECTION LIMIT = -1 - IS_TEMPLATE = False; - --- Create schema if it doesn't exist +-- Create schema if it doesn't exist CREATE SCHEMA IF NOT EXISTS contact_and_reservation; -- Grant access to the schema From 2250863f898b3fb110c020679112ad335783b8f7 Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Wed, 9 Oct 2024 17:08:53 +0200 Subject: [PATCH 96/98] Regenerate the Person data model --- src/Altinn.Profile/Models/Person.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Altinn.Profile/Models/Person.cs b/src/Altinn.Profile/Models/Person.cs index ccdb48f..14d90f7 100644 --- a/src/Altinn.Profile/Models/Person.cs +++ b/src/Altinn.Profile/Models/Person.cs @@ -13,7 +13,6 @@ namespace Altinn.Profile.Models; /// [Table("person", Schema = "contact_and_reservation")] [Index("FnumberAk", Name = "idx_fnumber_ak")] -[Index("MailboxSupplierIdFk", Name = "idx_mailbox_supplier_id_fk")] [Index("FnumberAk", Name = "person_fnumber_ak_key", IsUnique = true)] public partial class Person { From 71e5c7cb3418c2eae8af1b12d2165e694866921e Mon Sep 17 00:00:00 2001 From: Ahmed-Ghanam Date: Mon, 14 Oct 2024 10:04:02 +0200 Subject: [PATCH 97/98] Move the data models to the integration project --- .../Models/DbContext/ProfiledbContext.cs | 77 ------------ src/Altinn.Profile/Models/MailboxSupplier.cs | 40 ------ src/Altinn.Profile/Models/Metadata.cs | 28 ----- src/Altinn.Profile/Models/Person.cs | 117 ------------------ src/Altinn.Profile/efpt.config.json | 54 -------- 5 files changed, 316 deletions(-) delete mode 100644 src/Altinn.Profile/Models/DbContext/ProfiledbContext.cs delete mode 100644 src/Altinn.Profile/Models/MailboxSupplier.cs delete mode 100644 src/Altinn.Profile/Models/Metadata.cs delete mode 100644 src/Altinn.Profile/Models/Person.cs delete mode 100644 src/Altinn.Profile/efpt.config.json diff --git a/src/Altinn.Profile/Models/DbContext/ProfiledbContext.cs b/src/Altinn.Profile/Models/DbContext/ProfiledbContext.cs deleted file mode 100644 index 657e01d..0000000 --- a/src/Altinn.Profile/Models/DbContext/ProfiledbContext.cs +++ /dev/null @@ -1,77 +0,0 @@ -// This file has been auto generated by EF Core Power Tools. -#nullable disable - -using Microsoft.EntityFrameworkCore; - -namespace Altinn.Profile.Models; - -/// -/// Represents the database context for the profile database. -/// -public partial class ProfileDbContext : DbContext -{ - /// - /// Initializes a new instance of the class. - /// - /// The options to be used by a . - public ProfileDbContext(DbContextOptions options) - : base(options) - { - } - - /// - /// Gets or sets the representing the mailbox suppliers. - /// - public virtual DbSet MailboxSuppliers { get; set; } - - /// - /// Gets or sets the representing the metadata. - /// - public virtual DbSet Metadata { get; set; } - - /// - /// Gets or sets the representing the people. - /// - public virtual DbSet People { get; set; } - - /// - /// Configures the schema needed for the context. - /// - /// The builder being used to construct the model for this context. - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.MailboxSupplierId).HasName("mailbox_supplier_pkey"); - - entity.Property(e => e.MailboxSupplierId).UseIdentityAlwaysColumn(); - entity.Property(e => e.OrgNumberAk).IsFixedLength(); - }); - - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.LatestChangeNumber).HasName("metadata_pkey"); - - entity.Property(e => e.LatestChangeNumber).ValueGeneratedNever(); - }); - - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.ContactAndReservationUserId).HasName("person_pkey"); - - entity.Property(e => e.ContactAndReservationUserId).UseIdentityAlwaysColumn(); - entity.Property(e => e.FnumberAk).IsFixedLength(); - entity.Property(e => e.LanguageCode).IsFixedLength(); - - entity.HasOne(d => d.MailboxSupplierIdFkNavigation).WithMany(p => p.People).HasConstraintName("fk_mailbox_supplier"); - }); - - OnModelCreatingPartial(modelBuilder); - } - - /// - /// A partial method that can be used to configure the model further. - /// - /// The builder being used to construct the model for this context. - partial void OnModelCreatingPartial(ModelBuilder modelBuilder); -} diff --git a/src/Altinn.Profile/Models/MailboxSupplier.cs b/src/Altinn.Profile/Models/MailboxSupplier.cs deleted file mode 100644 index 9a9230c..0000000 --- a/src/Altinn.Profile/Models/MailboxSupplier.cs +++ /dev/null @@ -1,40 +0,0 @@ -// This file has been auto generated by EF Core Power Tools. -#nullable disable - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -using Microsoft.EntityFrameworkCore; - -namespace Altinn.Profile.Models; - -/// -/// Represents a mailbox supplier in the contact and reservation schema. -/// -[Table("mailbox_supplier", Schema = "contact_and_reservation")] -[Index("OrgNumberAk", Name = "unique_org_number_ak", IsUnique = true)] -public partial class MailboxSupplier -{ - /// - /// Gets or sets the unique identifier for the mailbox supplier. - /// - [Key] - [Column("mailbox_supplier_id")] - public int MailboxSupplierId { get; set; } - - /// - /// Gets or sets the organization number of the mailbox supplier. - /// - [Required] - [Column("org_number_ak")] - [StringLength(9)] - public string OrgNumberAk { get; set; } - - /// - /// Gets or sets the collection of people associated with the mailbox supplier. - /// - [InverseProperty("MailboxSupplierIdFkNavigation")] - public virtual ICollection People { get; set; } = new List(); -} diff --git a/src/Altinn.Profile/Models/Metadata.cs b/src/Altinn.Profile/Models/Metadata.cs deleted file mode 100644 index aa15bc4..0000000 --- a/src/Altinn.Profile/Models/Metadata.cs +++ /dev/null @@ -1,28 +0,0 @@ -// This file has been auto generated by EF Core Power Tools. -#nullable disable - -using System; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Altinn.Profile.Models; - -/// -/// Represents metadata in the contact and reservation schema. -/// -[Table("metadata", Schema = "contact_and_reservation")] -public partial class Metadata -{ - /// - /// Gets or sets the latest change number. - /// - [Key] - [Column("latest_change_number")] - public long LatestChangeNumber { get; set; } - - /// - /// Gets or sets the date and time when the metadata was exported. - /// - [Column("exported")] - public DateTime? Exported { get; set; } -} diff --git a/src/Altinn.Profile/Models/Person.cs b/src/Altinn.Profile/Models/Person.cs deleted file mode 100644 index 14d90f7..0000000 --- a/src/Altinn.Profile/Models/Person.cs +++ /dev/null @@ -1,117 +0,0 @@ -// This file has been auto generated by EF Core Power Tools. -#nullable disable - -using System; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -using Microsoft.EntityFrameworkCore; - -namespace Altinn.Profile.Models; -/// -/// Represents a person in the contact and reservation schema. -/// -[Table("person", Schema = "contact_and_reservation")] -[Index("FnumberAk", Name = "idx_fnumber_ak")] -[Index("FnumberAk", Name = "person_fnumber_ak_key", IsUnique = true)] -public partial class Person -{ - /// - /// Gets or sets the unique identifier for the contact and reservation user. - /// - [Key] - [Column("contact_and_reservation_user_id")] - public int ContactAndReservationUserId { get; set; } - - /// - /// Gets or sets the F-number (a unique identifier) of the person. - /// - [Required] - [Column("fnumber_ak")] - [StringLength(11)] - public string FnumberAk { get; set; } - - /// - /// Gets or sets a value indicating whether the person has a reservation. - /// - [Column("reservation")] - public bool? Reservation { get; set; } - - /// - /// Gets or sets the description of the person. - /// - [Column("description")] - [StringLength(20)] - public string Description { get; set; } - - /// - /// Gets or sets the mobile phone number of the person. - /// - [Column("mobile_phone_number")] - [StringLength(20)] - public string MobilePhoneNumber { get; set; } - - /// - /// Gets or sets the date and time when the mobile phone number was last updated. - /// - [Column("mobile_phone_number_last_updated")] - public DateTime? MobilePhoneNumberLastUpdated { get; set; } - - /// - /// Gets or sets the date and time when the mobile phone number was last verified. - /// - [Column("mobile_phone_number_last_verified")] - public DateTime? MobilePhoneNumberLastVerified { get; set; } - - /// - /// Gets or sets the email address of the person. - /// - [Column("email_address")] - [StringLength(400)] - public string EmailAddress { get; set; } - - /// - /// Gets or sets the date and time when the email address was last updated. - /// - [Column("email_address_last_updated")] - public DateTime? EmailAddressLastUpdated { get; set; } - - /// - /// Gets or sets the date and time when the email address was last verified. - /// - [Column("email_address_last_verified")] - public DateTime? EmailAddressLastVerified { get; set; } - - /// - /// Gets or sets the mailbox address of the person. - /// - [Column("mailbox_address")] - [StringLength(50)] - public string MailboxAddress { get; set; } - - /// - /// Gets or sets the foreign key to the mailbox supplier. - /// - [Column("mailbox_supplier_id_fk")] - public int? MailboxSupplierIdFk { get; set; } - - /// - /// Gets or sets the X.509 certificate of the person. - /// - [Column("x509_certificate")] - public string X509Certificate { get; set; } - - /// - /// Gets or sets the language code of the person. - /// - [Column("language_code")] - [StringLength(2)] - public string LanguageCode { get; set; } - - /// - /// Gets or sets the navigation property to the mailbox supplier. - /// - [ForeignKey("MailboxSupplierIdFk")] - [InverseProperty("People")] - public virtual MailboxSupplier MailboxSupplierIdFkNavigation { get; set; } -} diff --git a/src/Altinn.Profile/efpt.config.json b/src/Altinn.Profile/efpt.config.json deleted file mode 100644 index ac5afc4..0000000 --- a/src/Altinn.Profile/efpt.config.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "CodeGenerationMode": 4, - "ContextClassName": "ProfileDbContext", - "ContextNamespace": null, - "FilterSchemas": false, - "IncludeConnectionString": false, - "ModelNamespace": null, - "OutputContextPath": null, - "OutputPath": "Models", - "PreserveCasingWithRegex": true, - "ProjectRootNamespace": "Altinn.Profile", - "Schemas": null, - "SelectedHandlebarsLanguage": 2, - "SelectedToBeGenerated": 0, - "T4TemplatePath": null, - "Tables": [ - { - "Name": "contact_and_reservation.mailbox_supplier", - "ObjectType": 0 - }, - { - "Name": "contact_and_reservation.metadata", - "ObjectType": 0 - }, - { - "Name": "contact_and_reservation.person", - "ObjectType": 0 - } - ], - "UiHint": null, - "UncountableWords": null, - "UseAsyncStoredProcedureCalls": true, - "UseBoolPropertiesWithoutDefaultSql": false, - "UseDatabaseNames": false, - "UseDateOnlyTimeOnly": true, - "UseDbContextSplitting": false, - "UseDecimalDataAnnotationForSprocResult": true, - "UseFluentApiOnly": false, - "UseHandleBars": false, - "UseHierarchyId": false, - "UseInflector": true, - "UseLegacyPluralizer": false, - "UseManyToManyEntity": false, - "UseNoDefaultConstructor": false, - "UseNoNavigations": false, - "UseNoObjectFilter": false, - "UseNodaTime": false, - "UseNullableReferences": false, - "UsePrefixNavigationNaming": false, - "UseSchemaFolders": false, - "UseSchemaNamespaces": false, - "UseSpatial": false, - "UseT4": false -} \ No newline at end of file From 3398e40c7a9406b76534b3ca7e1335602c496353 Mon Sep 17 00:00:00 2001 From: Terje Holene Date: Mon, 14 Oct 2024 13:59:05 +0200 Subject: [PATCH 98/98] Fix connection string --- .../Extensions/ConfigurationExtensions.cs | 6 +++--- src/Altinn.Profile/appsettings.json | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Altinn.Profile.Integrations/Extensions/ConfigurationExtensions.cs b/src/Altinn.Profile.Integrations/Extensions/ConfigurationExtensions.cs index df8b1ab..f80b7c8 100644 --- a/src/Altinn.Profile.Integrations/Extensions/ConfigurationExtensions.cs +++ b/src/Altinn.Profile.Integrations/Extensions/ConfigurationExtensions.cs @@ -7,9 +7,9 @@ namespace Altinn.Profile.Integrations.Extensions; /// public static class ConfigurationExtensions { - private const string ProfileDbAdminUserNameKey = "PostgreSqlSettings--ProfileDbAdminUserName"; - private const string ProfileDbAdminPasswordKey = "PostgreSqlSettings--ProfileDbAdminPassword"; - private const string ProfileDbConnectionStringKey = "PostgreSqlSettings--ProfileDbConnectionString"; + private const string ProfileDbAdminUserNameKey = "PostgreSqlSettings:ProfileDbAdminUserName"; + private const string ProfileDbAdminPasswordKey = "PostgreSqlSettings:ProfileDbAdminPassword"; + private const string ProfileDbConnectionStringKey = "PostgreSqlSettings:ProfileDbConnectionString"; /// /// Retrieves the database connection string from the configuration. diff --git a/src/Altinn.Profile/appsettings.json b/src/Altinn.Profile/appsettings.json index d2b678e..40c9f24 100644 --- a/src/Altinn.Profile/appsettings.json +++ b/src/Altinn.Profile/appsettings.json @@ -8,5 +8,10 @@ }, "CoreSettings": { "ProfileCacheLifetimeSeconds": 600 + }, + "PostgreSqlSettings": { + "ProfileDbAdminUserName": "from keyvault", + "ProfileDbAdminPassword": "from keyvault", + "ProfileDbConnectionString": "Host=localhost;Port=5432;Database=profiledb;Username={0};Password={1};" } }