diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..0effd78
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,18 @@
+root = true
+
+[*]
+trim_trailing_whitespace = true
+insert_final_newline = false
+indent_style = space
+indent_size = 4
+charset = utf-8
+end_of_line = lf
+
+[*.{csproj,json,config,yml,props}]
+indent_size = 2
+
+[*.sh]
+end_of_line = lf
+
+[*.{cmd, bat}]
+end_of_line = crlf
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..8db427c
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,37 @@
+###############################
+# Git Line Endings #
+###############################
+
+# Set default behavior to automatically normalize line endings.
+* text=auto
+
+# Force batch scripts to always use CRLF line endings so that if a repo is accessed
+# in Windows via a file share from Linux, the scripts will work.
+*.{cmd,[cC][mM][dD]} text eol=crlf
+*.{bat,[bB][aA][tT]} text eol=crlf
+
+# Force bash scripts to always use LF line endings so that if a repo is accessed
+# in Unix via a file share from Windows, the scripts will work.
+*.sh text eol=lf
+
+###############################
+# Git Large File System (LFS) #
+###############################
+
+# Archives
+*.7z filter=lfs diff=lfs merge=lfs -text
+*.br filter=lfs diff=lfs merge=lfs -text
+*.gz filter=lfs diff=lfs merge=lfs -text
+*.tar filter=lfs diff=lfs merge=lfs -text
+*.zip filter=lfs diff=lfs merge=lfs -text
+
+# Images
+*.gif filter=lfs diff=lfs merge=lfs -text
+*.ico filter=lfs diff=lfs merge=lfs -text
+*.jpg filter=lfs diff=lfs merge=lfs -text
+*.png filter=lfs diff=lfs merge=lfs -text
+*.psd filter=lfs diff=lfs merge=lfs -text
+*.webp filter=lfs diff=lfs merge=lfs -text
+
+# Other
+*.exe filter=lfs diff=lfs merge=lfs -text
diff --git a/.gitignore b/.gitignore
index 3e759b7..8afdcb6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
+*.rsuser
*.suo
*.user
*.userosscache
@@ -12,6 +13,9 @@
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
+# Mono auto generated files
+mono_crash.*
+
# Build results
[Dd]ebug/
[Dd]ebugPublic/
@@ -19,10 +23,14 @@
[Rr]eleases/
x64/
x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
+[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
@@ -36,9 +44,10 @@ Generated\ Files/
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
-# NUNIT
+# NUnit
*.VisualState.xml
TestResult.xml
+nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
@@ -52,7 +61,12 @@ BenchmarkDotNet.Artifacts/
project.lock.json
project.fragment.lock.json
artifacts/
-**/Properties/launchSettings.json
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
@@ -60,7 +74,7 @@ StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
-*_i.h
+*_h.h
*.ilk
*.meta
*.obj
@@ -77,6 +91,7 @@ StyleCopReport.xml
*.tlh
*.tmp
*.tmp_proj
+*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
@@ -119,9 +134,6 @@ _ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
-# JustCode is a .NET coding add-in
-.JustCode
-
# TeamCity is a build add-in
_TeamCity*
@@ -132,6 +144,11 @@ _TeamCity*
.axoCover/*
!.axoCover/settings.json
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
# Visual Studio code coverage results
*.coverage
*.coveragexml
@@ -179,6 +196,8 @@ PublishScripts/
# NuGet Packages
*.nupkg
+# NuGet Symbol Packages
+*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
@@ -203,12 +222,14 @@ BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
+*.appxbundle
+*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
-!*.[Cc]ache/
+!?*.[Cc]ache/
# Others
ClientBin/
@@ -221,7 +242,7 @@ ClientBin/
*.publishsettings
orleans.codegen.cs
-# Including strong name files can present a security risk
+# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
@@ -252,6 +273,9 @@ ServiceFabricBackup/
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
@@ -287,12 +311,8 @@ paket-files/
# FAKE - F# Make
.fake/
-# JetBrains Rider
-.idea/
-*.sln.iml
-
-# CodeRush
-.cr/
+# CodeRush personal settings
+.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
@@ -317,7 +337,7 @@ __pycache__/
# OpenCover UI analysis results
OpenCover/
-# Azure Stream Analytics local run output
+# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
@@ -326,5 +346,109 @@ ASALocalRun/
# NVidia Nsight GPU debugger configuration file
*.nvuser
-# MFractors (Xamarin productivity tool) working folder
+# MFractors (Xamarin productivity tool) working folder
.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+##
+## Visual Studio Code
+##
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
diff --git a/AccessTokenClient.Extensions/AccessTokenClient.Extensions.csproj b/AccessTokenClient.Extensions/AccessTokenClient.Extensions.csproj
index 78c0be7..ec20a35 100644
--- a/AccessTokenClient.Extensions/AccessTokenClient.Extensions.csproj
+++ b/AccessTokenClient.Extensions/AccessTokenClient.Extensions.csproj
@@ -1,8 +1,9 @@
-
+
- netstandard2.0;netcoreapp3.1;net5.0
- latest
+ net6.0
+ true
+ enable
1.0.0
true
William Applegate
@@ -22,36 +23,22 @@
true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
-
+
\ No newline at end of file
diff --git a/AccessTokenClient.Extensions/AccessTokenClientPolicy.cs b/AccessTokenClient.Extensions/AccessTokenClientPolicy.cs
index 6cd6809..054d554 100644
--- a/AccessTokenClient.Extensions/AccessTokenClientPolicy.cs
+++ b/AccessTokenClient.Extensions/AccessTokenClientPolicy.cs
@@ -1,38 +1,37 @@
-using Polly;
+using Polly;
using Polly.Extensions.Http;
using System;
using System.Net;
using System.Net.Http;
using Microsoft.Extensions.Logging;
-namespace AccessTokenClient.Extensions
+namespace AccessTokenClient.Extensions;
+
+///
+/// This static class contains policies that can be applied
+/// easily to the access token client to increase resiliency.
+///
+public static class AccessTokenClientPolicy
{
///
- /// This static class contains policies that can be applied
- /// easily to the access token client to increase resiliency.
+ /// Returns the default retry policy for the access token client.
///
- public static class AccessTokenClientPolicy
+ /// An optional logger instance.
+ ///
+ /// A default that can be applied to the
+ /// instance that is injected into the access token client.
+ /// This policy retries the token request in the event of transient http errors
+ /// (5XX and 408) as well as when a 404 is encountered. The request will be retried
+ /// twice, with a 1 second wait time between retries.
+ ///
+ public static IAsyncPolicy GetDefaultRetryPolicy(ILogger? logger = null)
{
- ///
- /// Returns the default retry policy for the access token client.
- ///
- /// An optional logger instance.
- ///
- /// A default that can be applied to the
- /// instance that is injected into the access token client.
- /// This policy retries the token request in the event of transient http errors
- /// (5XX and 408) as well as when a 404 is encountered. The request will be retried
- /// twice, with a 1 second wait time between retries.
- ///
- public static IAsyncPolicy GetDefaultRetryPolicy(ILogger logger = null)
- {
- return HttpPolicyExtensions
- .HandleTransientHttpError()
- .OrResult(message => message.StatusCode == HttpStatusCode.NotFound)
- .WaitAndRetryAsync(2, _ => TimeSpan.FromSeconds(1), (_, timespan, retryAttempt, _) =>
- {
- logger?.LogWarning("Delaying for {delay}ms, then making retry attempt #{retry}.", timespan.TotalMilliseconds, retryAttempt);
- });
- }
+ return HttpPolicyExtensions
+ .HandleTransientHttpError()
+ .OrResult(message => message.StatusCode == HttpStatusCode.NotFound)
+ .WaitAndRetryAsync(2, _ => TimeSpan.FromSeconds(1), (_, timespan, retryAttempt, context) =>
+ {
+ logger?.LogWarning("Delaying for {delay}ms, then making retry attempt #{retry}.", timespan.TotalMilliseconds, retryAttempt);
+ });
}
}
\ No newline at end of file
diff --git a/AccessTokenClient.Extensions/ServiceCollectionExtensions.cs b/AccessTokenClient.Extensions/ServiceCollectionExtensions.cs
index 6bb52e5..0510f3a 100644
--- a/AccessTokenClient.Extensions/ServiceCollectionExtensions.cs
+++ b/AccessTokenClient.Extensions/ServiceCollectionExtensions.cs
@@ -1,83 +1,79 @@
-using AccessTokenClient.Caching;
-using AccessTokenClient.Serialization;
+using AccessTokenClient.Caching;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System;
using System.Net.Http;
-namespace AccessTokenClient.Extensions
+namespace AccessTokenClient.Extensions;
+
+///
+/// This static class contains an extension method used to add the
+/// and dependencies to the service collection.
+///
+public static class ServiceCollectionExtensions
{
///
- /// This static class contains an extension method used to add the
- /// and dependencies to the service collection.
+ /// Adds the token client to the service collection.
///
- public static class ServiceCollectionExtensions
+ /// The service collection.
+ ///
+ /// An optional action used to configure the instance that is returned
+ /// when the implementation is registered in the service collection via the
+ /// AddHttpClient extension method. This can be used to register an for
+ /// the token client, or to configure a retry policy for the to handle transient
+ /// errors that may be encountered.
+ ///
+ /// The service collection instance.
+ public static IServiceCollection AddAccessTokenClient(this IServiceCollection services, Action? builderAction = null)
{
- ///
- /// Adds the token client to the service collection.
- ///
- /// The service collection.
- ///
- /// An optional action used to configure the instance that is returned
- /// when the implementation is registered in the service collection via the
- /// AddHttpClient extension method. This can be used to register an for
- /// the token client, or to configure a retry policy for the to handle transient
- /// errors that may be encountered.
- ///
- /// The service collection instance.
- public static IServiceCollection AddAccessTokenClient(this IServiceCollection services, Action builderAction = null)
+ if (services == null)
{
- if (services == null)
- {
- throw new ArgumentNullException(nameof(services));
- }
-
- services.TryAddSingleton();
+ throw new ArgumentNullException(nameof(services));
+ }
- var httpClientBuilder = services.AddHttpClient("AccessTokenClient.TokenClient");
+ var httpClientBuilder = services.AddHttpClient("AccessTokenClient.TokenClient");
- builderAction?.Invoke(httpClientBuilder);
+ builderAction?.Invoke(httpClientBuilder);
- return services;
- }
+ return services;
+ }
- ///
- /// Enables caching for the token client.
- ///
- ///
- /// An implementation of the interface to register.
- ///
- /// The service collection.
- /// An optional action used to configure the token client cache options.
- /// The service collection instance.
- public static IServiceCollection AddAccessTokenClientCache(this IServiceCollection services, Action action = null) where T : ITokenResponseCache
+ ///
+ /// Enables caching for the token client.
+ ///
+ ///
+ /// An implementation of the interface to register.
+ ///
+ /// The service collection.
+ /// An optional action used to configure the token client cache options.
+ /// The service collection instance.
+ public static IServiceCollection AddAccessTokenClientCache(this IServiceCollection services, Action? action = null) where T : ITokenResponseCache
+ {
+ if (services == null)
{
- if (services == null)
- {
- throw new ArgumentNullException(nameof(services));
- }
+ throw new ArgumentNullException(nameof(services));
+ }
- var options = new TokenClientCacheOptions();
- action?.Invoke(options);
+ var options = new TokenClientCacheOptions();
+ action?.Invoke(options);
- if(options.ExpirationBuffer <= 0)
- {
- throw new ArgumentException("The expiration buffer must be greater than 0.");
- }
+ if (options.ExpirationBuffer <= 0)
+ {
+ throw new ArgumentException("The expiration buffer must be greater than 0.");
+ }
- if(string.IsNullOrWhiteSpace(options.CacheKeyPrefix))
- {
- throw new ArgumentException("A cache key prefix must be specified.");
- }
+ if (string.IsNullOrWhiteSpace(options.CacheKeyPrefix))
+ {
+ throw new ArgumentException("A cache key prefix must be specified.");
+ }
- services.TryAddSingleton(options);
- services.TryAddSingleton();
- services.TryAddSingleton(typeof(ITokenResponseCache), typeof(T));
- services.TryAddSingleton();
+ services.TryAddSingleton(options);
+ services.TryAddSingleton();
+ services.TryAddSingleton(typeof(ITokenResponseCache), typeof(T));
+ services.TryAddSingleton();
- services.TryDecorate();
+ services.TryDecorate();
- return services;
- }
+ return services;
}
}
\ No newline at end of file
diff --git a/AccessTokenClient.Tests/AccessTokenClient.Tests.csproj b/AccessTokenClient.Tests/AccessTokenClient.Tests.csproj
index 8e6d3c1..6aeced4 100644
--- a/AccessTokenClient.Tests/AccessTokenClient.Tests.csproj
+++ b/AccessTokenClient.Tests/AccessTokenClient.Tests.csproj
@@ -1,30 +1,34 @@
- net5.0
+ net6.0
+ enable
+ enable
false
- 5.0
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -35,4 +39,4 @@
-
+
\ No newline at end of file
diff --git a/AccessTokenClient.Tests/AccessTokenClientRetryIntegrationTests.cs b/AccessTokenClient.Tests/AccessTokenClientRetryIntegrationTests.cs
new file mode 100644
index 0000000..c1b5a33
--- /dev/null
+++ b/AccessTokenClient.Tests/AccessTokenClientRetryIntegrationTests.cs
@@ -0,0 +1,60 @@
+using AccessTokenClient.Caching;
+using AccessTokenClient.Extensions;
+using AccessTokenClient.Tests.Helpers;
+using FluentAssertions;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using System.Net;
+using Xunit;
+
+namespace AccessTokenClient.Tests;
+
+public class AccessTokenClientRetryIntegrationTests
+{
+ [Theory]
+ [InlineData(HttpStatusCode.NotFound)]
+ [InlineData(HttpStatusCode.RequestTimeout)]
+ [InlineData(HttpStatusCode.InternalServerError)]
+ [InlineData(HttpStatusCode.ServiceUnavailable)]
+ [InlineData(HttpStatusCode.GatewayTimeout)]
+ public async Task Test(HttpStatusCode statusCode)
+ {
+ var mockHandler = new MockHttpMessageHandler(string.Empty, statusCode);
+
+ var services = new ServiceCollection()
+ .AddLogging()
+ .AddMemoryCache()
+ .AddAccessTokenClient(builder =>
+ {
+ // Add the default retry policy:
+ builder.AddPolicyHandler((provider, _) =>
+ {
+ var logger = provider.GetRequiredService>();
+ return AccessTokenClientPolicy.GetDefaultRetryPolicy(logger);
+ });
+
+ // Set-up a delegating handler to mock the response for the test:
+ builder.AddHttpMessageHandler(() => mockHandler);
+ })
+ .AddAccessTokenClientCache();
+
+ var client = services.BuildServiceProvider().GetRequiredService();
+
+ client.ShouldNotBeNull();
+
+ Func func = async () => await client.RequestAccessToken(new TokenRequest
+ {
+ TokenEndpoint = "https://service/token",
+ ClientIdentifier = "client-identifier",
+ ClientSecret = "client-secret",
+ Scopes = new[]
+ {
+ "scope:read", "scope:create", "scope:edit", "scope:delete"
+ }
+ });
+
+ await func.Should().ThrowAsync();
+
+ mockHandler.NumberOfCalls.Should().Be(3);
+ }
+}
\ No newline at end of file
diff --git a/AccessTokenClient.Tests/DefaultAccessTokenTransformerTests.cs b/AccessTokenClient.Tests/DefaultAccessTokenTransformerTests.cs
index d2d2f9a..64fec5f 100644
--- a/AccessTokenClient.Tests/DefaultAccessTokenTransformerTests.cs
+++ b/AccessTokenClient.Tests/DefaultAccessTokenTransformerTests.cs
@@ -3,32 +3,31 @@
using FluentAssertions;
using Xunit;
-namespace AccessTokenClient.Tests
+namespace AccessTokenClient.Tests;
+
+public class DefaultAccessTokenTransformerTests
{
- public class DefaultAccessTokenTransformerTests
+ [Fact]
+ public void EnsureConvertedValueIsTheSameAsOriginal()
{
- [Fact]
- public void EnsureConvertedValueIsTheSameAsOriginal()
- {
- const string Value = "test";
+ const string Value = "test";
- var transformer = new DefaultAccessTokenTransformer();
- var convertedValue = transformer.Convert(Value);
+ var transformer = new DefaultAccessTokenTransformer();
+ var convertedValue = transformer.Convert(Value);
- convertedValue.ShouldNotBeNull();
- convertedValue.Should().Be(Value);
- }
+ convertedValue.ShouldNotBeNull();
+ convertedValue.Should().Be(Value);
+ }
- [Fact]
- public void EnsureRevertedValueIsTheSameAsOriginal()
- {
- const string Value = "test";
+ [Fact]
+ public void EnsureRevertedValueIsTheSameAsOriginal()
+ {
+ const string Value = "test";
- var transformer = new DefaultAccessTokenTransformer();
- var revertedValue = transformer.Revert(Value);
+ var transformer = new DefaultAccessTokenTransformer();
+ var revertedValue = transformer.Revert(Value);
- revertedValue.ShouldNotBeNull();
- revertedValue.Should().Be(Value);
- }
+ revertedValue.ShouldNotBeNull();
+ revertedValue.Should().Be(Value);
}
}
\ No newline at end of file
diff --git a/AccessTokenClient.Tests/DelegatingHandlerTests.cs b/AccessTokenClient.Tests/DelegatingHandlerTests.cs
index c5631e0..3671615 100644
--- a/AccessTokenClient.Tests/DelegatingHandlerTests.cs
+++ b/AccessTokenClient.Tests/DelegatingHandlerTests.cs
@@ -1,75 +1,70 @@
using FluentAssertions;
using Moq;
-using System;
using System.Net;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
using Xunit;
-namespace AccessTokenClient.Tests
+namespace AccessTokenClient.Tests;
+
+public class DelegatingHandlerTests
{
- public class DelegatingHandlerTests
+ [Fact]
+ public void EnsureExceptionThrownWhenOptionsAreNull()
{
- [Fact]
- public void EnsureExceptionThrownWhenOptionsAreNull()
- {
- ITokenRequestOptions options = new Options();
- Action action = () => _ = new AccessTokenDelegatingHandler(options, null);
+ ITokenRequestOptions options = new Options();
+ Action action = () => _ = new AccessTokenDelegatingHandler(options, null);
- action.Should().Throw();
- }
-
- [Fact]
- public void EnsureExceptionThrownWhenTokenClientIsNull()
- {
- var mockClient = new Mock();
- Action action = () => _ = new AccessTokenDelegatingHandler(null, mockClient.Object);
+ action.Should().Throw();
+ }
- action.Should().Throw();
- }
+ [Fact]
+ public void EnsureExceptionThrownWhenTokenClientIsNull()
+ {
+ var mockClient = new Mock();
+ Action action = () => _ = new AccessTokenDelegatingHandler(null, mockClient.Object);
- [Fact]
- public async Task EnsureAccessTokenAddedToRequest()
- {
- ITokenRequestOptions options = new Options();
- var mockClient = new Mock();
+ action.Should().Throw();
+ }
- mockClient
- .Setup(m => m.RequestAccessToken(It.IsAny(), It.IsAny>>(), It.IsAny()))
- .ReturnsAsync(new TokenResponse
- {
- AccessToken = "1234567890",
- ExpiresIn = 3000
- });
+ [Fact]
+ public async Task EnsureAccessTokenAddedToRequest()
+ {
+ ITokenRequestOptions options = new Options();
+ var mockClient = new Mock();
- var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://test.com");
- var handler = new AccessTokenDelegatingHandler(options, mockClient.Object)
+ mockClient
+ .Setup(m => m.RequestAccessToken(It.IsAny(), It.IsAny>>(), It.IsAny()))
+ .ReturnsAsync(new TokenResponse
{
- InnerHandler = new TestHandler()
- };
+ AccessToken = "1234567890",
+ ExpiresIn = 3000
+ });
+
+ var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://test.com");
+ var handler = new AccessTokenDelegatingHandler(options, mockClient.Object)
+ {
+ InnerHandler = new TestHandler()
+ };
- var invoker = new HttpMessageInvoker(handler);
- await invoker.SendAsync(httpRequestMessage, new CancellationToken());
- }
+ var invoker = new HttpMessageInvoker(handler);
+ await invoker.SendAsync(httpRequestMessage, new CancellationToken());
}
+}
- public class TestHandler : DelegatingHandler
+public class TestHandler : DelegatingHandler
+{
+ protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
- protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
- {
- return Task.Factory.StartNew(() => new HttpResponseMessage(HttpStatusCode.OK), cancellationToken);
- }
+ return Task.Factory.StartNew(() => new HttpResponseMessage(HttpStatusCode.OK), cancellationToken);
}
+}
- public class Options : ITokenRequestOptions
- {
- public string TokenEndpoint { get; set; }
+public class Options : ITokenRequestOptions
+{
+ public string TokenEndpoint { get; set; }
- public string ClientIdentifier { get; set; }
+ public string ClientIdentifier { get; set; }
- public string ClientSecret { get; set; }
+ public string ClientSecret { get; set; }
- public string[] Scopes { get; set; }
- }
+ public string[] Scopes { get; set; }
}
\ No newline at end of file
diff --git a/AccessTokenClient.Tests/Helpers/FluentAssertionsExtensions.cs b/AccessTokenClient.Tests/Helpers/FluentAssertionsExtensions.cs
index 90b2805..8b35d5b 100644
--- a/AccessTokenClient.Tests/Helpers/FluentAssertionsExtensions.cs
+++ b/AccessTokenClient.Tests/Helpers/FluentAssertionsExtensions.cs
@@ -1,21 +1,20 @@
using FluentAssertions;
using JetBrains.Annotations;
-namespace AccessTokenClient.Tests.Helpers
+namespace AccessTokenClient.Tests.Helpers;
+
+public static class FluentAssertionsExtensions
{
- public static class FluentAssertionsExtensions
+ ///
+ /// Asserts that the object to test is not null. Includes a
+ /// contract annotation that allows Resharper code analysis
+ /// to recognize accessing the object after this call is made
+ /// will not result in a null reference exception.
+ ///
+ /// The object to test.
+ [ContractAnnotation("null => stop")]
+ public static void ShouldNotBeNull(this object objectToTest)
{
- ///
- /// Asserts that the object to test is not null. Includes a
- /// contract annotation that allows Resharper code analysis
- /// to recognize accessing the object after this call is made
- /// will not result in a null reference exception.
- ///
- /// The object to test.
- [ContractAnnotation("null => stop")]
- public static void ShouldNotBeNull(this object objectToTest)
- {
- objectToTest.Should().NotBeNull();
- }
+ objectToTest.Should().NotBeNull();
}
}
\ No newline at end of file
diff --git a/AccessTokenClient.Tests/Helpers/MockHttpMessageHandler.cs b/AccessTokenClient.Tests/Helpers/MockHttpMessageHandler.cs
index af00351..df196b9 100644
--- a/AccessTokenClient.Tests/Helpers/MockHttpMessageHandler.cs
+++ b/AccessTokenClient.Tests/Helpers/MockHttpMessageHandler.cs
@@ -1,45 +1,41 @@
-using System.Net;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
+using System.Net;
-namespace AccessTokenClient.Tests.Helpers
+namespace AccessTokenClient.Tests.Helpers;
+
+public class MockHttpMessageHandler : DelegatingHandler
{
- public class MockHttpMessageHandler : DelegatingHandler
- {
- private readonly HttpStatusCode httpStatusCode;
+ private readonly HttpStatusCode httpStatusCode;
- private readonly string response;
+ private readonly string? response;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The response mock response to use.
- /// The HTTP status code to return.
- public MockHttpMessageHandler(string response, HttpStatusCode httpStatusCode)
- {
- this.response = response;
- this.httpStatusCode = httpStatusCode;
- }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The response mock response to use.
+ /// The HTTP status code to return.
+ public MockHttpMessageHandler(string? response, HttpStatusCode httpStatusCode)
+ {
+ this.response = response;
+ this.httpStatusCode = httpStatusCode;
+ }
+
+ public string? Input { get; private set; }
- public string Input { get; private set; }
+ public int NumberOfCalls { get; private set; }
- public int NumberOfCalls { get; private set; }
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ NumberOfCalls++;
- protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ if (request.Content != null)
{
- NumberOfCalls++;
-
- if (request.Content != null)
- {
- Input = await request.Content.ReadAsStringAsync(cancellationToken);
- }
-
- return new HttpResponseMessage
- {
- StatusCode = httpStatusCode,
- Content = new StringContent(response)
- };
+ Input = await request.Content.ReadAsStringAsync(cancellationToken);
}
+
+ return new HttpResponseMessage
+ {
+ StatusCode = httpStatusCode,
+ Content = response != null ? new StringContent(response) : null
+ };
}
}
\ No newline at end of file
diff --git a/AccessTokenClient.Tests/HttpClientBuilderExtensionsTests.cs b/AccessTokenClient.Tests/HttpClientBuilderExtensionsTests.cs
index 5e8d820..6a2945b 100644
--- a/AccessTokenClient.Tests/HttpClientBuilderExtensionsTests.cs
+++ b/AccessTokenClient.Tests/HttpClientBuilderExtensionsTests.cs
@@ -1,52 +1,50 @@
-using AccessTokenClient.Extensions;
+using AccessTokenClient.Extensions;
using AccessTokenClient.Tests.Helpers;
using Microsoft.Extensions.DependencyInjection;
-using System.Net.Http;
using Xunit;
-namespace AccessTokenClient.Tests
+namespace AccessTokenClient.Tests;
+
+public class HttpClientBuilderExtensionsTests
{
- public class HttpClientBuilderExtensionsTests
+ [Fact]
+ public void EnsureAccessTokenDelegatingHandlerRegisteredSuccessfully()
{
- [Fact]
- public void EnsureAccessTokenDelegatingHandlerRegisteredSuccessfully()
- {
- IServiceCollection services = new ServiceCollection();
-
- services.AddMemoryCache();
+ IServiceCollection services = new ServiceCollection();
- services.AddAccessTokenClient();
+ services.AddMemoryCache();
- services.AddSingleton(new TestClientTokenOptions());
+ services.AddAccessTokenClient();
- services.AddHttpClient().AddClientAccessTokenHandler();
+ services.AddSingleton(new TestClientTokenOptions());
- var provider = services.BuildServiceProvider();
+ services.AddHttpClient().AddClientAccessTokenHandler();
- var client = provider.GetService();
+ var provider = services.BuildServiceProvider();
- client.ShouldNotBeNull();
- }
+ var client = provider.GetRequiredService();
- private class TestClient
- {
- private readonly HttpClient client;
+ client.ShouldNotBeNull();
+ }
- public TestClient(HttpClient client)
- {
- this.client = client;
- }
- }
+ private class TestClient
+ {
+ private readonly HttpClient client;
- private class TestClientTokenOptions : ITokenRequestOptions
+ public TestClient(HttpClient client)
{
- public string TokenEndpoint { get; set; }
+ this.client = client;
+ }
+ }
- public string ClientIdentifier { get; set; }
+ private class TestClientTokenOptions : ITokenRequestOptions
+ {
+ public string TokenEndpoint { get; set; }
+
+ public string ClientIdentifier { get; set; }
- public string ClientSecret { get; set; }
+ public string ClientSecret { get; set; }
- public string[] Scopes { get; set; }
- }
+ public string[] Scopes { get; set; }
}
}
\ No newline at end of file
diff --git a/AccessTokenClient.Tests/MemoryTokenResponseCache.cs b/AccessTokenClient.Tests/MemoryTokenResponseCache.cs
index c3f3d9e..e7c1221 100644
--- a/AccessTokenClient.Tests/MemoryTokenResponseCache.cs
+++ b/AccessTokenClient.Tests/MemoryTokenResponseCache.cs
@@ -2,58 +2,55 @@
using AccessTokenClient.Tests.Helpers;
using FluentAssertions;
using Microsoft.Extensions.Caching.Memory;
-using System;
-using System.Threading.Tasks;
using Xunit;
-namespace AccessTokenClient.Tests
+namespace AccessTokenClient.Tests;
+
+public class MemoryTokenResponseCacheTests
{
- public class MemoryTokenResponseCacheTests
+ [Fact]
+ public void EnsureExceptionThrownWhenInjectedMemoryCacheIsNull()
{
- [Fact]
- public void EnsureExceptionThrownWhenInjectedMemoryCacheIsNull()
- {
- Func creationFunction = () => new MemoryTokenResponseCache(null);
- creationFunction.Should().Throw();
- }
+ Func creationFunction = () => new MemoryTokenResponseCache(null);
+ creationFunction.Should().Throw();
+ }
- [Fact]
- public async Task EnsureSuccessResponseReturnedWhenCachedResponseExists()
+ [Fact]
+ public async Task EnsureSuccessResponseReturnedWhenCachedResponseExists()
+ {
+ const string Key = "testing-key";
+ IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions());
+ memoryCache.Set(Key, new TokenResponse
{
- const string Key = "testing-key";
- IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions());
- memoryCache.Set(Key, new TokenResponse
- {
- AccessToken = "1234567890",
- ExpiresIn = 3000
- });
- var cache = new MemoryTokenResponseCache(memoryCache);
- var tokenResponse = await cache.Get(Key);
- tokenResponse.ShouldNotBeNull();
- }
+ AccessToken = "1234567890",
+ ExpiresIn = 3000
+ });
+ var cache = new MemoryTokenResponseCache(memoryCache);
+ var tokenResponse = await cache.Get(Key);
+ tokenResponse.ShouldNotBeNull();
+ }
- [Fact]
- public async Task EnsureSetReturnsTrueWhenCacheSetSuccessfully()
+ [Fact]
+ public async Task EnsureSetReturnsTrueWhenCacheSetSuccessfully()
+ {
+ const string Key = "testing-key";
+ IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions());
+ var cache = new MemoryTokenResponseCache(memoryCache);
+ var result = await cache.Set(Key, new TokenResponse
{
- const string Key = "testing-key";
- IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions());
- var cache = new MemoryTokenResponseCache(memoryCache);
- var result = await cache.Set(Key, new TokenResponse
- {
- AccessToken = "1234567890",
- ExpiresIn = 3000
- }, TimeSpan.FromMinutes(3000));
- result.Should().BeTrue();
- }
+ AccessToken = "1234567890",
+ ExpiresIn = 3000
+ }, TimeSpan.FromMinutes(3000));
+ result.Should().BeTrue();
+ }
- [Fact]
- public async Task EnsureNullReturnedWhenItemWithMatchingKeyDoesNotExist()
- {
- const string Key = "testing-key";
- IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions());
- var cache = new MemoryTokenResponseCache(memoryCache);
- var result = await cache.Get(Key);
- result.Should().BeNull();
- }
+ [Fact]
+ public async Task EnsureNullReturnedWhenItemWithMatchingKeyDoesNotExist()
+ {
+ const string Key = "testing-key";
+ IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions());
+ var cache = new MemoryTokenResponseCache(memoryCache);
+ var result = await cache.Get(Key);
+ result.Should().BeNull();
}
}
\ No newline at end of file
diff --git a/AccessTokenClient.Tests/ServiceCollectionExtensionsTests.cs b/AccessTokenClient.Tests/ServiceCollectionExtensionsTests.cs
index d474261..5af696f 100644
--- a/AccessTokenClient.Tests/ServiceCollectionExtensionsTests.cs
+++ b/AccessTokenClient.Tests/ServiceCollectionExtensionsTests.cs
@@ -1,133 +1,129 @@
-using AccessTokenClient.Caching;
+using AccessTokenClient.Caching;
using AccessTokenClient.Extensions;
using AccessTokenClient.Tests.Helpers;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using System;
using Xunit;
-namespace AccessTokenClient.Tests
+namespace AccessTokenClient.Tests;
+
+public class ServiceCollectionExtensionsTests
{
- public class ServiceCollectionExtensionsTests
+ [Fact]
+ public void EnsureServiceProviderReturnsTokenClient()
{
- [Fact]
- public void EnsureServiceProviderReturnsTokenClient()
- {
- var services = new ServiceCollection();
+ var services = new ServiceCollection();
- services.AddMemoryCache();
+ services.AddMemoryCache();
- services.AddAccessTokenClient().AddAccessTokenClientCache();
+ services.AddAccessTokenClient().AddAccessTokenClientCache();
- var provider = services.BuildServiceProvider();
+ var provider = services.BuildServiceProvider();
- var client = provider.GetService();
+ var client = provider.GetRequiredService();
- client.ShouldNotBeNull();
- client.Should().BeOfType();
- }
+ client.ShouldNotBeNull();
+ client.Should().BeOfType();
+ }
- [Fact]
- public void EnsureServiceProviderReturnsTokenClientWhenRetryPolicySpecified()
- {
- var services = new ServiceCollection();
+ [Fact]
+ public void EnsureServiceProviderReturnsTokenClientWhenRetryPolicySpecified()
+ {
+ var services = new ServiceCollection();
- services.AddAccessTokenClient(builder =>
- {
- builder.AddPolicyHandler(_ => AccessTokenClientPolicy.GetDefaultRetryPolicy());
- });
+ services.AddAccessTokenClient(builder =>
+ {
+ builder.AddPolicyHandler(_ => AccessTokenClientPolicy.GetDefaultRetryPolicy());
+ });
- var provider = services.BuildServiceProvider();
+ var provider = services.BuildServiceProvider();
- var client = provider.GetService();
+ var client = provider.GetRequiredService();
- client.ShouldNotBeNull();
- client.Should().BeOfType();
- }
+ client.ShouldNotBeNull();
+ client.Should().BeOfType();
+ }
- [Fact]
- public void EnsureServiceProviderReturnsTokenClientWhenRetryPolicySpecifiedWithLogger()
- {
- var services = new ServiceCollection();
+ [Fact]
+ public void EnsureServiceProviderReturnsTokenClientWhenRetryPolicySpecifiedWithLogger()
+ {
+ var services = new ServiceCollection();
- services.AddLogging();
+ services.AddLogging();
- services.AddAccessTokenClient(builder =>
+ services.AddAccessTokenClient(builder =>
+ {
+ builder.AddPolicyHandler((p, _) =>
{
- builder.AddPolicyHandler((p, _) =>
- {
- var logger = p.GetService>();
- return AccessTokenClientPolicy.GetDefaultRetryPolicy(logger);
- });
+ var logger = p.GetRequiredService>();
+ return AccessTokenClientPolicy.GetDefaultRetryPolicy(logger);
});
+ });
- var provider = services.BuildServiceProvider();
+ var provider = services.BuildServiceProvider();
- var client = provider.GetService();
+ var client = provider.GetRequiredService();
- client.ShouldNotBeNull();
- client.Should().BeOfType();
- }
+ client.ShouldNotBeNull();
+ client.Should().BeOfType();
+ }
- [Fact]
- public void EnsureServiceProviderReturnsTokenClientWhenCachingDisabled()
- {
- var services = new ServiceCollection();
+ [Fact]
+ public void EnsureServiceProviderReturnsTokenClientWhenCachingDisabled()
+ {
+ var services = new ServiceCollection();
- services.AddMemoryCache();
+ services.AddMemoryCache();
- services.AddAccessTokenClient();
+ services.AddAccessTokenClient();
- var provider = services.BuildServiceProvider();
+ var provider = services.BuildServiceProvider();
- var client = provider.GetService();
+ var client = provider.GetRequiredService();
- client.ShouldNotBeNull();
- client.Should().BeOfType();
- }
+ client.ShouldNotBeNull();
+ client.Should().BeOfType();
+ }
- [Fact]
- public void EnsureExceptionThrownWhenInvalidCachingOptionsSpecified()
- {
- var services = new ServiceCollection();
+ [Fact]
+ public void EnsureExceptionThrownWhenInvalidCachingOptionsSpecified()
+ {
+ var services = new ServiceCollection();
- Action invalidPrefixAction = () => services.AddAccessTokenClient().AddAccessTokenClientCache(options =>
- {
- options.CacheKeyPrefix = string.Empty;
- });
+ Action invalidPrefixAction = () => services.AddAccessTokenClient().AddAccessTokenClientCache(options =>
+ {
+ options.CacheKeyPrefix = string.Empty;
+ });
- invalidPrefixAction.Should().Throw();
+ invalidPrefixAction.Should().Throw();
- Action invalidBufferAction = () => services.AddAccessTokenClient().AddAccessTokenClientCache(options =>
- {
- options.ExpirationBuffer = 0;
- });
+ Action invalidBufferAction = () => services.AddAccessTokenClient().AddAccessTokenClientCache(options =>
+ {
+ options.ExpirationBuffer = 0;
+ });
- invalidBufferAction.Should().Throw();
- }
+ invalidBufferAction.Should().Throw();
+ }
- [Fact]
- public void EnsureExceptionThrownWhenServiceCollectionNullUsingAddAccessTokenClientExtensionMethod()
- {
- IServiceCollection services = null;
+ [Fact]
+ public void EnsureExceptionThrownWhenServiceCollectionNullUsingAddAccessTokenClientExtensionMethod()
+ {
+ IServiceCollection? services = null;
- // ReSharper disable once ExpressionIsAlwaysNull
- Action action = () => services.AddAccessTokenClient();
+ Action action = () => services!.AddAccessTokenClient();
- action.Should().Throw();
- }
+ action.Should().Throw();
+ }
- [Fact]
- public void EnsureExceptionThrownWhenServiceCollectionNullUsingAddAccessTokenClientCacheMethod()
- {
- IServiceCollection services = null;
+ [Fact]
+ public void EnsureExceptionThrownWhenServiceCollectionNullUsingAddAccessTokenClientCacheMethod()
+ {
+ IServiceCollection? services = null;
- // ReSharper disable once ExpressionIsAlwaysNull
- Action action = () => services.AddAccessTokenClientCache();
+ Action action = () => services!.AddAccessTokenClientCache();
- action.Should().Throw();
- }
+ action.Should().Throw();
}
}
\ No newline at end of file
diff --git a/AccessTokenClient.Tests/TokenClientCachingDecoratorTests.cs b/AccessTokenClient.Tests/TokenClientCachingDecoratorTests.cs
index 3035a74..d5f9eb4 100644
--- a/AccessTokenClient.Tests/TokenClientCachingDecoratorTests.cs
+++ b/AccessTokenClient.Tests/TokenClientCachingDecoratorTests.cs
@@ -1,335 +1,245 @@
-using AccessTokenClient.Caching;
-using AccessTokenClient.Serialization;
+using AccessTokenClient.Caching;
using AccessTokenClient.Tests.Helpers;
using FluentAssertions;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
-using System;
using System.Net;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
using Xunit;
-namespace AccessTokenClient.Tests
+namespace AccessTokenClient.Tests;
+
+public class TokenClientCachingDecoratorTests
{
- public class TokenClientCachingDecoratorTests
+ [Fact]
+ public async Task EnsureTokenResponseIsReturnedWhenCacheKeyIsFound()
{
- [Fact]
- public async Task EnsureTokenResponseIsReturnedWhenCacheKeyIsFound()
- {
- const string Response = @"{""access_token"":""1234567890"",""token_type"":""Bearer"",""expires_in"":7199}";
+ const string Response = @"{""access_token"":""1234567890"",""token_type"":""Bearer"",""expires_in"":7199}";
- var logger = new NullLogger();
- var messageHandler = new MockHttpMessageHandler(Response, HttpStatusCode.OK);
- var httpClient = new HttpClient(messageHandler);
+ var logger = new NullLogger();
+ var messageHandler = new MockHttpMessageHandler(Response, HttpStatusCode.OK);
+ var httpClient = new HttpClient(messageHandler);
- // Set-up the token response cache mock:
- var cacheMock = new Mock();
- cacheMock.Setup(m => m.Get(It.IsAny(), It.IsAny())).ReturnsAsync(new TokenResponse
- {
- AccessToken = "1234567890"
- });
+ // Set-up the token response cache mock:
+ var cacheMock = new Mock();
+ cacheMock.Setup(m => m.Get(It.IsAny(), It.IsAny())).ReturnsAsync(new TokenResponse
+ {
+ AccessToken = "1234567890"
+ });
- var decoratorLogger = new NullLogger();
+ var decoratorLogger = new NullLogger();
- // Set-up the key generator mock:
- var keyGeneratorMock = new Mock();
- keyGeneratorMock.Setup(m => m.GenerateTokenRequestKey(It.IsAny(), It.IsAny())).Returns("KEY-123");
+ // Set-up the key generator mock:
+ var keyGeneratorMock = new Mock();
+ keyGeneratorMock.Setup(m => m.GenerateTokenRequestKey(It.IsAny(), It.IsAny())).Returns("KEY-123");
- var mockTransformer = new Mock();
+ var mockTransformer = new Mock();
- // Set-up the access token client, token client, and the caching decorator:
- var tokenClient = new TokenClient(logger, httpClient, new ResponseDeserializer());
+ // Set-up the access token client, token client, and the caching decorator:
+ var tokenClient = new TokenClient(logger, httpClient);
- ITokenClient cachingDecorator = new TokenClientCachingDecorator(
- decoratorLogger,
- tokenClient,
- new TokenClientCacheOptions(),
- cacheMock.Object,
- keyGeneratorMock.Object,
- mockTransformer.Object
- );
+ ITokenClient cachingDecorator = new TokenClientCachingDecorator(
+ decoratorLogger,
+ tokenClient,
+ new TokenClientCacheOptions(),
+ cacheMock.Object,
+ keyGeneratorMock.Object,
+ mockTransformer.Object
+ );
- var tokenResponse = await cachingDecorator.RequestAccessToken(new TokenRequest
- {
- TokenEndpoint = "http://www.token-endpoint.com",
- ClientIdentifier = "client-identifier",
- ClientSecret = "client-secret",
- Scopes = new[] { "scope:read" }
- });
-
- tokenResponse.ShouldNotBeNull();
-
- messageHandler.NumberOfCalls.Should().Be(0);
- }
-
- [Fact]
- public async Task EnsureTokenResponseIsReturnedWhenCacheKeyIsNotFound()
+ var tokenResponse = await cachingDecorator.RequestAccessToken(new TokenRequest
{
- const string Response = @"{""access_token"":""1234567890"",""token_type"":""Bearer"",""expires_in"":7199}";
+ TokenEndpoint = "http://www.token-endpoint.com",
+ ClientIdentifier = "client-identifier",
+ ClientSecret = "client-secret",
+ Scopes = new[] { "scope:read" }
+ });
- var logger = new NullLogger();
- var messageHandler = new MockHttpMessageHandler(Response, HttpStatusCode.OK);
- var httpClient = new HttpClient(messageHandler);
+ tokenResponse.ShouldNotBeNull();
- var decoratorLogger = new NullLogger();
+ messageHandler.NumberOfCalls.Should().Be(0);
+ }
- // Set-up the token response cache mock:
- var cacheMock = new Mock();
- cacheMock.Setup(m => m.Get(It.IsAny(), It.IsAny())).ReturnsAsync((TokenResponse)null);
+ [Fact]
+ public async Task EnsureTokenResponseIsReturnedWhenCacheKeyIsNotFound()
+ {
+ const string Response = @"{""access_token"":""1234567890"",""token_type"":""Bearer"",""expires_in"":7199}";
- // Set-up the key generator mock:
- var keyGeneratorMock = new Mock();
- keyGeneratorMock.Setup(m => m.GenerateTokenRequestKey(It.IsAny(), It.IsAny())).Returns("KEY-123");
+ var logger = new NullLogger();
+ var messageHandler = new MockHttpMessageHandler(Response, HttpStatusCode.OK);
+ var httpClient = new HttpClient(messageHandler);
- var mockTransformer = new Mock();
+ var decoratorLogger = new NullLogger();
- // Set-up the token client and the caching decorator:
- var tokenClient = new TokenClient(logger, httpClient, new ResponseDeserializer());
+ // Set-up the token response cache mock:
+ var cacheMock = new Mock();
+ cacheMock.Setup(m => m.Get(It.IsAny(), It.IsAny())).ReturnsAsync((TokenResponse)null);
- ITokenClient cachingDecorator = new TokenClientCachingDecorator(
- decoratorLogger,
- tokenClient,
- new TokenClientCacheOptions(),
- cacheMock.Object,
- keyGeneratorMock.Object,
- mockTransformer.Object
- );
+ // Set-up the key generator mock:
+ var keyGeneratorMock = new Mock();
+ keyGeneratorMock.Setup(m => m.GenerateTokenRequestKey(It.IsAny(), It.IsAny())).Returns("KEY-123");
- var tokenResponse = await cachingDecorator.RequestAccessToken(new TokenRequest
- {
- TokenEndpoint = "http://www.token-endpoint.com",
- ClientIdentifier = "client-identifier",
- ClientSecret = "client-secret",
- Scopes = new[] { "scope:read" }
- });
+ var mockTransformer = new Mock();
- tokenResponse.ShouldNotBeNull();
- tokenResponse.AccessToken.ShouldNotBeNull();
- tokenResponse.AccessToken.Should().Be("1234567890");
+ // Set-up the token client and the caching decorator:
+ var tokenClient = new TokenClient(logger, httpClient);
- messageHandler.NumberOfCalls.Should().Be(1);
- }
+ ITokenClient cachingDecorator = new TokenClientCachingDecorator(
+ decoratorLogger,
+ tokenClient,
+ new TokenClientCacheOptions(),
+ cacheMock.Object,
+ keyGeneratorMock.Object,
+ mockTransformer.Object
+ );
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public async Task EnsureTokenResponseReturnedWhenCacheSetOperationIsSuccessfulOrFails(bool setResult)
+ var tokenResponse = await cachingDecorator.RequestAccessToken(new TokenRequest
{
- const string Response = @"{""access_token"":""1234567890"",""token_type"":""Bearer"",""expires_in"":7199}";
+ TokenEndpoint = "http://www.token-endpoint.com",
+ ClientIdentifier = "client-identifier",
+ ClientSecret = "client-secret",
+ Scopes = new[] { "scope:read" }
+ });
- var logger = new NullLogger();
- var messageHandler = new MockHttpMessageHandler(Response, HttpStatusCode.OK);
- var httpClient = new HttpClient(messageHandler);
+ tokenResponse.ShouldNotBeNull();
+ tokenResponse.AccessToken.ShouldNotBeNull();
+ tokenResponse.AccessToken.Should().Be("1234567890");
- var decoratorLogger = new NullLogger();
+ messageHandler.NumberOfCalls.Should().Be(1);
+ }
- // Set-up the token response cache mock:
- var cacheMock = new Mock();
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task EnsureTokenResponseReturnedWhenCacheSetOperationIsSuccessfulOrFails(bool setResult)
+ {
+ const string Response = @"{""access_token"":""1234567890"",""token_type"":""Bearer"",""expires_in"":7199}";
- cacheMock
- .Setup(m => m.Get(It.IsAny(), It.IsAny()))
- .ReturnsAsync((TokenResponse)null);
+ var logger = new NullLogger();
+ var messageHandler = new MockHttpMessageHandler(Response, HttpStatusCode.OK);
+ var httpClient = new HttpClient(messageHandler);
- cacheMock
- .Setup(m => m.Set(It.IsAny(),It.IsAny(), It.IsAny(), It.IsAny()))
- .ReturnsAsync(setResult);
+ var decoratorLogger = new NullLogger();
- // Set-up the key generator mock:
- var keyGeneratorMock = new Mock();
- keyGeneratorMock.Setup(m => m.GenerateTokenRequestKey(It.IsAny(), It.IsAny())).Returns("KEY-123");
+ // Set-up the token response cache mock:
+ var cacheMock = new Mock();
- var mockTransformer = new Mock();
+ cacheMock
+ .Setup(m => m.Get(It.IsAny(), It.IsAny()))
+ .ReturnsAsync((TokenResponse?)null);
- // Set-up the token client and the caching decorator:
- var tokenClient = new TokenClient(logger, httpClient, new ResponseDeserializer());
+ cacheMock
+ .Setup(m => m.Set(It.IsAny(),It.IsAny(), It.IsAny(), It.IsAny()))
+ .ReturnsAsync(setResult);
- ITokenClient cachingDecorator = new TokenClientCachingDecorator(
- decoratorLogger,
- tokenClient,
- new TokenClientCacheOptions(),
- cacheMock.Object,
- keyGeneratorMock.Object,
- mockTransformer.Object
- );
+ // Set-up the key generator mock:
+ var keyGeneratorMock = new Mock();
+ keyGeneratorMock.Setup(m => m.GenerateTokenRequestKey(It.IsAny(), It.IsAny())).Returns("KEY-123");
- var tokenResponse = await cachingDecorator.RequestAccessToken(new TokenRequest
- {
- TokenEndpoint = "http://www.token-endpoint.com",
- ClientIdentifier = "client-identifier",
- ClientSecret = "client-secret",
- Scopes = new[] { "scope:read" }
- });
+ var mockTransformer = new Mock();
- tokenResponse.ShouldNotBeNull();
- tokenResponse.AccessToken.ShouldNotBeNull();
- tokenResponse.AccessToken.Should().Be("1234567890");
+ // Set-up the token client and the caching decorator:
+ var tokenClient = new TokenClient(logger, httpClient);
- messageHandler.NumberOfCalls.Should().Be(1);
- }
+ ITokenClient cachingDecorator = new TokenClientCachingDecorator(
+ decoratorLogger,
+ tokenClient,
+ new TokenClientCacheOptions(),
+ cacheMock.Object,
+ keyGeneratorMock.Object,
+ mockTransformer.Object
+ );
- [Fact]
- public async Task EnsureTokenResponseIsReturnedWhenScopesAreNotSpecified()
+ var tokenResponse = await cachingDecorator.RequestAccessToken(new TokenRequest
{
- const string Response = @"{""access_token"":""1234567890"",""token_type"":""Bearer"",""expires_in"":7199}";
+ TokenEndpoint = "http://www.token-endpoint.com",
+ ClientIdentifier = "client-identifier",
+ ClientSecret = "client-secret",
+ Scopes = new[] { "scope:read" }
+ });
- var logger = new NullLogger();
- var messageHandler = new MockHttpMessageHandler(Response, HttpStatusCode.OK);
- var httpClient = new HttpClient(messageHandler);
+ tokenResponse.ShouldNotBeNull();
+ tokenResponse.AccessToken.ShouldNotBeNull();
+ tokenResponse.AccessToken.Should().Be("1234567890");
- var decoratorLogger = new NullLogger();
+ messageHandler.NumberOfCalls.Should().Be(1);
+ }
- // Set-up the token response cache mock:
- var cacheMock = new Mock();
- cacheMock.Setup(m => m.Get(It.IsAny(), It.IsAny())).ReturnsAsync((TokenResponse)null);
+ [Fact]
+ public async Task EnsureTokenResponseIsReturnedWhenScopesAreNotSpecified()
+ {
+ const string Response = @"{""access_token"":""1234567890"",""token_type"":""Bearer"",""expires_in"":7199}";
- // Set-up the key generator mock:
- var keyGeneratorMock = new Mock();
- keyGeneratorMock.Setup(m => m.GenerateTokenRequestKey(It.IsAny(), It.IsAny())).Returns("KEY-123");
+ var logger = new NullLogger();
+ var messageHandler = new MockHttpMessageHandler(Response, HttpStatusCode.OK);
+ var httpClient = new HttpClient(messageHandler);
- var mockTransformer = new Mock();
+ var decoratorLogger = new NullLogger();
- // Set-up the token client and the caching decorator:
- var tokenClient = new TokenClient(logger, httpClient, new ResponseDeserializer());
+ // Set-up the token response cache mock:
+ var cacheMock = new Mock();
+ cacheMock.Setup(m => m.Get(It.IsAny(), It.IsAny())).ReturnsAsync((TokenResponse)null);
- var cachingDecorator = new TokenClientCachingDecorator(
- decoratorLogger,
- tokenClient,
- new TokenClientCacheOptions(),
- cacheMock.Object,
- keyGeneratorMock.Object,
- mockTransformer.Object
- );
+ // Set-up the key generator mock:
+ var keyGeneratorMock = new Mock();
+ keyGeneratorMock.Setup(m => m.GenerateTokenRequestKey(It.IsAny(), It.IsAny())).Returns("KEY-123");
- var tokenResponse = await cachingDecorator.RequestAccessToken(new TokenRequest
- {
- TokenEndpoint = "http://www.token-endpoint.com",
- ClientIdentifier = "client-identifier",
- ClientSecret = "client-secret",
- });
+ var mockTransformer = new Mock();
- tokenResponse.ShouldNotBeNull();
- tokenResponse.AccessToken.ShouldNotBeNull();
- tokenResponse.AccessToken.Should().Be("1234567890");
+ // Set-up the token client and the caching decorator:
+ var tokenClient = new TokenClient(logger, httpClient);
- messageHandler.NumberOfCalls.Should().Be(1);
- }
+ var cachingDecorator = new TokenClientCachingDecorator(
+ decoratorLogger,
+ tokenClient,
+ new TokenClientCacheOptions(),
+ cacheMock.Object,
+ keyGeneratorMock.Object,
+ mockTransformer.Object
+ );
- [Fact]
- public void EnsureExceptionThrownWhenCancellationRequested()
- {
- Func> creationAction = async () =>
- {
- // Cancel the token before executing the decorator so it throws immediately:
- var source = new CancellationTokenSource();
- source.Cancel();
-
- var decorator = new TokenClientCachingDecorator(
- new NullLogger(),
- new TokenClient(new NullLogger(), new HttpClient(), new ResponseDeserializer()),
- new TokenClientCacheOptions(),
- new MemoryTokenResponseCache(new MemoryCache(new MemoryCacheOptions())),
- new TokenRequestKeyGenerator(),
- new DefaultAccessTokenTransformer());
-
- var response = await decorator.RequestAccessToken(new TokenRequest
- {
- TokenEndpoint = "http://www.token-endpoint.com",
- ClientIdentifier = "client-identifier",
- ClientSecret = "client-secret",
- Scopes = new[] { "scope:read" }
- }, cancellationToken: source.Token);
-
- return response;
- };
-
- creationAction.Should().Throw();
- }
-
- [Fact]
- public void EnsureExceptionThrownWhenLoggerIsNull()
+ var tokenResponse = await cachingDecorator.RequestAccessToken(new TokenRequest
{
- Func creationAction = () => new TokenClientCachingDecorator(
- null,
- new TokenClient(new NullLogger(), new HttpClient(), new ResponseDeserializer()),
- new TokenClientCacheOptions(),
- new MemoryTokenResponseCache(new MemoryCache(new MemoryCacheOptions())),
- new TokenRequestKeyGenerator(),
- new DefaultAccessTokenTransformer());
+ TokenEndpoint = "http://www.token-endpoint.com",
+ ClientIdentifier = "client-identifier",
+ ClientSecret = "client-secret",
+ });
- creationAction.Should().Throw();
- }
+ tokenResponse.ShouldNotBeNull();
+ tokenResponse.AccessToken.ShouldNotBeNull();
+ tokenResponse.AccessToken.Should().Be("1234567890");
- [Fact]
- public void EnsureExceptionThrownWhenTokenClientIsNull()
- {
- Func creationAction = () => new TokenClientCachingDecorator(
- new NullLogger(),
- null,
- new TokenClientCacheOptions(),
- new MemoryTokenResponseCache(new MemoryCache(new MemoryCacheOptions())),
- new TokenRequestKeyGenerator(),
- new DefaultAccessTokenTransformer());
-
- creationAction.Should().Throw();
- }
-
- [Fact]
- public void EnsureExceptionThrownWhenTokenClientCacheOptionsIsNull()
- {
- Func creationAction = () => new TokenClientCachingDecorator(
- new NullLogger(),
- new TokenClient(new NullLogger(), new HttpClient(), new ResponseDeserializer()),
- null,
- new MemoryTokenResponseCache(new MemoryCache(new MemoryCacheOptions())),
- new TokenRequestKeyGenerator(),
- new DefaultAccessTokenTransformer());
-
- creationAction.Should().Throw();
- }
+ messageHandler.NumberOfCalls.Should().Be(1);
+ }
- [Fact]
- public void EnsureExceptionThrownWhenTokenResponseCacheIsNull()
+ [Fact]
+ public void EnsureExceptionThrownWhenCancellationRequested()
+ {
+ Func> creationAction = async () =>
{
- Func creationAction = () => new TokenClientCachingDecorator(
- new NullLogger(),
- new TokenClient(new NullLogger(), new HttpClient(), new ResponseDeserializer()),
- new TokenClientCacheOptions(),
- null,
- new TokenRequestKeyGenerator(),
- new DefaultAccessTokenTransformer());
+ // Cancel the token before executing the decorator so it throws immediately:
+ var source = new CancellationTokenSource();
+ source.Cancel();
- creationAction.Should().Throw();
- }
-
- [Fact]
- public void EnsureExceptionThrownWhenKeyGeneratorIsNull()
- {
- Func creationAction = () => new TokenClientCachingDecorator(
+ var decorator = new TokenClientCachingDecorator(
new NullLogger(),
- new TokenClient(new NullLogger(), new HttpClient(), new ResponseDeserializer()),
+ new TokenClient(new NullLogger(), new HttpClient()),
new TokenClientCacheOptions(),
new MemoryTokenResponseCache(new MemoryCache(new MemoryCacheOptions())),
- null,
+ new TokenRequestKeyGenerator(),
new DefaultAccessTokenTransformer());
- creationAction.Should().Throw();
- }
+ var response = await decorator.RequestAccessToken(new TokenRequest
+ {
+ TokenEndpoint = "http://www.token-endpoint.com",
+ ClientIdentifier = "client-identifier",
+ ClientSecret = "client-secret",
+ Scopes = new[] { "scope:read" }
+ }, cancellationToken: source.Token);
- [Fact]
- public void EnsureExceptionThrownWhenTransformerIsNull()
- {
- Func creationAction = () => new TokenClientCachingDecorator(
- new NullLogger(),
- new TokenClient(new NullLogger(), new HttpClient(), new ResponseDeserializer()),
- new TokenClientCacheOptions(),
- new MemoryTokenResponseCache(new MemoryCache(new MemoryCacheOptions())),
- new TokenRequestKeyGenerator(),
- null);
+ return response;
+ };
- creationAction.Should().Throw();
- }
+ creationAction.Should().ThrowAsync();
}
}
\ No newline at end of file
diff --git a/AccessTokenClient.Tests/TokenClientRetryTests.cs b/AccessTokenClient.Tests/TokenClientRetryTests.cs
index e0d2bf7..8133b79 100644
--- a/AccessTokenClient.Tests/TokenClientRetryTests.cs
+++ b/AccessTokenClient.Tests/TokenClientRetryTests.cs
@@ -1,112 +1,108 @@
-using AccessTokenClient.Extensions;
-using AccessTokenClient.Serialization;
+using AccessTokenClient.Extensions;
using AccessTokenClient.Tests.Helpers;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Net;
-using System.Net.Http;
-using System.Threading.Tasks;
using Xunit;
-namespace AccessTokenClient.Tests
+namespace AccessTokenClient.Tests;
+
+public class TokenClientRetryTests
{
- public class TokenClientRetryTests
+ [Theory]
+ [InlineData(HttpStatusCode.NotFound)]
+ [InlineData(HttpStatusCode.RequestTimeout)]
+ [InlineData(HttpStatusCode.InternalServerError)]
+ public async Task EnsureTokenRequestRetriedOnFailure(HttpStatusCode httpStatusCode)
{
- [Theory]
- [InlineData(HttpStatusCode.NotFound)]
- [InlineData(HttpStatusCode.RequestTimeout)]
- [InlineData(HttpStatusCode.InternalServerError)]
- public async Task EnsureTokenRequestRetriedOnFailure(HttpStatusCode httpStatusCode)
- {
- var services = new ServiceCollection();
+ var services = new ServiceCollection();
- var mockHandler = new MockHttpMessageHandler(string.Empty, httpStatusCode);
+ var mockHandler = new MockHttpMessageHandler(string.Empty, httpStatusCode);
- // Configure an http client with the default retry policy handler and a mock http message handler:
- services
- .AddHttpClient("TestingClient")
- .AddPolicyHandler((p, _) => AccessTokenClientPolicy.GetDefaultRetryPolicy())
- .AddHttpMessageHandler(() => mockHandler);
+ // Configure an http client with the default retry policy handler and a mock http message handler:
+ services
+ .AddHttpClient("TestingClient")
+ .AddPolicyHandler((_, _) => AccessTokenClientPolicy.GetDefaultRetryPolicy())
+ .AddHttpMessageHandler(() => mockHandler);
- var provider = services.BuildServiceProvider();
+ var provider = services.BuildServiceProvider();
- // Retrieve the configured client from the service provider:
- var httpClient = provider
- .GetRequiredService()
- .CreateClient("TestingClient");
+ // Retrieve the configured client from the service provider:
+ var httpClient = provider
+ .GetRequiredService()
+ .CreateClient("TestingClient");
- var logger = provider.GetRequiredService>();
- var tokenClient = new TokenClient(logger, httpClient, new ResponseDeserializer());
+ var logger = provider.GetRequiredService>();
+ var tokenClient = new TokenClient(logger, httpClient);
- try
- {
- await tokenClient.RequestAccessToken(new TokenRequest
- {
- TokenEndpoint = "http://www.token-endpoint.com",
- ClientIdentifier = "client-identifier",
- ClientSecret = "client-secret",
- Scopes = new[] { "scope:read" }
- });
- }
- catch
+ try
+ {
+ await tokenClient.RequestAccessToken(new TokenRequest
{
- // Swallowed purposefully.
- }
-
- // Currently, the retry policy is configured to retry twice, thus the total number of calls will be three:
- mockHandler.NumberOfCalls.Should().Be(3);
+ TokenEndpoint = "http://www.token-endpoint.com",
+ ClientIdentifier = "client-identifier",
+ ClientSecret = "client-secret",
+ Scopes = new[] { "scope:read" }
+ });
}
-
- [Theory]
- [InlineData(HttpStatusCode.NotFound)]
- [InlineData(HttpStatusCode.RequestTimeout)]
- [InlineData(HttpStatusCode.InternalServerError)]
- public async Task EnsureTokenRequestRetriedOnFailureWhenLoggerSpecified(HttpStatusCode httpStatusCode)
+ catch
{
- var services = new ServiceCollection();
+ // Swallowed purposefully.
+ }
+
+ // Currently, the retry policy is configured to retry twice, thus the total number of calls will be three:
+ mockHandler.NumberOfCalls.Should().Be(3);
+ }
- var mockHandler = new MockHttpMessageHandler(string.Empty, httpStatusCode);
+ [Theory]
+ [InlineData(HttpStatusCode.NotFound)]
+ [InlineData(HttpStatusCode.RequestTimeout)]
+ [InlineData(HttpStatusCode.InternalServerError)]
+ public async Task EnsureTokenRequestRetriedOnFailureWhenLoggerSpecified(HttpStatusCode httpStatusCode)
+ {
+ var services = new ServiceCollection();
- services.AddLogging();
+ var mockHandler = new MockHttpMessageHandler(string.Empty, httpStatusCode);
- // Configure an http client with the default retry policy handler and a mock http message handler:
- services
- .AddHttpClient("TestingClient")
- .AddPolicyHandler((p, _) =>
- {
- var policyLogger = p.GetRequiredService>();
- return AccessTokenClientPolicy.GetDefaultRetryPolicy(policyLogger);
- })
- .AddHttpMessageHandler(() => mockHandler);
+ services.AddLogging();
- var provider = services.BuildServiceProvider();
+ // Configure an http client with the default retry policy handler and a mock http message handler:
+ services
+ .AddHttpClient("TestingClient")
+ .AddPolicyHandler((p, _) =>
+ {
+ var policyLogger = p.GetRequiredService>();
+ return AccessTokenClientPolicy.GetDefaultRetryPolicy(policyLogger);
+ })
+ .AddHttpMessageHandler(() => mockHandler);
- // Retrieve the configured client from the service provider:
- var httpClient = provider
- .GetRequiredService()
- .CreateClient("TestingClient");
+ var provider = services.BuildServiceProvider();
- var logger = provider.GetRequiredService>();
- var tokenClient = new TokenClient(logger, httpClient, new ResponseDeserializer());
+ // Retrieve the configured client from the service provider:
+ var httpClient = provider
+ .GetRequiredService()
+ .CreateClient("TestingClient");
- try
- {
- await tokenClient.RequestAccessToken(new TokenRequest
- {
- TokenEndpoint = "http://www.token-endpoint.com",
- ClientIdentifier = "client-identifier",
- ClientSecret = "client-secret",
- Scopes = new[] { "scope:read" }
- });
- }
- catch
- {
- // Swallowed purposefully.
- }
+ var logger = provider.GetRequiredService>();
+ var tokenClient = new TokenClient(logger, httpClient);
- // Currently, the retry policy is configured to retry twice, thus the total number of calls will be three:
- mockHandler.NumberOfCalls.Should().Be(3);
+ try
+ {
+ await tokenClient.RequestAccessToken(new TokenRequest
+ {
+ TokenEndpoint = "http://www.token-endpoint.com",
+ ClientIdentifier = "client-identifier",
+ ClientSecret = "client-secret",
+ Scopes = new[] { "scope:read" }
+ });
+ }
+ catch
+ {
+ // Swallowed purposefully.
}
+
+ // Currently, the retry policy is configured to retry twice, thus the total number of calls will be three:
+ mockHandler.NumberOfCalls.Should().Be(3);
}
}
\ No newline at end of file
diff --git a/AccessTokenClient.Tests/TokenClientUnitTests.cs b/AccessTokenClient.Tests/TokenClientUnitTests.cs
index 89c339d..6d08c22 100644
--- a/AccessTokenClient.Tests/TokenClientUnitTests.cs
+++ b/AccessTokenClient.Tests/TokenClientUnitTests.cs
@@ -1,320 +1,213 @@
-using AccessTokenClient.Serialization;
using AccessTokenClient.Tests.Helpers;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
-using Moq;
-using System;
using System.Net;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
using Xunit;
-namespace AccessTokenClient.Tests
+namespace AccessTokenClient.Tests;
+
+public class TokenClientUnitTests
{
- public class TokenClientUnitTests
+ [Fact]
+ public async Task EnsureExceptionThrownWhenAccessTokenIsEmpty()
{
- [Fact]
- public async Task EnsureExceptionThrownWhenAccessTokenIsEmpty()
- {
- const string Response = @"{""access_token"":"""",""token_type"":""Bearer"",""expires_in"":7199}";
+ const string Response = @"{""access_token"":"""",""token_type"":""Bearer"",""expires_in"":7199}";
- var logger = new NullLogger();
- var messageHandler = new MockHttpMessageHandler(Response, HttpStatusCode.OK);
- var httpClient = new HttpClient(messageHandler);
+ var logger = new NullLogger();
+ var messageHandler = new MockHttpMessageHandler(Response, HttpStatusCode.OK);
+ var httpClient = new HttpClient(messageHandler);
- var tokenClient = new TokenClient(logger, httpClient, new ResponseDeserializer());
+ var tokenClient = new TokenClient(logger, httpClient);
- Func> function = async () => await tokenClient.RequestAccessToken(new TokenRequest
- {
- TokenEndpoint = "http://www.token-endpoint.com",
- ClientIdentifier = "client-identifier",
- ClientSecret = "client-secret",
- Scopes = new[] { "scope:read" }
- });
-
- await function.Should().ThrowAsync();
- }
-
- [Fact]
- public async Task EnsureExceptionThrownWhenTokenResponseIsEmpty()
+ Func> function = async () => await tokenClient.RequestAccessToken(new TokenRequest
{
- const string Response = "";
+ TokenEndpoint = "http://www.token-endpoint.com",
+ ClientIdentifier = "client-identifier",
+ ClientSecret = "client-secret",
+ Scopes = new[] { "scope:read" }
+ });
- var logger = new NullLogger();
- var messageHandler = new MockHttpMessageHandler(Response, HttpStatusCode.OK);
- var httpClient = new HttpClient(messageHandler);
+ await function.Should().ThrowAsync();
+ }
- var tokenClient = new TokenClient(logger, httpClient, new ResponseDeserializer());
+ [Fact]
+ public async Task EnsureExceptionThrownWhenTokenResponseIsEmpty()
+ {
+ const string Response = "";
- Func> function = async () => await tokenClient.RequestAccessToken(new TokenRequest
- {
- TokenEndpoint = "http://www.token-endpoint.com",
- ClientIdentifier = "client-identifier",
- ClientSecret = "client-secret",
- Scopes = new[] { "scope:read" }
- });
+ var logger = new NullLogger();
+ var messageHandler = new MockHttpMessageHandler(Response, HttpStatusCode.OK);
+ var httpClient = new HttpClient(messageHandler);
- await function.Should().ThrowAsync();
- }
+ var tokenClient = new TokenClient(logger, httpClient);
- [Fact]
- public async Task EnsureExceptionThrownWhenServerErrorReturned()
+ Func> function = async () => await tokenClient.RequestAccessToken(new TokenRequest
{
- const string Response = "";
+ TokenEndpoint = "http://www.token-endpoint.com",
+ ClientIdentifier = "client-identifier",
+ ClientSecret = "client-secret",
+ Scopes = new[] { "scope:read" }
+ });
- var logger = new NullLogger();
- var messageHandler = new MockHttpMessageHandler(Response, HttpStatusCode.InternalServerError);
- var httpClient = new HttpClient(messageHandler);
-
- var mockDeserializer = new Mock();
- mockDeserializer.Setup(m => m.Deserialize(It.IsAny())).Returns((TokenResponse)null);
+ await function.Should().ThrowAsync();
+ }
- var tokenClient = new TokenClient(logger, httpClient, mockDeserializer.Object);
+ [Theory]
+ [InlineData(HttpStatusCode.NotFound)]
+ [InlineData(HttpStatusCode.InternalServerError)]
+ [InlineData(HttpStatusCode.ServiceUnavailable)]
+ public async Task EnsureExceptionThrownWhenNonSuccessStatusCodeReturned(HttpStatusCode statusCode)
+ {
+ const string Response = "";
- Func> function = async () => await tokenClient.RequestAccessToken(new TokenRequest
- {
- TokenEndpoint = "http://www.token-endpoint.com",
- ClientIdentifier = "client-identifier",
- ClientSecret = "client-secret",
- Scopes = new[] { "scope:read" }
- });
+ var logger = new NullLogger();
+ var messageHandler = new MockHttpMessageHandler(Response, statusCode);
+ var httpClient = new HttpClient(messageHandler);
- await function.Should().ThrowAsync();
- }
+ var tokenClient = new TokenClient(logger, httpClient);
- [Fact]
- public async Task EnsureExceptionThrownWhenDeserializerReturnsNullTokenResponse()
+ Func> function = async () => await tokenClient.RequestAccessToken(new TokenRequest
{
- const string Response = @"{""access_token"":""1234567890"",""token_type"":""Bearer"",""expires_in"":7199}";
+ TokenEndpoint = "http://www.token-endpoint.com",
+ ClientIdentifier = "client-identifier",
+ ClientSecret = "client-secret",
+ Scopes = new[] { "scope:read" }
+ });
- var logger = new NullLogger();
- var messageHandler = new MockHttpMessageHandler(Response, HttpStatusCode.OK);
- var httpClient = new HttpClient(messageHandler);
-
- var mockDeserializer = new Mock();
- mockDeserializer.Setup(m => m.Deserialize(It.IsAny())).Returns((TokenResponse)null);
-
- var tokenClient = new TokenClient(logger, httpClient, mockDeserializer.Object);
+ await function.Should().ThrowAsync();
+ }
- Func> function = async () => await tokenClient.RequestAccessToken(new TokenRequest
- {
- TokenEndpoint = "http://www.token-endpoint.com",
- ClientIdentifier = "client-identifier",
- ClientSecret = "client-secret",
- Scopes = new[] { "scope:read" }
- });
+ [Fact]
+ public async Task EnsureExceptionThrownWhenCancellationRequested()
+ {
+ var logger = new NullLogger();
+ var messageHandler = new MockHttpMessageHandler(string.Empty, HttpStatusCode.OK);
+ var httpClient = new HttpClient(messageHandler);
- await function.Should().ThrowAsync();
- }
+ // Cancel the token before executing the token client so it throws immediately:
+ var source = new CancellationTokenSource();
+ source.Cancel();
+
+ var client = new TokenClient(logger, httpClient);
- [Fact]
- public async Task EnsureExceptionThrownWhenDeserializerReturnsEmptyAccessToken()
+ Func> function = async () =>
{
- const string Response = @"{""access_token"":""1234567890"",""token_type"":""Bearer"",""expires_in"":7199}";
-
- var logger = new NullLogger();
- var messageHandler = new MockHttpMessageHandler(Response, HttpStatusCode.OK);
- var httpClient = new HttpClient(messageHandler);
-
- var mockDeserializer = new Mock();
- mockDeserializer.Setup(m => m.Deserialize(It.IsAny())).Returns(new TokenResponse
- {
- AccessToken = "",
- ExpiresIn = 3000
- });
-
- var tokenClient = new TokenClient(logger, httpClient, mockDeserializer.Object);
-
- Func> function = async () => await tokenClient.RequestAccessToken(new TokenRequest
+ var tokenResponse = await client.RequestAccessToken(new TokenRequest
{
TokenEndpoint = "http://www.token-endpoint.com",
ClientIdentifier = "client-identifier",
ClientSecret = "client-secret",
Scopes = new[] { "scope:read" }
- });
+ }, cancellationToken: source.Token);
- await function.Should().ThrowAsync();
- }
+ return tokenResponse;
+ };
- [Fact]
- public void EnsureExceptionThrownWhenLoggerIsNull()
- {
- var messageHandler = new MockHttpMessageHandler(string.Empty, HttpStatusCode.OK);
- var httpClient = new HttpClient(messageHandler);
- var mockDeserializer = new Mock();
- mockDeserializer.Setup(m => m.Deserialize(It.IsAny())).Returns((TokenResponse)null);
-
- Action action = () =>
- {
- var _ = new TokenClient(null, httpClient, new ResponseDeserializer());
- };
-
- action.Should().Throw();
- }
+ await function.Should().ThrowAsync();
+ }
- [Fact]
- public void EnsureExceptionThrownWhenClientIsNull()
- {
- var logger = new NullLogger();
- var mockDeserializer = new Mock();
- mockDeserializer.Setup(m => m.Deserialize(It.IsAny())).Returns((TokenResponse)null);
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public async Task EnsureExceptionThrownWhenTokenEndpointNotSet(string tokenEndpoint)
+ {
+ var response = string.Empty;
- Action action = () =>
- {
- var _ = new TokenClient(logger, null, new ResponseDeserializer());
- };
+ var logger = new NullLogger();
+ var messageHandler = new MockHttpMessageHandler(response, HttpStatusCode.OK);
+ var httpClient = new HttpClient(messageHandler);
- action.Should().Throw();
- }
+ var tokenClient = new TokenClient(logger, httpClient);
- [Fact]
- public void EnsureExceptionThrownWhenDeserializerIsNull()
+ Func> function = async () => await tokenClient.RequestAccessToken(new TokenRequest
{
- var logger = new NullLogger();
- var messageHandler = new MockHttpMessageHandler(string.Empty, HttpStatusCode.OK);
- var httpClient = new HttpClient(messageHandler);
+ TokenEndpoint = tokenEndpoint,
+ ClientIdentifier = "client-identifier",
+ ClientSecret = "client-secret",
+ Scopes = new[] { "scope:read" }
+ });
- Action action = () =>
- {
- var _ = new TokenClient(logger, httpClient, null);
- };
+ await function.Should().ThrowAsync();
+ }
- action.Should().Throw();
- }
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public async Task EnsureExceptionThrownWhenClientIdentifierNotSet(string clientIdentifier)
+ {
+ var response = string.Empty;
- [Fact]
- public async Task EnsureExceptionThrownWhenCancellationRequested()
- {
- var logger = new NullLogger();
- var messageHandler = new MockHttpMessageHandler(string.Empty, HttpStatusCode.OK);
- var httpClient = new HttpClient(messageHandler);
+ var logger = new NullLogger();
+ var messageHandler = new MockHttpMessageHandler(response, HttpStatusCode.OK);
+ var httpClient = new HttpClient(messageHandler);
- // Cancel the token before executing the token client so it throws immediately:
- var source = new CancellationTokenSource();
- source.Cancel();
-
- var client = new TokenClient(logger, httpClient, new ResponseDeserializer());
+ var tokenClient = new TokenClient(logger, httpClient);
- Func> function = async () =>
- {
- var tokenResponse = await client.RequestAccessToken(new TokenRequest
- {
- TokenEndpoint = "http://www.token-endpoint.com",
- ClientIdentifier = "client-identifier",
- ClientSecret = "client-secret",
- Scopes = new[] { "scope:read" }
- }, cancellationToken: source.Token);
-
- return tokenResponse;
- };
-
- await function.Should().ThrowAsync();
- }
-
- [Theory]
- [InlineData(null)]
- [InlineData("")]
- [InlineData(" ")]
- public async Task EnsureExceptionThrownWhenTokenEndpointNotSet(string tokenEndpoint)
+ Func