committed by
GitHub
17 changed files with 1123 additions and 5 deletions
@ -0,0 +1,22 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFrameworks>net461;netcoreapp2.1;netcoreapp3.1;netstandard2.0;netstandard2.1</TargetFrameworks> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<IsShipping>false</IsShipping> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<Description>Quartz.NET integration package for the OpenIddict server services.</Description> |
||||
|
<PackageTags>$(PackageTags);quartz;server</PackageTags> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\OpenIddict.Server\OpenIddict.Server.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Quartz.Extensions.DependencyInjection" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,92 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using System; |
||||
|
using System.ComponentModel; |
||||
|
using OpenIddict.Server.Quartz; |
||||
|
using SR = OpenIddict.Abstractions.OpenIddictResources; |
||||
|
|
||||
|
namespace Microsoft.Extensions.DependencyInjection |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Exposes the necessary methods required to configure
|
||||
|
/// the OpenIddict server Quartz.NET integration.
|
||||
|
/// </summary>
|
||||
|
public class OpenIddictServerQuartzBuilder |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of <see cref="OpenIddictServerQuartzBuilder"/>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="services">The services collection.</param>
|
||||
|
public OpenIddictServerQuartzBuilder(IServiceCollection services) |
||||
|
=> Services = services ?? throw new ArgumentNullException(nameof(services)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the services collection.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public IServiceCollection Services { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Amends the default OpenIddict server Quartz.NET configuration.
|
||||
|
/// </summary>
|
||||
|
/// <param name="configuration">The delegate used to configure the OpenIddict options.</param>
|
||||
|
/// <remarks>This extension can be safely called multiple times.</remarks>
|
||||
|
/// <returns>The <see cref="OpenIddictServerQuartzBuilder"/>.</returns>
|
||||
|
public OpenIddictServerQuartzBuilder Configure(Action<OpenIddictServerQuartzOptions> configuration) |
||||
|
{ |
||||
|
if (configuration == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(configuration)); |
||||
|
} |
||||
|
|
||||
|
Services.Configure(configuration); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Disables authorizations pruning.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The <see cref="OpenIddictServerQuartzBuilder"/>.</returns>
|
||||
|
public OpenIddictServerQuartzBuilder DisableAuthorizationsPruning() |
||||
|
=> Configure(options => options.DisableAuthorizationsPruning = true); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Disables tokens pruning.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The <see cref="OpenIddictServerQuartzBuilder"/>.</returns>
|
||||
|
public OpenIddictServerQuartzBuilder DisableTokensPruning() |
||||
|
=> Configure(options => options.DisableTokensPruning = true); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the number of times a failed Quartz.NET job can be retried.
|
||||
|
/// </summary>
|
||||
|
/// <param name="count">The number of times a failed Quartz.NET job can be retried.</param>
|
||||
|
/// <returns>The <see cref="OpenIddictServerQuartzBuilder"/>.</returns>
|
||||
|
public OpenIddictServerQuartzBuilder SetMaximumRefireCount(int count) |
||||
|
{ |
||||
|
if (count < 0) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException(nameof(count), SR.GetResourceString(SR.ID1278)); |
||||
|
} |
||||
|
|
||||
|
return Configure(options => options.MaximumRefireCount = count); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public override bool Equals(object? obj) => base.Equals(obj); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public override int GetHashCode() => base.GetHashCode(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public override string? ToString() => base.ToString(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,101 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using Microsoft.Extensions.DependencyInjection.Extensions; |
||||
|
using OpenIddict.Server.Quartz; |
||||
|
using Quartz; |
||||
|
using SR = OpenIddict.Abstractions.OpenIddictResources; |
||||
|
|
||||
|
namespace Microsoft.Extensions.DependencyInjection |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Exposes extensions allowing to register the OpenIddict server Quartz.NET integration.
|
||||
|
/// </summary>
|
||||
|
public static class OpenIddictServerQuartzExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Registers the OpenIddict server Quartz.NET integration in the DI container.
|
||||
|
/// </summary>
|
||||
|
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
|
||||
|
/// <remarks>This extension can be safely called multiple times.</remarks>
|
||||
|
/// <returns>The <see cref="OpenIddictServerQuartzBuilder"/>.</returns>
|
||||
|
public static OpenIddictServerQuartzBuilder UseQuartz(this OpenIddictServerBuilder builder) |
||||
|
{ |
||||
|
if (builder == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(builder)); |
||||
|
} |
||||
|
|
||||
|
// Warning: the AddQuartz() method is deliberately not used as it's not idempotent.
|
||||
|
// Calling it at this point may override user-defined services (e.g Quartz DI support).
|
||||
|
|
||||
|
builder.Services.TryAddTransient<OpenIddictServerQuartzJob>(); |
||||
|
|
||||
|
// To ensure this method can be safely called multiple times, the job details
|
||||
|
// of the OpenIddict server job are only added if no existing IJobDetail instance
|
||||
|
// pointing to OpenIddictServerQuartzJob was already registered in the DI container.
|
||||
|
if (!builder.Services.Any(descriptor => descriptor.ServiceType == typeof(IJobDetail) && |
||||
|
descriptor.ImplementationInstance is IJobDetail job && |
||||
|
job.Key.Equals(OpenIddictServerQuartzJob.Identity))) |
||||
|
{ |
||||
|
builder.Services.AddSingleton( |
||||
|
JobBuilder.Create<OpenIddictServerQuartzJob>() |
||||
|
.StoreDurably() |
||||
|
.WithIdentity(OpenIddictServerQuartzJob.Identity) |
||||
|
.WithDescription(SR.GetResourceString(SR.ID9000)) |
||||
|
.Build()); |
||||
|
} |
||||
|
|
||||
|
// To ensure this method can be safely called multiple times, the trigger details
|
||||
|
// of the OpenIddict server job are only added if no existing ITrigger instance
|
||||
|
// pointing to OpenIddictServerQuartzJob was already registered in the DI container.
|
||||
|
if (!builder.Services.Any(descriptor => descriptor.ServiceType == typeof(ITrigger) && |
||||
|
descriptor.ImplementationInstance is ITrigger trigger && |
||||
|
trigger.JobKey.Equals(OpenIddictServerQuartzJob.Identity))) |
||||
|
{ |
||||
|
// Note: this trigger uses a quite long interval (1 hour), which means it may be
|
||||
|
// potentially never reached if the application is shut down or recycled. As such,
|
||||
|
// this trigger is set up to fire immediately to ensure it's executed at least once.
|
||||
|
builder.Services.AddSingleton( |
||||
|
TriggerBuilder.Create() |
||||
|
.ForJob(OpenIddictServerQuartzJob.Identity) |
||||
|
.WithSimpleSchedule(options => options.WithIntervalInHours(1).RepeatForever()) |
||||
|
.WithDescription(SR.GetResourceString(SR.ID9001)) |
||||
|
.StartNow() |
||||
|
.Build()); |
||||
|
} |
||||
|
|
||||
|
return new OpenIddictServerQuartzBuilder(builder.Services); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Registers the OpenIddict server Quartz.NET integration in the DI container.
|
||||
|
/// </summary>
|
||||
|
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
|
||||
|
/// <param name="configuration">The configuration delegate used to configure the server services.</param>
|
||||
|
/// <remarks>This extension can be safely called multiple times.</remarks>
|
||||
|
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
|
||||
|
public static OpenIddictServerBuilder UseQuartz( |
||||
|
this OpenIddictServerBuilder builder, Action<OpenIddictServerQuartzBuilder> configuration) |
||||
|
{ |
||||
|
if (builder == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(builder)); |
||||
|
} |
||||
|
|
||||
|
if (configuration == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(configuration)); |
||||
|
} |
||||
|
|
||||
|
configuration(builder.UseQuartz()); |
||||
|
|
||||
|
return builder; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,208 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using OpenIddict.Abstractions; |
||||
|
using Quartz; |
||||
|
using SR = OpenIddict.Abstractions.OpenIddictResources; |
||||
|
|
||||
|
namespace OpenIddict.Server.Quartz |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a Quartz.NET job performing scheduled tasks for the OpenIddict server feature.
|
||||
|
/// </summary>
|
||||
|
[DisallowConcurrentExecution] |
||||
|
public class OpenIddictServerQuartzJob : IJob |
||||
|
{ |
||||
|
private readonly IOptionsMonitor<OpenIddictServerQuartzOptions> _options; |
||||
|
private readonly IServiceProvider _provider; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates a new instance of the <see cref="OpenIddictServerQuartzJob"/> class.
|
||||
|
/// </summary>
|
||||
|
public OpenIddictServerQuartzJob() => throw new InvalidOperationException(SR.GetResourceString(SR.ID1081)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates a new instance of the <see cref="OpenIddictServerQuartzJob"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="options">The OpenIddict server Quartz.NET options.</param>
|
||||
|
/// <param name="provider">The service provider.</param>
|
||||
|
public OpenIddictServerQuartzJob(IOptionsMonitor<OpenIddictServerQuartzOptions> options, IServiceProvider provider) |
||||
|
{ |
||||
|
_options = options; |
||||
|
_provider = provider; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the default identity assigned to this job.
|
||||
|
/// </summary>
|
||||
|
public static JobKey Identity { get; } = new JobKey( |
||||
|
name: typeof(OpenIddictServerQuartzJob).Name, |
||||
|
group: typeof(OpenIddictServerQuartzJob).Assembly.GetName().Name!); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public async Task Execute(IJobExecutionContext context) |
||||
|
{ |
||||
|
if (context == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
List<Exception>? exceptions = null; |
||||
|
|
||||
|
// Note: this job is registered as a transient service. As such, it cannot directly depend on scoped services
|
||||
|
// like the core managers. To work around this limitation, a scope is manually created for each invocation.
|
||||
|
var scope = _provider.CreateScope(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
// Note: this background task is responsible of automatically removing orphaned tokens/authorizations
|
||||
|
// (i.e tokens that are no longer valid and ad-hoc authorizations that have no valid tokens associated).
|
||||
|
// Since ad-hoc authorizations and their associated tokens are removed as part of the same operation
|
||||
|
// when they no longer have any token attached, it's more efficient to remove the authorizations first.
|
||||
|
|
||||
|
// Note: the authorization/token managers MUST be resolved from the scoped provider
|
||||
|
// as they depend on scoped stores that should be disposed as soon as possible.
|
||||
|
|
||||
|
if (!_options.CurrentValue.DisableAuthorizationsPruning) |
||||
|
{ |
||||
|
var manager = scope.ServiceProvider.GetService<IOpenIddictAuthorizationManager>(); |
||||
|
if (manager == null) |
||||
|
{ |
||||
|
// Inform Quartz.NET that the triggers associated with this job should be removed,
|
||||
|
// as the future invocations will always fail until the application is correctly
|
||||
|
// re-configured to register the OpenIddict core services in the DI container.
|
||||
|
throw new JobExecutionException(new InvalidOperationException(SR.GetResourceString(SR.ID1277))) |
||||
|
{ |
||||
|
RefireImmediately = false, |
||||
|
UnscheduleAllTriggers = true, |
||||
|
UnscheduleFiringTrigger = true |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await manager.PruneAsync(context.CancellationToken); |
||||
|
} |
||||
|
|
||||
|
// OutOfMemoryExceptions are treated as fatal errors and are always re-thrown as-is.
|
||||
|
catch (OutOfMemoryException) |
||||
|
{ |
||||
|
throw; |
||||
|
} |
||||
|
|
||||
|
// OperationCanceledExceptions are typically thrown the host is about to shut down.
|
||||
|
// To allow the host to shut down as fast as possible, this exception type is special-cased
|
||||
|
// to prevent further processing in this job and inform Quartz.NET it shouldn't be refired.
|
||||
|
catch (OperationCanceledException exception) when (exception.CancellationToken == context.CancellationToken) |
||||
|
{ |
||||
|
throw new JobExecutionException(exception) |
||||
|
{ |
||||
|
RefireImmediately = false |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// AggregateExceptions are generally thrown by the manager itself when one or multiple exception(s)
|
||||
|
// occurred while trying to prune the entities. In this case, add the inner exceptions to the collection.
|
||||
|
catch (AggregateException exception) |
||||
|
{ |
||||
|
exceptions ??= new List<Exception>(capacity: 1); |
||||
|
exceptions.AddRange(exception.InnerExceptions); |
||||
|
} |
||||
|
|
||||
|
// Other exceptions are assumed to be transient and are added to the exceptions collection
|
||||
|
// to be re-thrown later (typically, at the very end of this job, as an AggregateException).
|
||||
|
catch (Exception exception) |
||||
|
{ |
||||
|
exceptions ??= new List<Exception>(capacity: 1); |
||||
|
exceptions.Add(exception); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!_options.CurrentValue.DisableTokensPruning) |
||||
|
{ |
||||
|
var manager = scope.ServiceProvider.GetService<IOpenIddictTokenManager>(); |
||||
|
if (manager == null) |
||||
|
{ |
||||
|
// Inform Quartz.NET that the triggers associated with this job should be removed,
|
||||
|
// as the future invocations will always fail until the application is correctly
|
||||
|
// re-configured to register the OpenIddict core services in the DI container.
|
||||
|
throw new JobExecutionException(new InvalidOperationException(SR.GetResourceString(SR.ID1277))) |
||||
|
{ |
||||
|
RefireImmediately = false, |
||||
|
UnscheduleAllTriggers = true, |
||||
|
UnscheduleFiringTrigger = true |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await manager.PruneAsync(context.CancellationToken); |
||||
|
} |
||||
|
|
||||
|
// OutOfMemoryExceptions are treated as fatal errors and are always re-thrown as-is.
|
||||
|
catch (OutOfMemoryException) |
||||
|
{ |
||||
|
throw; |
||||
|
} |
||||
|
|
||||
|
// OperationCanceledExceptions are typically thrown the host is about to shut down.
|
||||
|
// To allow the host to shut down as fast as possible, this exception type is special-cased
|
||||
|
// to prevent further processing in this job and inform Quartz.NET it shouldn't be refired.
|
||||
|
catch (OperationCanceledException exception) when (exception.CancellationToken == context.CancellationToken) |
||||
|
{ |
||||
|
throw new JobExecutionException(exception) |
||||
|
{ |
||||
|
RefireImmediately = false |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// AggregateExceptions are generally thrown by the manager itself when one or multiple exception(s)
|
||||
|
// occurred while trying to prune the entities. In this case, add the inner exceptions to the collection.
|
||||
|
catch (AggregateException exception) |
||||
|
{ |
||||
|
exceptions ??= new List<Exception>(capacity: 1); |
||||
|
exceptions.AddRange(exception.InnerExceptions); |
||||
|
} |
||||
|
|
||||
|
// Other exceptions are assumed to be transient and are added to the exceptions collection
|
||||
|
// to be re-thrown later (typically, at the very end of this job, as an AggregateException).
|
||||
|
catch (Exception exception) |
||||
|
{ |
||||
|
exceptions ??= new List<Exception>(capacity: 1); |
||||
|
exceptions.Add(exception); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (exceptions != null) |
||||
|
{ |
||||
|
throw new JobExecutionException(new AggregateException(exceptions)) |
||||
|
{ |
||||
|
// Only refire the job if the maximum refire count set in the options wasn't reached.
|
||||
|
RefireImmediately = context.RefireCount < _options.CurrentValue.MaximumRefireCount |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
finally |
||||
|
{ |
||||
|
if (scope is IAsyncDisposable disposable) |
||||
|
{ |
||||
|
await disposable.DisposeAsync(); |
||||
|
} |
||||
|
|
||||
|
else |
||||
|
{ |
||||
|
scope.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
namespace OpenIddict.Server.Quartz |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides various settings needed to configure the OpenIddict Quartz.NET server integration.
|
||||
|
/// </summary>
|
||||
|
public class OpenIddictServerQuartzOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets or sets a boolean indicating whether authorizations pruning should be disabled.
|
||||
|
/// </summary>
|
||||
|
public bool DisableAuthorizationsPruning { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a boolean indicating whether tokens pruning should be disabled.
|
||||
|
/// </summary>
|
||||
|
public bool DisableTokensPruning { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the number of times a failed Quartz.NET job can be retried.
|
||||
|
/// By default, failed jobs are automatically retried twice after the initial failure.
|
||||
|
/// </summary>
|
||||
|
public int MaximumRefireCount { get; set; } = 2; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFrameworks>net461;netcoreapp2.1;netcoreapp3.1</TargetFrameworks> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
<Nullable>enable</Nullable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Moq" /> |
||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\OpenIddict.Server.Quartz\OpenIddict.Server.Quartz.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,127 @@ |
|||||
|
using System; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Xunit; |
||||
|
using SR = OpenIddict.Abstractions.OpenIddictResources; |
||||
|
|
||||
|
namespace OpenIddict.Server.Quartz.Tests |
||||
|
{ |
||||
|
public class OpenIddictServerQuartzBuilderTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Constructor_ThrowsAnExceptionForNullServices() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = (IServiceCollection) null!; |
||||
|
|
||||
|
// Act and assert
|
||||
|
var exception = Assert.Throws<ArgumentNullException>(() => new OpenIddictServerQuartzBuilder(services)); |
||||
|
|
||||
|
Assert.Equal("services", exception.ParamName); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Configure_DelegateIsCorrectlyRegistered() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = CreateServices(); |
||||
|
var builder = CreateBuilder(services); |
||||
|
var configuration = new Action<OpenIddictServerQuartzOptions>(options => { }); |
||||
|
|
||||
|
// Act
|
||||
|
builder.Configure(configuration); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Contains(services, service => service.ServiceType == typeof(IConfigureOptions<OpenIddictServerQuartzOptions>) && |
||||
|
service.ImplementationInstance is ConfigureNamedOptions<OpenIddictServerQuartzOptions> options && |
||||
|
options.Action == configuration && string.IsNullOrEmpty(options.Name)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Configure_ThrowsAnExceptionWhenConfigurationIsNull() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = CreateServices(); |
||||
|
var builder = CreateBuilder(services); |
||||
|
|
||||
|
// Act and assert
|
||||
|
var exception = Assert.Throws<ArgumentNullException>(() => builder.Configure(configuration: null!)); |
||||
|
Assert.Equal("configuration", exception.ParamName); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void DisableAuthorizationsPruning_AuthorizationsPruningIsDisabled() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = CreateServices(); |
||||
|
var builder = CreateBuilder(services); |
||||
|
|
||||
|
// Act
|
||||
|
builder.DisableAuthorizationsPruning(); |
||||
|
|
||||
|
var options = GetOptions(services); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.True(options.DisableAuthorizationsPruning); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void DisableTokensPruning_TokensPruningIsDisabled() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = CreateServices(); |
||||
|
var builder = CreateBuilder(services); |
||||
|
|
||||
|
// Act
|
||||
|
builder.DisableTokensPruning(); |
||||
|
|
||||
|
var options = GetOptions(services); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.True(options.DisableTokensPruning); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void SetMaximumRefireCount_ThrowsAnExceptionForNegativeCount() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = CreateServices(); |
||||
|
var builder = CreateBuilder(services); |
||||
|
|
||||
|
// Act and assert
|
||||
|
var exception = Assert.Throws<ArgumentOutOfRangeException>(() => builder.SetMaximumRefireCount(-1)); |
||||
|
|
||||
|
Assert.Equal("count", exception.ParamName); |
||||
|
Assert.StartsWith(SR.GetResourceString(SR.ID1278), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void SetMaximumRefireCount_MaximumRefireCountIsSet() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = CreateServices(); |
||||
|
var builder = CreateBuilder(services); |
||||
|
|
||||
|
// Act
|
||||
|
builder.SetMaximumRefireCount(42); |
||||
|
|
||||
|
var options = GetOptions(services); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Equal(42, options.MaximumRefireCount); |
||||
|
} |
||||
|
|
||||
|
private static IServiceCollection CreateServices() |
||||
|
=> new ServiceCollection().AddOptions(); |
||||
|
|
||||
|
private static OpenIddictServerQuartzBuilder CreateBuilder(IServiceCollection services) |
||||
|
=> new OpenIddictServerQuartzBuilder(services); |
||||
|
|
||||
|
private static OpenIddictServerQuartzOptions GetOptions(IServiceCollection services) |
||||
|
{ |
||||
|
var provider = services.BuildServiceProvider(); |
||||
|
var options = provider.GetRequiredService<IOptions<OpenIddictServerQuartzOptions>>(); |
||||
|
return options.Value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,105 @@ |
|||||
|
using System; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Quartz; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace OpenIddict.Server.Quartz.Tests |
||||
|
{ |
||||
|
public class OpenIddictServerQuartzExtensionsTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void UseQuartz_ThrowsAnExceptionForNullBuilder() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var builder = (OpenIddictServerBuilder) null!; |
||||
|
|
||||
|
// Act and assert
|
||||
|
var exception = Assert.Throws<ArgumentNullException>(() => builder.UseQuartz()); |
||||
|
|
||||
|
Assert.Equal("builder", exception.ParamName); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void UseQuartz_ThrowsAnExceptionForNullConfiguration() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = new ServiceCollection(); |
||||
|
var builder = new OpenIddictServerBuilder(services); |
||||
|
|
||||
|
// Act and assert
|
||||
|
var exception = Assert.Throws<ArgumentNullException>(() => builder.UseQuartz(configuration: null!)); |
||||
|
|
||||
|
Assert.Equal("configuration", exception.ParamName); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void UseQuartz_RegistersJobService() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = new ServiceCollection(); |
||||
|
var builder = new OpenIddictServerBuilder(services); |
||||
|
|
||||
|
// Act
|
||||
|
builder.UseQuartz(); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Contains(services, service => service.ServiceType == typeof(OpenIddictServerQuartzJob) && |
||||
|
service.ImplementationType == typeof(OpenIddictServerQuartzJob) && |
||||
|
service.Lifetime == ServiceLifetime.Transient); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void UseQuartz_RegistersJobDetails() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = new ServiceCollection(); |
||||
|
var builder = new OpenIddictServerBuilder(services); |
||||
|
|
||||
|
// Act
|
||||
|
builder.UseQuartz(); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Contains(services, service => service.ServiceType == typeof(IJobDetail) && |
||||
|
service.ImplementationInstance is IJobDetail job && |
||||
|
job.Key.Equals(OpenIddictServerQuartzJob.Identity)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void UseQuartz_RegistersTriggerDetails() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = new ServiceCollection(); |
||||
|
var builder = new OpenIddictServerBuilder(services); |
||||
|
|
||||
|
// Act
|
||||
|
builder.UseQuartz(); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Contains(services, service => service.ServiceType == typeof(ITrigger) && |
||||
|
service.ImplementationInstance is ITrigger trigger && |
||||
|
trigger.JobKey.Equals(OpenIddictServerQuartzJob.Identity)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void UseQuartz_CanBeSafelyInvokedMultipleTimes() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = new ServiceCollection(); |
||||
|
var builder = new OpenIddictServerBuilder(services); |
||||
|
|
||||
|
// Act
|
||||
|
builder.UseQuartz(); |
||||
|
builder.UseQuartz(); |
||||
|
builder.UseQuartz(); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Single(services, service => service.ServiceType == typeof(IJobDetail) && |
||||
|
service.ImplementationInstance is IJobDetail job && |
||||
|
job.Key.Equals(OpenIddictServerQuartzJob.Identity)); |
||||
|
|
||||
|
Assert.Single(services, service => service.ServiceType == typeof(ITrigger) && |
||||
|
service.ImplementationInstance is ITrigger trigger && |
||||
|
trigger.JobKey.Equals(OpenIddictServerQuartzJob.Identity)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,349 @@ |
|||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Moq; |
||||
|
using OpenIddict.Abstractions; |
||||
|
using Quartz; |
||||
|
using Xunit; |
||||
|
using SR = OpenIddict.Abstractions.OpenIddictResources; |
||||
|
|
||||
|
namespace OpenIddict.Server.Quartz.Tests |
||||
|
{ |
||||
|
public class OpenIddictServerQuartzJobTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Constructor_ThrowsAnException() |
||||
|
{ |
||||
|
// Arrange, act and assert
|
||||
|
var exception = Assert.Throws<InvalidOperationException>(() => new OpenIddictServerQuartzJob()); |
||||
|
|
||||
|
Assert.Equal(SR.GetResourceString(SR.ID1081), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Execute_UsesServiceScope() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var provider = Mock.Of<IServiceProvider>(provider => |
||||
|
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() && |
||||
|
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>()); |
||||
|
|
||||
|
var scope = Mock.Of<IServiceScope>(scope => scope.ServiceProvider == provider); |
||||
|
var factory = Mock.Of<IServiceScopeFactory>(factory => factory.CreateScope() == scope); |
||||
|
var monitor = Mock.Of<IOptionsMonitor<OpenIddictServerQuartzOptions>>( |
||||
|
monitor => monitor.CurrentValue == new OpenIddictServerQuartzOptions()); |
||||
|
|
||||
|
var job = new OpenIddictServerQuartzJob(monitor, |
||||
|
Mock.Of<IServiceProvider>(provider => provider.GetService(typeof(IServiceScopeFactory)) == factory)); |
||||
|
|
||||
|
// Act
|
||||
|
await job.Execute(Mock.Of<IJobExecutionContext>()); |
||||
|
|
||||
|
Mock.Get(factory).Verify(factory => factory.CreateScope(), Times.Once()); |
||||
|
Mock.Get(scope).Verify(scope => scope.Dispose(), Times.Once()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Execute_IgnoresPruningWhenAuthorizationsPruningIsDisabled() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
|
||||
|
var manager = new Mock<IOpenIddictAuthorizationManager>(); |
||||
|
|
||||
|
var provider = Mock.Of<IServiceProvider>(provider => |
||||
|
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object && |
||||
|
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>()); |
||||
|
|
||||
|
var job = CreateJob(provider, new OpenIddictServerQuartzOptions |
||||
|
{ |
||||
|
DisableAuthorizationsPruning = true |
||||
|
}); |
||||
|
|
||||
|
// Act
|
||||
|
await job.Execute(Mock.Of<IJobExecutionContext>()); |
||||
|
|
||||
|
// Assert
|
||||
|
manager.Verify(manager => manager.PruneAsync(It.IsAny<CancellationToken>()), Times.Never()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Execute_IgnoresPruningWhenTokensPruningIsDisabled() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
|
||||
|
var manager = new Mock<IOpenIddictAuthorizationManager>(); |
||||
|
|
||||
|
var provider = Mock.Of<IServiceProvider>(provider => |
||||
|
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() && |
||||
|
provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object); |
||||
|
|
||||
|
var job = CreateJob(provider, new OpenIddictServerQuartzOptions |
||||
|
{ |
||||
|
DisableTokensPruning = true |
||||
|
}); |
||||
|
|
||||
|
// Act
|
||||
|
await job.Execute(Mock.Of<IJobExecutionContext>()); |
||||
|
|
||||
|
// Assert
|
||||
|
manager.Verify(manager => manager.PruneAsync(It.IsAny<CancellationToken>()), Times.Never()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Execute_UnschedulesTriggersWhenAuthorizationManagerIsMissing() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var provider = Mock.Of<IServiceProvider>(provider => |
||||
|
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == null); |
||||
|
|
||||
|
var job = CreateJob(provider); |
||||
|
|
||||
|
// Act and assert
|
||||
|
var exception = await Assert.ThrowsAsync<JobExecutionException>(() => job.Execute(Mock.Of<IJobExecutionContext>())); |
||||
|
|
||||
|
Assert.False(exception.RefireImmediately); |
||||
|
Assert.True(exception.UnscheduleAllTriggers); |
||||
|
Assert.True(exception.UnscheduleFiringTrigger); |
||||
|
|
||||
|
Assert.IsType<InvalidOperationException>(exception.InnerException); |
||||
|
Assert.Equal(SR.GetResourceString(SR.ID1277), exception.InnerException!.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Execute_UnschedulesTriggersWhenTokenManagerIsMissing() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var provider = Mock.Of<IServiceProvider>(provider => |
||||
|
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() && |
||||
|
provider.GetService(typeof(IOpenIddictTokenManager)) == null); |
||||
|
|
||||
|
var job = CreateJob(provider); |
||||
|
|
||||
|
// Act and assert
|
||||
|
var exception = await Assert.ThrowsAsync<JobExecutionException>(() => job.Execute(Mock.Of<IJobExecutionContext>())); |
||||
|
|
||||
|
Assert.False(exception.RefireImmediately); |
||||
|
Assert.True(exception.UnscheduleAllTriggers); |
||||
|
Assert.True(exception.UnscheduleFiringTrigger); |
||||
|
|
||||
|
Assert.IsType<InvalidOperationException>(exception.InnerException); |
||||
|
Assert.Equal(SR.GetResourceString(SR.ID1277), exception.InnerException!.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Execute_RethrowsOutOfMemoryExceptionsThrownDuringAuthorizationsPruning() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var manager = new Mock<IOpenIddictAuthorizationManager>(); |
||||
|
manager.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>())) |
||||
|
.Throws(new OutOfMemoryException()); |
||||
|
|
||||
|
var provider = Mock.Of<IServiceProvider>(provider => |
||||
|
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object && |
||||
|
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>()); |
||||
|
|
||||
|
var job = CreateJob(provider); |
||||
|
|
||||
|
// Act and assert
|
||||
|
await Assert.ThrowsAsync<OutOfMemoryException>(() => job.Execute(Mock.Of<IJobExecutionContext>())); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Execute_RethrowsOutOfMemoryExceptionsThrownDuringTokensPruning() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var manager = new Mock<IOpenIddictTokenManager>(); |
||||
|
manager.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>())) |
||||
|
.Throws(new OutOfMemoryException()); |
||||
|
|
||||
|
var provider = Mock.Of<IServiceProvider>(provider => |
||||
|
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() && |
||||
|
provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object); |
||||
|
|
||||
|
var job = CreateJob(provider); |
||||
|
|
||||
|
// Act and assert
|
||||
|
await Assert.ThrowsAsync<OutOfMemoryException>(() => job.Execute(Mock.Of<IJobExecutionContext>())); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Execute_DisablesRefiringWhenJobIsCanceledDuringAuthorizationsPruning() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var token = new CancellationToken(canceled: true); |
||||
|
|
||||
|
var manager = new Mock<IOpenIddictAuthorizationManager>(); |
||||
|
manager.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>())) |
||||
|
.Throws(new OperationCanceledException(token)); |
||||
|
|
||||
|
var provider = Mock.Of<IServiceProvider>(provider => |
||||
|
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object && |
||||
|
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>()); |
||||
|
|
||||
|
var context = Mock.Of<IJobExecutionContext>(context => context.CancellationToken == token); |
||||
|
|
||||
|
var job = CreateJob(provider); |
||||
|
|
||||
|
// Act and assert
|
||||
|
var exception = await Assert.ThrowsAsync<JobExecutionException>(() => job.Execute(context)); |
||||
|
|
||||
|
Assert.False(exception.RefireImmediately); |
||||
|
|
||||
|
manager.Verify(manager => manager.PruneAsync(It.IsAny<CancellationToken>()), Times.Once()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Execute_DisablesRefiringWhenJobIsCanceledDuringTokensPruning() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var token = new CancellationToken(canceled: true); |
||||
|
|
||||
|
var manager = new Mock<IOpenIddictTokenManager>(); |
||||
|
manager.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>())) |
||||
|
.Throws(new OperationCanceledException(token)); |
||||
|
|
||||
|
var provider = Mock.Of<IServiceProvider>(provider => |
||||
|
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() && |
||||
|
provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object); |
||||
|
|
||||
|
var context = Mock.Of<IJobExecutionContext>(context => context.CancellationToken == token); |
||||
|
|
||||
|
var job = CreateJob(provider); |
||||
|
|
||||
|
// Act and assert
|
||||
|
var exception = await Assert.ThrowsAsync<JobExecutionException>(() => job.Execute(context)); |
||||
|
|
||||
|
Assert.False(exception.RefireImmediately); |
||||
|
|
||||
|
manager.Verify(manager => manager.PruneAsync(It.IsAny<CancellationToken>()), Times.Once()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Execute_AllowsRefiringWhenExceptionsAreThrown() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var provider = new Mock<IServiceProvider>(); |
||||
|
provider.Setup(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager))) |
||||
|
.Returns(CreateAuthorizationManager(new ApplicationException())); |
||||
|
|
||||
|
provider.Setup(provider => provider.GetService(typeof(IOpenIddictTokenManager))) |
||||
|
.Returns(CreateTokenManager(new ApplicationException())); |
||||
|
|
||||
|
var context = Mock.Of<IJobExecutionContext>(context => context.RefireCount == 0); |
||||
|
|
||||
|
var job = CreateJob(provider.Object); |
||||
|
|
||||
|
// Act and assert
|
||||
|
var exception = await Assert.ThrowsAsync<JobExecutionException>(() => job.Execute(context)); |
||||
|
|
||||
|
Assert.True(exception.RefireImmediately); |
||||
|
Assert.IsType<AggregateException>(exception.InnerException); |
||||
|
Assert.Equal(2, ((AggregateException) exception.InnerException!).InnerExceptions.Count); |
||||
|
Assert.IsType<ApplicationException>(((AggregateException) exception.InnerException!).InnerExceptions[0]); |
||||
|
Assert.IsType<ApplicationException>(((AggregateException) exception.InnerException!).InnerExceptions[1]); |
||||
|
|
||||
|
static IOpenIddictAuthorizationManager CreateAuthorizationManager(Exception exception) |
||||
|
{ |
||||
|
var mock = new Mock<IOpenIddictAuthorizationManager>(); |
||||
|
mock.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>())) |
||||
|
.Throws(exception); |
||||
|
|
||||
|
return mock.Object; |
||||
|
} |
||||
|
|
||||
|
static IOpenIddictTokenManager CreateTokenManager(Exception exception) |
||||
|
{ |
||||
|
var mock = new Mock<IOpenIddictTokenManager>(); |
||||
|
mock.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>())) |
||||
|
.Throws(exception); |
||||
|
|
||||
|
return mock.Object; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Execute_AllowsRefiringWhenAggregateExceptionsAreThrown() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var provider = new Mock<IServiceProvider>(); |
||||
|
provider.Setup(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager))) |
||||
|
.Returns(CreateAuthorizationManager(new AggregateException( |
||||
|
new InvalidOperationException(), new ApplicationException()))); |
||||
|
|
||||
|
provider.Setup(provider => provider.GetService(typeof(IOpenIddictTokenManager))) |
||||
|
.Returns(CreateTokenManager(new AggregateException( |
||||
|
new InvalidOperationException(), new ApplicationException()))); |
||||
|
|
||||
|
var context = Mock.Of<IJobExecutionContext>(context => context.RefireCount == 0); |
||||
|
|
||||
|
var job = CreateJob(provider.Object); |
||||
|
|
||||
|
// Act and assert
|
||||
|
var exception = await Assert.ThrowsAsync<JobExecutionException>(() => job.Execute(context)); |
||||
|
|
||||
|
Assert.True(exception.RefireImmediately); |
||||
|
Assert.IsType<AggregateException>(exception.InnerException); |
||||
|
Assert.Equal(4, ((AggregateException) exception.InnerException!).InnerExceptions.Count); |
||||
|
Assert.IsType<InvalidOperationException>(((AggregateException) exception.InnerException!).InnerExceptions[0]); |
||||
|
Assert.IsType<ApplicationException>(((AggregateException) exception.InnerException!).InnerExceptions[1]); |
||||
|
Assert.IsType<InvalidOperationException>(((AggregateException) exception.InnerException!).InnerExceptions[2]); |
||||
|
Assert.IsType<ApplicationException>(((AggregateException) exception.InnerException!).InnerExceptions[3]); |
||||
|
|
||||
|
static IOpenIddictAuthorizationManager CreateAuthorizationManager(Exception exception) |
||||
|
{ |
||||
|
var mock = new Mock<IOpenIddictAuthorizationManager>(); |
||||
|
mock.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>())) |
||||
|
.Throws(exception); |
||||
|
|
||||
|
return mock.Object; |
||||
|
} |
||||
|
|
||||
|
static IOpenIddictTokenManager CreateTokenManager(Exception exception) |
||||
|
{ |
||||
|
var mock = new Mock<IOpenIddictTokenManager>(); |
||||
|
mock.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>())) |
||||
|
.Throws(exception); |
||||
|
|
||||
|
return mock.Object; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Execute_DisallowsRefiringWhenMaximumRefireCountIsReached() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var manager = new Mock<IOpenIddictAuthorizationManager>(); |
||||
|
manager.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>())) |
||||
|
.Throws(new ApplicationException()); |
||||
|
|
||||
|
var provider = Mock.Of<IServiceProvider>(provider => |
||||
|
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object && |
||||
|
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>()); |
||||
|
|
||||
|
var context = Mock.Of<IJobExecutionContext>(context => context.RefireCount == 5); |
||||
|
|
||||
|
var job = CreateJob(provider, new OpenIddictServerQuartzOptions |
||||
|
{ |
||||
|
MaximumRefireCount = 5 |
||||
|
}); |
||||
|
|
||||
|
// Act and assert
|
||||
|
var exception = await Assert.ThrowsAsync<JobExecutionException>(() => job.Execute(context)); |
||||
|
|
||||
|
Assert.False(exception.RefireImmediately); |
||||
|
} |
||||
|
|
||||
|
private static OpenIddictServerQuartzJob CreateJob(IServiceProvider provider, OpenIddictServerQuartzOptions? options = null) |
||||
|
{ |
||||
|
var scope = Mock.Of<IServiceScope>(scope => scope.ServiceProvider == provider); |
||||
|
var factory = Mock.Of<IServiceScopeFactory>(factory => factory.CreateScope() == scope); |
||||
|
var monitor = Mock.Of<IOptionsMonitor<OpenIddictServerQuartzOptions>>( |
||||
|
monitor => monitor.CurrentValue == (options ?? new OpenIddictServerQuartzOptions())); |
||||
|
|
||||
|
return new OpenIddictServerQuartzJob(monitor, |
||||
|
Mock.Of<IServiceProvider>(provider => provider.GetService(typeof(IServiceScopeFactory)) == factory)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue