From d5badd5b3371025c087712292a9eec65db0c8113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Thu, 7 Aug 2025 11:39:34 +0200 Subject: [PATCH] Release 4.14.0 - refactor: introduces AuthenticationClient and reorganise authentication standalone module --- Directory.Build.props | 2 +- src/CheckoutSdk/LogProvider.cs | 27 ++++++- .../CheckoutSdkIntegrationTest.cs | 2 +- test/CheckoutSdkTest/LogProviderTest.cs | 76 ++++++++++++++++++- 4 files changed, 98 insertions(+), 9 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index a7bace06..a7563b69 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,5 @@ - 4.13.1 + 4.14.0 diff --git a/src/CheckoutSdk/LogProvider.cs b/src/CheckoutSdk/LogProvider.cs index 43ae3ca6..47f0082c 100644 --- a/src/CheckoutSdk/LogProvider.cs +++ b/src/CheckoutSdk/LogProvider.cs @@ -2,23 +2,42 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using System; +using System.Collections.Concurrent; namespace Checkout { - public static class LogProvider { + private static readonly object SyncRoot = new object(); private static ILoggerFactory _loggerFactory = new LoggerFactory(); + private static readonly ConcurrentDictionary Loggers = new ConcurrentDictionary(); public static void SetLogFactory(ILoggerFactory factory) { - _loggerFactory?.Dispose(); - _loggerFactory = factory ?? new LoggerFactory(); + lock (SyncRoot) + { + _loggerFactory?.Dispose(); + _loggerFactory = factory ?? new LoggerFactory(); + Loggers.Clear(); + } } public static ILogger GetLogger(Type loggerType) { - return loggerType is null ? NullLogger.Instance : _loggerFactory.CreateLogger(loggerType) ?? NullLogger.Instance;; + if (loggerType == null) + { + return NullLogger.Instance; + } + + return Loggers.GetOrAdd(loggerType, type => + { + lock (SyncRoot) + { + var name = type.FullName ?? type.Name; + var logger = _loggerFactory.CreateLogger(name); + return logger ?? NullLogger.Instance; + } + }); } } } diff --git a/test/CheckoutSdkTest/CheckoutSdkIntegrationTest.cs b/test/CheckoutSdkTest/CheckoutSdkIntegrationTest.cs index 288322ee..a416bbfd 100644 --- a/test/CheckoutSdkTest/CheckoutSdkIntegrationTest.cs +++ b/test/CheckoutSdkTest/CheckoutSdkIntegrationTest.cs @@ -72,7 +72,7 @@ private class TestingClientFactory : IHttpClientFactory public HttpClient CreateClient() { var httpClient = new HttpClient(new CustomMessageHandler()); - httpClient.Timeout = TimeSpan.FromSeconds(2); + httpClient.Timeout = TimeSpan.FromSeconds(60); return httpClient; } } diff --git a/test/CheckoutSdkTest/LogProviderTest.cs b/test/CheckoutSdkTest/LogProviderTest.cs index 8f989f11..ab5d2ccc 100644 --- a/test/CheckoutSdkTest/LogProviderTest.cs +++ b/test/CheckoutSdkTest/LogProviderTest.cs @@ -6,9 +6,13 @@ namespace Checkout { + [Collection("NonParallel")] public sealed class LogProviderTests : IDisposable { private readonly ILoggerFactory _loggerFactory; + + private static readonly object RandLock = new object(); + private static readonly Random Random = new Random(); public LogProviderTests() { @@ -25,14 +29,20 @@ public void ShouldGetLoggerReturnsValidLogger() public async Task ShouldCreateASingleLoggerInstanceForMultipleConcurrentRequests() { LogProvider.SetLogFactory(_loggerFactory); - Type[] loggerTypes = new[] { typeof(LogProviderTests), typeof(AnotherTestClass), typeof(NoInitializedType) }; + Type[] loggerTypes = { typeof(LogProviderTests), typeof(AnotherTestClass), typeof(NoInitializedType) }; + Task[] createLoggerTasks = Enumerable.Range(1, 50) .Select(async index => { - int randomDelayMs = new Random().Next(1, 5); - await Task.Delay(randomDelayMs); + int delay; + lock (RandLock) + { + delay = Random.Next(1, 5); + } + await Task.Delay(delay); return await Task.FromResult(LogProvider.GetLogger(loggerTypes[index % loggerTypes.Length])); }).ToArray(); + ILogger[] loggers = await Task.WhenAll(createLoggerTasks); Assert.Equal(loggerTypes.Length, loggers.Distinct().Count()); } @@ -54,6 +64,66 @@ public void ShouldNotThrowExceptionWhenSetLogFactoryWithNullParameter() { Assert.Null(Record.Exception(() => LogProvider.SetLogFactory(null))); } + + [Fact] + public void ShouldReplaceLoggerFactoryCorrectly() + { + var loggerBefore = LogProvider.GetLogger(typeof(LogProviderTests)); + + var newFactory = LoggerFactory.Create(builder => builder.AddFilter(_ => false)); + LogProvider.SetLogFactory(newFactory); + + var loggerAfter = LogProvider.GetLogger(typeof(LogProviderTests)); + + Assert.NotSame(loggerBefore, loggerAfter); + } + + [Fact] + public void ShouldClearLoggersWhenFactoryChanges() + { + LogProvider.SetLogFactory(_loggerFactory); + var logger1 = LogProvider.GetLogger(typeof(LogProviderTests)); + + var newFactory = new LoggerFactory(); + LogProvider.SetLogFactory(newFactory); + var logger2 = LogProvider.GetLogger(typeof(LogProviderTests)); + + Assert.NotSame(logger1, logger2); + } + + [Fact] + public void ShouldReturnSameLoggerOnMultipleCalls() + { + LogProvider.SetLogFactory(_loggerFactory); + + var logger1 = LogProvider.GetLogger(typeof(LogProviderTests)); + var logger2 = LogProvider.GetLogger(typeof(LogProviderTests)); + + Assert.Same(logger1, logger2); + } + + [Fact] + public async Task ShouldNotThrowWhenCallingSetLogFactoryConcurrently() + { + var tasks = Enumerable.Range(0, 10).Select(_ => Task.Run(() => + { + LogProvider.SetLogFactory(new LoggerFactory()); + })); + + var exception = await Record.ExceptionAsync(async () => await Task.WhenAll(tasks)); + + Assert.Null(exception); + } + + [Fact] + public void ShouldAllowMultipleNullLoggerFactoryAssignments() + { + LogProvider.SetLogFactory(null); + LogProvider.SetLogFactory(null); + var logger = LogProvider.GetLogger(typeof(LogProviderTests)); + + Assert.NotNull(logger); + } public void Dispose() {