Skip to content

Commit

Permalink
Profile integration for contact point lookup by national identity num…
Browse files Browse the repository at this point in the history
…ber (#462)

* Added profile client for retrieving contact points

* Added missing endpoint

* implemented tests - not run -kafka not working

* added unit tests

* added missing changes in profile client

* fixed code smells

* undid changes in int tests

* fixed options for settings

* added unit test for exception

* fixed indentation

* removed unused reference

* altinnservicesettings -> platformsettings
  • Loading branch information
acn-sbuad authored Mar 13, 2024
1 parent 532f155 commit 8e11d1f
Show file tree
Hide file tree
Showing 16 changed files with 487 additions and 5 deletions.
23 changes: 23 additions & 0 deletions src/Altinn.Notifications.Core/Integrations/IProfileClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Altinn.Notifications.Core.Models.ContactPoints;

namespace Altinn.Notifications.Core.Integrations;

/// <summary>
/// Interface describing a client for the profile service
/// </summary>
public interface IProfileClient
{
/// <summary>
/// Retrieves contact points for a list of users corresponding to a list of national identity numbers
/// </summary>
/// <param name="nationalIdentityNumbers">A list of national identity numbers to look up contact points for</param>
/// <returns>A list of contact points for the provided national identity numbers </returns>
public Task<List<UserContactPoints>> GetUserContactPoints(List<string> nationalIdentityNumbers);

/// <summary>
/// Retrieves contact point availability for a list of users corresponding to a list of national identity numbers
/// </summary>
/// <param name="nationalIdentityNumbers">A list of national identity numbers to look up contact point availability for</param>
/// <returns>A list of <see cref="UserContactPointAvailability"/> for the provided national identity numbers </returns>
public Task<List<UserContactPointAvailability>> GetUserContactPointAvailabilities(List<string> nationalIdentityNumbers);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public static class JsonSerializerOptionsProvider
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Converters = { new JsonStringEnumConverter() }
Converters = { new JsonStringEnumConverter() },
PropertyNameCaseInsensitive = true
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Altinn.Notifications.Core.Models.ContactPoints;

/// <summary>
/// Class describing the contact points of a user
/// </summary>
public class UserContactPointAvailability
{
/// <summary>
/// Gets or sets the ID of the user
/// </summary>
public int UserId { get; set; }

/// <summary>
/// Gets or sets the national identityt number of the user
/// </summary>
public string NationalIdentityNumber { get; set; } = string.Empty;

/// <summary>
/// Gets or sets a boolean indicating whether the user has reserved themselves from electronic communication
/// </summary>
public bool IsReserved { get; set; }

/// <summary>
/// Gets or sets a boolean indicating whether the user has registered a mobile number
/// </summary>
public bool MobileNumberRegistered { get; set; }

/// <summary>
/// Gets or sets a boolean indicating whether the user has registered an email address
/// </summary>
public bool EmailRegistered { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Altinn.Notifications.Core.Models.ContactPoints;

/// <summary>
/// Class describing the availability of contact points for a user
/// </summary>
public class UserContactPoints
{
/// <summary>
/// Gets or sets the ID of the user
/// </summary>
public int UserId { get; set; }

/// <summary>
/// Gets or sets the national identityt number of the user
/// </summary>
public string NationalIdentityNumber { get; set; } = string.Empty;

/// <summary>
/// Gets or sets a boolean indicating whether the user has reserved themselves from electronic communication
/// </summary>
public bool IsReserved { get; set; }

/// <summary>
/// Gets or sets the mobile number
/// </summary>
public string MobileNumber { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the email address
/// </summary>
public string Email { get; set; } = string.Empty;
}
36 changes: 36 additions & 0 deletions src/Altinn.Notifications.Core/Shared/PlatformHttpException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace Altinn.Notifications.Core.Shared;

/// <summary>
/// Exception class to hold exceptions when interacting with other Altinn platform REST services
/// </summary>
public class PlatformHttpException : Exception
{
/// <summary>
/// Responsible for holding an http request exception towards platform (storage).
/// </summary>
public HttpResponseMessage Response { get; }

/// <summary>
/// Copy the response for further investigations
/// </summary>
/// <param name="response">the response</param>
/// <param name="message">A description of the cause of the exception.</param>
public PlatformHttpException(HttpResponseMessage response, string message) : base(message)
{
this.Response = response;
}

/// <summary>
/// Create a new <see cref="PlatformHttpException"/> by reading the <see cref="HttpResponseMessage"/>
/// content asynchronously.
/// </summary>
/// <param name="response">The <see cref="HttpResponseMessage"/> to read.</param>
/// <returns>A new <see cref="PlatformHttpException"/>.</returns>
public static async Task<PlatformHttpException> CreateAsync(HttpResponseMessage response)
{
string content = await response.Content.ReadAsStringAsync();
string message = $"{(int)response.StatusCode} - {response.ReasonPhrase} - {content}";

return new PlatformHttpException(response, message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Altinn.Notifications.Integrations.Configuration;

/// <summary>
/// Configuration object used to hold settings for all Altinn Platform integrations.
/// </summary>
public class PlatformSettings
{
/// <summary>
/// Gets or sets the url for the API profile endpoint
/// </summary>
public string ApiProfileEndpoint { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Altinn.Notifications.Core.Integrations;
using Altinn.Notifications.Integrations.Clients;
using Altinn.Notifications.Integrations.Configuration;
using Altinn.Notifications.Integrations.Health;
using Altinn.Notifications.Integrations.Kafka.Consumers;
Expand Down Expand Up @@ -35,6 +36,22 @@ public static void AddKafkaServices(this IServiceCollection services, IConfigura
.Configure<KafkaSettings>(config.GetSection(nameof(KafkaSettings)));
}

/// <summary>
/// Adds Altinn clients and configurations to DI container.
/// </summary>
/// <param name="services">service collection.</param>
/// <param name="config">the configuration collection</param>
public static void AddAltinnClients(this IServiceCollection services, IConfiguration config)
{
_ = config.GetSection(nameof(PlatformSettings))
.Get<PlatformSettings>()
?? throw new ArgumentNullException(nameof(config), "Required AltinnServiceSettings is missing from application configuration");

services
.Configure<PlatformSettings>(config.GetSection(nameof(PlatformSettings)))
.AddHttpClient<IProfileClient, ProfileClient>();
}

/// <summary>
/// Adds kafka health checks
/// </summary>
Expand Down
74 changes: 74 additions & 0 deletions src/Altinn.Notifications.Integrations/Profile/ProfileClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Text;
using System.Text.Json;

using Altinn.Notifications.Core;
using Altinn.Notifications.Core.Integrations;
using Altinn.Notifications.Core.Models.ContactPoints;
using Altinn.Notifications.Core.Shared;
using Altinn.Notifications.Integrations.Configuration;
using Altinn.Notifications.Integrations.Profile;

using Microsoft.Extensions.Options;

namespace Altinn.Notifications.Integrations.Clients;

/// <summary>
/// Implementation of the <see cref="IProfileClient"/>
/// </summary>
public class ProfileClient : IProfileClient
{
private readonly HttpClient _client;

/// <summary>
/// Initializes a new instance of the <see cref="ProfileClient"/> class.
/// </summary>
public ProfileClient(HttpClient client, IOptions<PlatformSettings> settings)
{
_client = client;
_client.BaseAddress = new Uri(settings.Value.ApiProfileEndpoint);
}

/// <inheritdoc/>
public async Task<List<UserContactPoints>> GetUserContactPoints(List<string> nationalIdentityNumbers)
{
var lookupObject = new UserContactPointLookup
{
NationalIdentityNumbers = nationalIdentityNumbers
};

HttpContent content = new StringContent(JsonSerializer.Serialize(lookupObject, JsonSerializerOptionsProvider.Options), Encoding.UTF8, "application/json");

var response = await _client.PostAsync("users/contactpoint/lookup", content);

if (!response.IsSuccessStatusCode)
{
throw new PlatformHttpException(response, $"ProfileClient.GetUserContactPoints failed with status code {response.StatusCode}");
}

string responseContent = await response.Content.ReadAsStringAsync();
List<UserContactPoints>? contactPoints = JsonSerializer.Deserialize<UserContactPointsList>(responseContent, JsonSerializerOptionsProvider.Options)!.ContactPointList;
return contactPoints!;
}

/// <inheritdoc/>
public async Task<List<UserContactPointAvailability>> GetUserContactPointAvailabilities(List<string> nationalIdentityNumbers)
{
var lookupObject = new UserContactPointLookup
{
NationalIdentityNumbers = nationalIdentityNumbers
};

HttpContent content = new StringContent(JsonSerializer.Serialize(lookupObject, JsonSerializerOptionsProvider.Options), Encoding.UTF8, "application/json");

var response = await _client.PostAsync("users/contactpoint/availability", content);

if (!response.IsSuccessStatusCode)
{
throw new PlatformHttpException(response, $"ProfileClient.GetUserContactPointAvailabilities failed with status code {response.StatusCode}");
}

string responseContent = await response.Content.ReadAsStringAsync();
List<UserContactPointAvailability>? contactPoints = JsonSerializer.Deserialize<UserContactPointAvailabilityList>(responseContent, JsonSerializerOptionsProvider.Options)!.AvailabilityList;
return contactPoints!;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Altinn.Notifications.Core.Models.ContactPoints;

namespace Altinn.Notifications.Integrations.Profile;

/// <summary>
/// A list representation of <see cref="UserContactPointAvailability"/>
/// </summary>
public class UserContactPointAvailabilityList
{
/// <summary>
/// A list containing contact point availabiliy for users
/// </summary>
public List<UserContactPointAvailability> AvailabilityList { get; set; } = [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Altinn.Notifications.Integrations.Profile;

/// <summary>
/// A class respresenting a user contact point lookup object
/// </summary>
public class UserContactPointLookup
{
/// <summary>
/// A list of national identity numbers to look up contact points or contact point availability for
/// </summary>
public List<string> NationalIdentityNumbers { get; set; } = [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Altinn.Notifications.Core.Models.ContactPoints;

namespace Altinn.Notifications.Integrations.Profile;

/// <summary>
/// A list representation of <see cref="UserContactPoints"/>
/// </summary>
public class UserContactPointsList
{
/// <summary>
/// A list containing contact points for users
/// </summary>
public List<UserContactPoints> ContactPointList { get; set; } = [];
}
1 change: 1 addition & 0 deletions src/Altinn.Notifications/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ void ConfigureServices(IServiceCollection services, IConfiguration config)
services.AddCoreServices(config);

services.AddKafkaServices(config);
services.AddAltinnClients(config);
services.AddPostgresRepositories(config);
}

Expand Down
6 changes: 2 additions & 4 deletions src/Altinn.Notifications/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{
"PlatformSettings": {
"ProfileEndpointAddress": "url",
"ProfileSubscriptionKeyHeaderName": "Ocp-Apim-Subscription-Key",
"ProfileSubscriptionKey": "obtained at runtime"
"ApiProfileEndpoint": "http://localhost:5101/profil/api/v1/"
},
"PostgreSQLSettings": {
"MigrationScriptPath": "Migration",
Expand All @@ -14,7 +12,7 @@
},
"NotificationOrderConfig": {
"DefaultEmailFromAddress": "[email protected]",
"DefaultSmsSenderNumber": "Altinn"
"DefaultSmsSenderNumber": "Altinn"
},
"KafkaSettings": {
"BrokerAddress": "localhost:9092",
Expand Down
28 changes: 28 additions & 0 deletions test/Altinn.Notifications.Tests/DelegatingHandlerStub.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace Altinn.Notifications.IntegrationTests
{
public class DelegatingHandlerStub : DelegatingHandler
{
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;

public DelegatingHandlerStub()
{
_handlerFunc = (request, cancellationToken) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
}

public DelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc)
{
_handlerFunc = handlerFunc;
}

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return _handlerFunc(request, cancellationToken);
}
}
}
Loading

0 comments on commit 8e11d1f

Please sign in to comment.