Skip to content

Commit

Permalink
ClientFactory improvements; tests refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
tl-Roberto-Mancinelli committed Dec 23, 2024
1 parent a9ea66a commit a0fb393
Show file tree
Hide file tree
Showing 13 changed files with 267 additions and 397 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ public void ConfigureServices(IServiceCollection services)
Use keyed version of TrueLayer client (.NET 9.0/.NET 8.0):

```c#
.AddKeyedTrueLayer(configuration, options =>
.AddKeyedTrueLayer("TrueLayerGbp",
configuration,
options =>
{
// For demo purposes only. Private key should be stored securely
var privateKey = File.ReadAllText("ec512-private-key.pem");
Expand All @@ -136,10 +138,10 @@ Use keyed version of TrueLayer client (.NET 9.0/.NET 8.0):
options.Payments.SigningKey.PrivateKey = privateKey;
}
},
configurationSectionName: "TrueLayerGbp",
serviceKey: "TrueLayerGbp",
authTokenCachingStrategy: AuthTokenCachingStrategies.InMemory)
.AddKeyedTrueLayer(configuration, options =>
.AddKeyedTrueLayer("TrueLayerEur",
configuration,
options =>
{
// For demo purposes only. Private key should be stored securely
var privateKey = File.ReadAllText("ec512-private-key.pem");
Expand All @@ -148,8 +150,6 @@ Use keyed version of TrueLayer client (.NET 9.0/.NET 8.0):
options.Payments.SigningKey.PrivateKey = privateKey;
}
},
configurationSectionName: "TrueLayerEur",
serviceKey: "TrueLayerEur",
authTokenCachingStrategy: AuthTokenCachingStrategies.InMemory)
```

Expand Down
53 changes: 5 additions & 48 deletions src/TrueLayer/TrueLayerClientFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,65 +12,22 @@
namespace TrueLayer
{
internal class TrueLayerClientFactory
{
private readonly IApiClient _apiClient;
private readonly IOptions<TrueLayerOptions> _options;
private readonly IAuthTokenCache _authTokenCache;

public TrueLayerClientFactory(IApiClient apiClient, IOptions<TrueLayerOptions> options, IAuthTokenCache authTokenCache)
{
options.NotNull(nameof(options));
_apiClient = apiClient;
_options = options;
_authTokenCache = authTokenCache;
}

public ITrueLayerClient Create()
{
var options = _options.Value;
var auth = new AuthApi(_apiClient, options);

return new TrueLayerClient(
auth,
new Lazy<IPaymentsApi>(() => new PaymentsApi(_apiClient, auth, options)),
new Lazy<IPaymentsProvidersApi>(() => new PaymentsProvidersApi(_apiClient, auth, options)),
new Lazy<IPayoutsApi>(() => new PayoutsApi(_apiClient, auth, options)),
new Lazy<IMerchantAccountsApi>(() => new MerchantAccountsApi(_apiClient, auth, options)),
new Lazy<IMandatesApi>(() => new MandatesApi(_apiClient, auth, options)));
}

public ITrueLayerClient CreateWithCache()
{
var options = _options.Value;
var decoratedAuthApi = new AuthApiCacheDecorator(new AuthApi(_apiClient, options), _authTokenCache, options);

return new TrueLayerClient(
decoratedAuthApi,
new Lazy<IPaymentsApi>(() => new PaymentsApi(_apiClient, decoratedAuthApi, options)),
new Lazy<IPaymentsProvidersApi>(() => new PaymentsProvidersApi(_apiClient, decoratedAuthApi, options)),
new Lazy<IPayoutsApi>(() => new PayoutsApi(_apiClient, decoratedAuthApi, options)),
new Lazy<IMerchantAccountsApi>(() => new MerchantAccountsApi(_apiClient, decoratedAuthApi, options)),
new Lazy<IMandatesApi>(() => new MandatesApi(_apiClient, decoratedAuthApi, options)));
}
}

internal class TrueLayerKeyedClientFactory
{
private readonly IApiClient _apiClient;
private readonly IOptionsFactory<TrueLayerOptions> _options;
private readonly IAuthTokenCache _authTokenCache;

public TrueLayerKeyedClientFactory(IApiClient apiClient, IOptionsFactory<TrueLayerOptions> options, IAuthTokenCache authTokenCache)
public TrueLayerClientFactory(IApiClient apiClient, IOptionsFactory<TrueLayerOptions> options, IAuthTokenCache authTokenCache)
{
options.NotNull(nameof(options));
_apiClient = apiClient;
_options = options;
_authTokenCache = authTokenCache;
}

public ITrueLayerClient CreateKeyed(string serviceKey)
public ITrueLayerClient Create(string configName)
{
var options = _options.Create(serviceKey);
var options = _options.Create(configName);
var auth = new AuthApi(_apiClient, options);

return new TrueLayerClient(
Expand All @@ -82,9 +39,9 @@ public ITrueLayerClient CreateKeyed(string serviceKey)
new Lazy<IMandatesApi>(() => new MandatesApi(_apiClient, auth, options)));
}

public ITrueLayerClient CreateWithCacheKeyed(string serviceKey)
public ITrueLayerClient CreateWithCache(string configName)
{
var options = _options.Create(serviceKey);
var options =_options.Create(configName);
var decoratedAuthApi = new AuthApiCacheDecorator(new AuthApi(_apiClient, options), _authTokenCache, options);

return new TrueLayerClient(
Expand Down
36 changes: 20 additions & 16 deletions src/TrueLayer/TrueLayerServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static IServiceCollection AddTrueLayer(
if (services is null) throw new ArgumentNullException(nameof(services));
if (configuration is null) throw new ArgumentNullException(nameof(configuration));

services.Configure<TrueLayerOptions>(options =>
services.Configure<TrueLayerOptions>(configurationSectionName, options =>
{
configuration.GetSection(configurationSectionName).Bind(options);
configureOptions?.Invoke(options);
Expand All @@ -49,80 +49,84 @@ public static IServiceCollection AddTrueLayer(
{
case AuthTokenCachingStrategies.None:
services.AddSingleton<IAuthTokenCache, NullMemoryCache>();
services.AddTransient<ITrueLayerClient>(s => s.GetRequiredService<TrueLayerClientFactory>().Create());
services.AddTransient<ITrueLayerClient>(s => s.GetRequiredService<TrueLayerClientFactory>().Create(configurationSectionName));
break;
case AuthTokenCachingStrategies.InMemory:
services.AddMemoryCache();
services.AddSingleton<IAuthTokenCache, InMemoryAuthTokenCache>();
services.AddTransient<ITrueLayerClient>(x => x.GetRequiredService<TrueLayerClientFactory>().CreateWithCache());
services.AddTransient<ITrueLayerClient>(x => x.GetRequiredService<TrueLayerClientFactory>().CreateWithCache(configurationSectionName));
break;
case AuthTokenCachingStrategies.Custom:
services.AddTransient<ITrueLayerClient>(x => x.GetRequiredService<TrueLayerClientFactory>().CreateWithCache());
services.AddTransient<ITrueLayerClient>(x => x.GetRequiredService<TrueLayerClientFactory>().CreateWithCache(configurationSectionName));
break;
}

return services;
}

#if NET8_0_OR_GREATER
/// <summary>
/// Registers the keyed TrueLayer SDK services to the provided <paramref name="services"/>.
/// Required for multi client support.
/// </summary>
/// <param name="services">The service collection to add to.</param>
/// <param name="serviceKey">Key used to register TrueLayer client in DI.
/// Also used as <paramref name="configurationSectionName"/> if the parameter is not specified </param>
/// <param name="configuration">The Microsoft configuration used to obtain the TrueLayer SDK configuration.</param>
/// <param name="configureOptions">Action to customise the TrueLayer options created from configuration.</param>
/// <param name="configureBuilder">Action to override the HttpClientBuilder.</param>
/// <param name="configurationSectionName">Name of configuration section used to build the TrueLayer client</param>
/// <param name="serviceKey">Key used to register TrueLayer client in DI</param>
/// <param name="authTokenCachingStrategy">Caching strategy for auth token</param>
/// <returns>The service collection with registered TrueLayer SDK services.</returns>
public static IServiceCollection AddKeyedTrueLayer(
this IServiceCollection services,
string serviceKey,
IConfiguration configuration,
Action<TrueLayerOptions>? configureOptions = null,
Action<IHttpClientBuilder>? configureBuilder = null,
string configurationSectionName = "TrueLayer",
string serviceKey = "TrueLayerClient",
string? configurationSectionName = null,
AuthTokenCachingStrategies authTokenCachingStrategy = AuthTokenCachingStrategies.None)
{
if (services is null) throw new ArgumentNullException(nameof(services));
if (configuration is null) throw new ArgumentNullException(nameof(configuration));

services.Configure<TrueLayerOptions>(serviceKey, options =>
var configName = configurationSectionName ?? serviceKey;
services.Configure<TrueLayerOptions>(configName, options =>
{
configuration.GetSection(configurationSectionName).Bind(options);
configuration.GetSection(configName).Bind(options);
configureOptions?.Invoke(options);
options.Validate();
});

IHttpClientBuilder httpClientBuilder = services.AddHttpClient<IApiClient, ApiClient>();
configureBuilder?.Invoke(httpClientBuilder);

services.AddKeyedTransient<TrueLayerKeyedClientFactory>(serviceKey);
services.AddKeyedTransient<TrueLayerClientFactory>(serviceKey);

switch (authTokenCachingStrategy)
{
case AuthTokenCachingStrategies.None:
services.AddSingleton<IAuthTokenCache, NullMemoryCache>();
services.AddKeyedTransient<ITrueLayerClient>(serviceKey,
(x, _) => x.GetRequiredKeyedService<TrueLayerKeyedClientFactory>(serviceKey)
.CreateKeyed(serviceKey));
(x, _) => x.GetRequiredKeyedService<TrueLayerClientFactory>(serviceKey)
.Create(configName));
break;
case AuthTokenCachingStrategies.InMemory:
services.AddMemoryCache();
services.AddSingleton<IAuthTokenCache, InMemoryAuthTokenCache>();
services.AddKeyedTransient<ITrueLayerClient>(serviceKey,
(x, _) => x.GetRequiredKeyedService<TrueLayerKeyedClientFactory>(serviceKey)
.CreateWithCacheKeyed(serviceKey));
(x, _) => x.GetRequiredKeyedService<TrueLayerClientFactory>(serviceKey)
.CreateWithCache(configName));
break;
case AuthTokenCachingStrategies.Custom:
services.AddKeyedTransient<ITrueLayerClient>(serviceKey,
(x, _) => x.GetRequiredKeyedService<TrueLayerKeyedClientFactory>(serviceKey)
.CreateWithCacheKeyed(serviceKey));
(x, _) => x.GetRequiredKeyedService<TrueLayerClientFactory>(serviceKey)
.CreateWithCache(configName));
break;
}

return services;
}
#endif
}
}
47 changes: 33 additions & 14 deletions test/TrueLayer.AcceptanceTests/ApiTestFixture.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TrueLayer.Auth;
using TrueLayer.Caching;

namespace TrueLayer.AcceptanceTests
Expand All @@ -14,42 +15,52 @@ public ApiTestFixture()
{
IConfiguration configuration = LoadConfiguration();

const string configName1 = "TrueLayer";
const string configName2 = "TrueLayer2";
const string serviceKey1 = "TrueLayerClient";
const string serviceKey1 = "TrueLayer";
const string serviceKey2 = "TrueLayerClient2";
const string configName2 = "TrueLayer2";

ServiceProvider = new ServiceCollection()
.AddKeyedTrueLayer(configuration, options =>
.AddKeyedTrueLayer(serviceKey1,
configuration,
options =>
{
var privateKey = File.ReadAllText("ec512-private-key.pem");
if (options.Payments?.SigningKey != null)
{
options.Payments.SigningKey.PrivateKey = privateKey;
}
},
configurationSectionName: configName1,
serviceKey: serviceKey1)
.AddKeyedTrueLayer(configuration, options =>
authTokenCachingStrategy: AuthTokenCachingStrategies.InMemory)
.AddKeyedTrueLayer(serviceKey2,
configuration,
options =>
{
var privateKey = File.ReadAllText("ec512-private-key.pem");
var privateKey = File.ReadAllText("ec512-private-key-sbx.pem");
if (options.Payments?.SigningKey != null)
{
options.Payments.SigningKey.PrivateKey = privateKey;
}
},
configurationSectionName: configName2,
serviceKey: serviceKey2,
authTokenCachingStrategy: AuthTokenCachingStrategies.InMemory)
.BuildServiceProvider();

Client = ServiceProvider.GetRequiredKeyedService<ITrueLayerClient>(serviceKey1);
Client2 = ServiceProvider.GetRequiredKeyedService<ITrueLayerClient>(serviceKey2);
TlClients =
[
ServiceProvider.GetRequiredKeyedService<ITrueLayerClient>(serviceKey1),
ServiceProvider.GetRequiredKeyedService<ITrueLayerClient>(serviceKey2)
];

ClientMerchantAccounts =
[
GetMerchantBeneficiaryAccountsAsync(TlClients[0]).Result,
GetMerchantBeneficiaryAccountsAsync(TlClients[1]).Result,
];
}

public IServiceProvider ServiceProvider { get; }
public ITrueLayerClient Client { get; }
public ITrueLayerClient Client2 { get; }
public ITrueLayerClient[] TlClients { get; }
public (string GbpMerchantAccountId, string EurMerchantAccountId)[] ClientMerchantAccounts { get; }

private static IConfiguration LoadConfiguration()
=> new ConfigurationBuilder()
Expand All @@ -58,5 +69,13 @@ private static IConfiguration LoadConfiguration()
.AddJsonFile("appsettings.local.json", true)
.AddEnvironmentVariables()
.Build();

private static async Task<(string gbpMerchantAccountId, string eurMerchantAccountId)> GetMerchantBeneficiaryAccountsAsync(ITrueLayerClient client)
{
var merchantAccounts = await client.MerchantAccounts.ListMerchantAccounts();
var gbpMerchantAccount = merchantAccounts.Data!.Items.First(m => m.Currency == Currencies.GBP);
var eurMerchantAccount = merchantAccounts.Data!.Items.First(m => m.Currency == Currencies.EUR);
return (gbpMerchantAccount.Id, eurMerchantAccount.Id);
}
}
}
4 changes: 2 additions & 2 deletions test/TrueLayer.AcceptanceTests/AuthTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public AuthTests(ApiTestFixture fixture)
public async Task Can_get_auth_token()
{
ApiResponse<GetAuthTokenResponse> apiResponse
= await _fixture.Client.Auth.GetAuthToken(new GetAuthTokenRequest());
= await _fixture.TlClients[0].Auth.GetAuthToken(new GetAuthTokenRequest());

apiResponse.IsSuccessful.Should().BeTrue();
apiResponse.StatusCode.Should().Be(HttpStatusCode.OK);
Expand All @@ -35,7 +35,7 @@ ApiResponse<GetAuthTokenResponse> apiResponse
public async Task Can_get_scoped_access_token()
{
GetAuthTokenResponse? apiResponse
= await _fixture.Client.Auth.GetAuthToken(new GetAuthTokenRequest("payments"));
= await _fixture.TlClients[0].Auth.GetAuthToken(new GetAuthTokenRequest("payments"));

apiResponse.Should().NotBeNull();
apiResponse!.Scope.Should().Be("payments");
Expand Down
Loading

0 comments on commit a0fb393

Please sign in to comment.