diff --git a/Directory.Build.props b/Directory.Build.props index 5b684373..fc086139 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -128,25 +128,21 @@ - net9.0-android; net10.0-android - net9.0-ios; net10.0-ios - net9.0-maccatalyst; net10.0-maccatalyst - net9.0-macos; net10.0-macos diff --git a/Directory.Packages.props b/Directory.Packages.props index 6fa6c30b..ed985d4c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -22,25 +22,26 @@ Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' And $([MSBuild]::VersionEquals($(TargetFrameworkVersion), '4.6.2')) "> - - - + + + - + - + - + + @@ -60,19 +61,19 @@ - - - + + + - + - + @@ -80,7 +81,8 @@ - + + @@ -92,8 +94,8 @@ --> - - + + @@ -111,19 +113,19 @@ - - - + + + - + - + @@ -131,7 +133,8 @@ - + + @@ -143,8 +146,8 @@ --> - - + + @@ -166,12 +169,12 @@ - - - - - - + + + + + + @@ -197,23 +200,24 @@ - - + + - + - + - + + @@ -222,7 +226,7 @@ --> - + @@ -239,23 +243,24 @@ - - - - - - + + + + + + - - - + + + - - + + + - - + + @@ -280,23 +285,23 @@ - - - - - - + + + + + + - - - + + + - - + + - - + + - + @@ -380,23 +385,24 @@ - + - + - + - + + diff --git a/WorkloadRollback.json b/WorkloadRollback.json index 040477c7..d99071ba 100644 --- a/WorkloadRollback.json +++ b/WorkloadRollback.json @@ -1,18 +1,18 @@ { - "microsoft.net.sdk.android": "36.1.43/10.0.100", - "microsoft.net.sdk.ios": "26.5.10284/10.0.100", - "microsoft.net.sdk.maccatalyst": "26.5.10284/10.0.100", - "microsoft.net.sdk.macos": "26.5.10284/10.0.100", + "microsoft.net.sdk.android": "36.1.69/10.0.100", + "microsoft.net.sdk.ios": "26.5.10301/10.0.100", + "microsoft.net.sdk.maccatalyst": "26.5.10301/10.0.100", + "microsoft.net.sdk.macos": "26.5.10301/10.0.100", "microsoft.net.sdk.maui": "10.0.20/10.0.100", - "microsoft.net.sdk.tvos": "26.5.10284/10.0.100", - "microsoft.net.workload.mono.toolchain.current": "10.0.110/10.0.100", - "microsoft.net.workload.emscripten.current": "10.0.110/10.0.100", - "microsoft.net.workload.emscripten.net6": "10.0.110/10.0.100", - "microsoft.net.workload.emscripten.net7": "10.0.110/10.0.100", - "microsoft.net.workload.emscripten.net8": "10.0.110/10.0.100", - "microsoft.net.workload.emscripten.net9": "10.0.110/10.0.100", - "microsoft.net.workload.mono.toolchain.net6": "10.0.110/10.0.100", - "microsoft.net.workload.mono.toolchain.net7": "10.0.110/10.0.100", - "microsoft.net.workload.mono.toolchain.net8": "10.0.110/10.0.100", - "microsoft.net.workload.mono.toolchain.net9": "10.0.110/10.0.100" + "microsoft.net.sdk.tvos": "26.5.10301/10.0.100", + "microsoft.net.workload.mono.toolchain.current": "10.0.111/10.0.100", + "microsoft.net.workload.emscripten.current": "10.0.111/10.0.100", + "microsoft.net.workload.emscripten.net6": "10.0.111/10.0.100", + "microsoft.net.workload.emscripten.net7": "10.0.111/10.0.100", + "microsoft.net.workload.emscripten.net8": "10.0.111/10.0.100", + "microsoft.net.workload.emscripten.net9": "10.0.111/10.0.100", + "microsoft.net.workload.mono.toolchain.net6": "10.0.111/10.0.100", + "microsoft.net.workload.mono.toolchain.net7": "10.0.111/10.0.100", + "microsoft.net.workload.mono.toolchain.net8": "10.0.111/10.0.100", + "microsoft.net.workload.mono.toolchain.net9": "10.0.111/10.0.100" } diff --git a/global.json b/global.json index 3b244ade..5c048ec0 100644 --- a/global.json +++ b/global.json @@ -1,17 +1,17 @@ { "sdk": { - "version": "10.0.302", + "version": "10.0.400", "allowPrerelease": true, "rollForward": "major" }, "tools": { - "dotnet": "10.0.302", + "dotnet": "10.0.400", "runtimes": { "aspnetcore": [ - "8.0.29", - "9.0.18" + "8.0.30", + "9.0.19" ] } }, diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/OpenIddict.Sandbox.AspNet.Client.csproj b/sandbox/OpenIddict.Sandbox.AspNet.Client/OpenIddict.Sandbox.AspNet.Client.csproj index 63106125..f39d2e2c 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Client/OpenIddict.Sandbox.AspNet.Client.csproj +++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/OpenIddict.Sandbox.AspNet.Client.csproj @@ -15,7 +15,6 @@ - diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/OpenIddict.Sandbox.AspNetCore.Client.csproj b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/OpenIddict.Sandbox.AspNetCore.Client.csproj index f871dffe..49edf614 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/OpenIddict.Sandbox.AspNetCore.Client.csproj +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/OpenIddict.Sandbox.AspNetCore.Client.csproj @@ -12,7 +12,6 @@ - @@ -26,6 +25,7 @@ + diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/OpenIddict.Sandbox.AspNetCore.Server.csproj b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/OpenIddict.Sandbox.AspNetCore.Server.csproj index b7311c4e..86138b1d 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/OpenIddict.Sandbox.AspNetCore.Server.csproj +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/OpenIddict.Sandbox.AspNetCore.Server.csproj @@ -15,7 +15,6 @@ - @@ -29,6 +28,7 @@ + diff --git a/src/OpenIddict.Quartz/OpenIddict.Quartz.csproj b/src/OpenIddict.Quartz/OpenIddict.Quartz.csproj index b3293c82..7e76d00c 100644 --- a/src/OpenIddict.Quartz/OpenIddict.Quartz.csproj +++ b/src/OpenIddict.Quartz/OpenIddict.Quartz.csproj @@ -18,6 +18,13 @@ + + + + diff --git a/src/OpenIddict.Quartz/OpenIddictQuartzConfiguration.cs b/src/OpenIddict.Quartz/OpenIddictQuartzConfiguration.cs index 94966814..0cfd6fb2 100644 --- a/src/OpenIddict.Quartz/OpenIddictQuartzConfiguration.cs +++ b/src/OpenIddict.Quartz/OpenIddictQuartzConfiguration.cs @@ -14,7 +14,7 @@ namespace OpenIddict.Quartz; /// Contains the methods required to ensure that the OpenIddict Quartz.NET configuration is valid. /// [EditorBrowsable(EditorBrowsableState.Advanced)] -public sealed class OpenIddictQuartzConfiguration : IConfigureOptions, IPostConfigureOptions +public sealed class OpenIddictQuartzConfiguration : IPostConfigureOptions { private readonly IServiceProvider _provider; @@ -25,31 +25,6 @@ public sealed class OpenIddictQuartzConfiguration : IConfigureOptions _provider = provider ?? throw new ArgumentNullException(nameof(provider)); - /// - public void Configure(QuartzOptions options) - { - ArgumentNullException.ThrowIfNull(options); - - options.AddJob(builder => - { - builder.StoreDurably() - .WithIdentity(OpenIddictQuartzJob.Identity) - .WithDescription(SR.GetResourceString(SR.ID8001)); - }); - - options.AddTrigger(builder => - { - // 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 - // between 1 and 10 minutes after the application starts to ensure the job is executed at least once. - builder.ForJob(OpenIddictQuartzJob.Identity) - .WithIdentity(SR.GetResourceString(SR.ID8004), SR.GetResourceString(SR.ID8005)) - .WithSimpleSchedule(options => options.WithIntervalInHours(1).RepeatForever()) - .WithDescription(SR.GetResourceString(SR.ID8002)) - .StartAt(DateBuilder.FutureDate(new Random().Next(1, 10), IntervalUnit.Minute)); - }); - } - /// public void PostConfigure(string? name, OpenIddictQuartzOptions options) { diff --git a/src/OpenIddict.Quartz/OpenIddictQuartzExtensions.cs b/src/OpenIddict.Quartz/OpenIddictQuartzExtensions.cs index 628cd50b..83bb59e9 100644 --- a/src/OpenIddict.Quartz/OpenIddictQuartzExtensions.cs +++ b/src/OpenIddict.Quartz/OpenIddictQuartzExtensions.cs @@ -25,16 +25,38 @@ public static class OpenIddictQuartzExtensions { ArgumentNullException.ThrowIfNull(builder); - builder.Services.AddQuartz(); + // Note: the AddQuartz() method MUST only be called once to avoid adding multiple jobs and triggers. + if (!builder.Services.Any(static descriptor => descriptor.ServiceType == typeof(OpenIddictQuartzJob))) + { + builder.Services.AddQuartz(options => + { + options.AddJob(static builder => + { + builder.StoreDurably() + .WithIdentity(OpenIddictQuartzJob.Identity) + .WithDescription(SR.GetResourceString(SR.ID8001)); + }); - // The OpenIddict job is registered as a service to allow - // Quartz.NET's DI integration to resolve it from the DI. - builder.Services.TryAddTransient(); + options.AddTrigger(static builder => + { + // 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 + // between 1 and 10 minutes after the application starts to ensure the job is executed at least once. + builder.ForJob(OpenIddictQuartzJob.Identity) + .WithIdentity(SR.GetResourceString(SR.ID8004), SR.GetResourceString(SR.ID8005)) + .WithSimpleSchedule(options => options.WithInterval(TimeSpan.FromHours(1)).RepeatForever()) + .WithDescription(SR.GetResourceString(SR.ID8002)) + .StartAt(TimeProvider.System.GetUtcNow() + TimeSpan.FromMinutes(new Random().Next(1, 10))); + }); + }); + } - // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once. - builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton< - IConfigureOptions, OpenIddictQuartzConfiguration>()); +#if !NET10_0_OR_GREATER + // Note: unlike Quartz.NET 4.0+, Quartz.NET 3.x doesn't automatically register the job as a service. + builder.Services.TryAddTransient(); +#endif + // Note: TryAddEnumerable() is used here to ensure the initializer is registered only once. builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton< IPostConfigureOptions, OpenIddictQuartzConfiguration>()); diff --git a/src/OpenIddict.Quartz/OpenIddictQuartzJob.cs b/src/OpenIddict.Quartz/OpenIddictQuartzJob.cs index 98f69c6c..83ee8b6a 100644 --- a/src/OpenIddict.Quartz/OpenIddictQuartzJob.cs +++ b/src/OpenIddict.Quartz/OpenIddictQuartzJob.cs @@ -16,24 +16,21 @@ namespace OpenIddict.Quartz; [DisallowConcurrentExecution, EditorBrowsable(EditorBrowsableState.Advanced)] public sealed class OpenIddictQuartzJob : IJob { - private readonly IOptionsMonitor _options; private readonly IServiceProvider _provider; +#if !NET10_0_OR_GREATER /// /// Creates a new instance of the class. /// public OpenIddictQuartzJob() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0082)); +#endif /// /// Creates a new instance of the class. /// - /// The OpenIddict Quartz.NET options. /// The service provider. - public OpenIddictQuartzJob(IOptionsMonitor options, IServiceProvider provider) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - _provider = provider ?? throw new ArgumentNullException(nameof(provider)); - } + public OpenIddictQuartzJob(IServiceProvider provider) + => _provider = provider ?? throw new ArgumentNullException(nameof(provider)); /// /// Gets the default identity assigned to this job. @@ -43,20 +40,24 @@ public sealed class OpenIddictQuartzJob : IJob group: SR.GetResourceString(SR.ID8005)); /// +#if NET10_0_OR_GREATER + public async ValueTask Execute(IJobExecutionContext context, CancellationToken cancellationToken = default) +#else public async Task Execute(IJobExecutionContext context) +#endif { ArgumentNullException.ThrowIfNull(context); List? 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. await using var scope = _provider.CreateAsyncScope(); + var options = scope.ServiceProvider.GetRequiredService>().CurrentValue; + // Important: since authorizations that still have tokens attached are never // pruned, the tokens MUST be deleted before deleting the authorizations. - if (!_options.CurrentValue.DisableTokenPruning) + if (!options.DisableTokenPruning) { var manager = scope.ServiceProvider.GetService() ?? throw new JobExecutionException(new InvalidOperationException(SR.GetResourceString(SR.ID0278))) @@ -66,7 +67,7 @@ public sealed class OpenIddictQuartzJob : IJob UnscheduleFiringTrigger = true }; - var threshold = _options.CurrentValue.TimeProvider.GetUtcNow() - _options.CurrentValue.MinimumTokenLifespan; + var threshold = options.TimeProvider.GetUtcNow() - options.MinimumTokenLifespan; try { @@ -88,7 +89,7 @@ public sealed class OpenIddictQuartzJob : IJob // occurred while trying to prune the entities. In this case, add the inner exceptions to the collection. catch (AggregateException exception) when (!OpenIddictHelpers.IsFatal(exception)) { - exceptions ??= []; + exceptions ??= new List(capacity: exception.InnerExceptions.Count); exceptions.AddRange(exception.InnerExceptions); } @@ -96,12 +97,12 @@ public sealed class OpenIddictQuartzJob : IJob // to be re-thrown later (typically, at the very end of this job, as an AggregateException). catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { - exceptions ??= []; + exceptions ??= new List(capacity: 1); exceptions.Add(exception); } } - if (!_options.CurrentValue.DisableAuthorizationPruning) + if (!options.DisableAuthorizationPruning) { var manager = scope.ServiceProvider.GetService() ?? throw new JobExecutionException(new InvalidOperationException(SR.GetResourceString(SR.ID0278))) @@ -111,7 +112,7 @@ public sealed class OpenIddictQuartzJob : IJob UnscheduleFiringTrigger = true }; - var threshold = _options.CurrentValue.TimeProvider.GetUtcNow() - _options.CurrentValue.MinimumAuthorizationLifespan; + var threshold = options.TimeProvider.GetUtcNow() - options.MinimumAuthorizationLifespan; try { @@ -133,7 +134,7 @@ public sealed class OpenIddictQuartzJob : IJob // occurred while trying to prune the entities. In this case, add the inner exceptions to the collection. catch (AggregateException exception) when (!OpenIddictHelpers.IsFatal(exception)) { - exceptions ??= []; + exceptions ??= new List(capacity: exception.InnerExceptions.Count); exceptions.AddRange(exception.InnerExceptions); } @@ -141,17 +142,17 @@ public sealed class OpenIddictQuartzJob : IJob // to be re-thrown later (typically, at the very end of this job, as an AggregateException). catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { - exceptions ??= []; + exceptions ??= new List(capacity: 1); exceptions.Add(exception); } } - if (exceptions is not null) + if (exceptions is { Count: > 0 }) { 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 + RefireImmediately = context.RefireCount < options.MaximumRefireCount }; } } diff --git a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzConfigurationTests.cs b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzConfigurationTests.cs deleted file mode 100644 index 9f654c4c..00000000 --- a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzConfigurationTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Quartz; -using Xunit; - -namespace OpenIddict.Quartz.Tests; - -public class OpenIddictQuartzConfigurationTests -{ - [Fact] - public void UseQuartz_RegistersJobDetails() - { - // Arrange - var options = new QuartzOptions(); - var configuration = new OpenIddictQuartzConfiguration(new ServiceCollection().BuildServiceProvider()); - - // Act - configuration.Configure(options); - - // Assert - Assert.Single(options.JobDetails); - Assert.Equal(OpenIddictQuartzJob.Identity, options.JobDetails[0].Key); - Assert.Equal(SR.GetResourceString(SR.ID8003), options.JobDetails[0].Key.Name); - Assert.Equal(SR.GetResourceString(SR.ID8005), options.JobDetails[0].Key.Group); - Assert.Equal(SR.GetResourceString(SR.ID8001), options.JobDetails[0].Description); - } - - [Fact] - public void UseQuartz_RegistersTriggerDetails() - { - // Arrange - var options = new QuartzOptions(); - var configuration = new OpenIddictQuartzConfiguration(new ServiceCollection().BuildServiceProvider()); - - // Act - configuration.Configure(options); - - // Assert - Assert.Single(options.Triggers); - Assert.Equal(OpenIddictQuartzJob.Identity, options.Triggers[0].JobKey); - Assert.Equal(SR.GetResourceString(SR.ID8004), options.Triggers[0].Key.Name); - Assert.Equal(SR.GetResourceString(SR.ID8005), options.Triggers[0].Key.Group); - Assert.Equal(SR.GetResourceString(SR.ID8002), options.Triggers[0].Description); - } -} diff --git a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzExtensionsTests.cs b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzExtensionsTests.cs index d72a54b3..41ba0681 100644 --- a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzExtensionsTests.cs +++ b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzExtensionsTests.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Quartz; using Xunit; namespace OpenIddict.Quartz.Tests; @@ -43,9 +42,15 @@ public class OpenIddictQuartzExtensionsTests builder.UseQuartz(); // Assert +#if NET10_0_OR_GREATER Assert.Contains(services, service => service.ServiceType == typeof(OpenIddictQuartzJob) && service.ImplementationType == typeof(OpenIddictQuartzJob) && - service.Lifetime == ServiceLifetime.Transient); + service.Lifetime is ServiceLifetime.Scoped); +#else + Assert.Contains(services, service => service.ServiceType == typeof(OpenIddictQuartzJob) && + service.ImplementationType == typeof(OpenIddictQuartzJob) && + service.Lifetime is ServiceLifetime.Transient); +#endif } [Fact] @@ -62,11 +67,9 @@ public class OpenIddictQuartzExtensionsTests // Assert Assert.Single(services, service => service.ServiceType == typeof(OpenIddictQuartzJob) && - service.ImplementationType == typeof(OpenIddictQuartzJob) && - service.Lifetime == ServiceLifetime.Transient); + service.ImplementationType == typeof(OpenIddictQuartzJob)); - Assert.Single(services, service => service.ServiceType == typeof(IConfigureOptions) && - service.ImplementationType == typeof(OpenIddictQuartzConfiguration) && - service.Lifetime == ServiceLifetime.Singleton); + Assert.Single(services, service => service.ServiceType == typeof(IPostConfigureOptions) && + service.ImplementationType == typeof(OpenIddictQuartzConfiguration)); } } diff --git a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzJobTests.cs b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzJobTests.cs index 05798bdf..d531d8e7 100644 --- a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzJobTests.cs +++ b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzJobTests.cs @@ -8,33 +8,36 @@ namespace OpenIddict.Quartz.Tests; public class OpenIddictQuartzJobTests { +#if !NET10_0_OR_GREATER [Fact] - public void Constructor_ThrowsAnException() + public void Constructor_ThrowsAnExceptionWhenServiceProviderCannotBeResolved() { // Arrange, act and assert var exception = Assert.Throws(() => new OpenIddictQuartzJob()); Assert.Equal(SR.GetResourceString(SR.ID0082), exception.Message); } +#endif [Fact] public async Task Execute_UsesServiceScope() { // Arrange - var provider = Mock.Of(provider => - provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of() && - provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of()); - - var scope = Mock.Of(scope => scope.ServiceProvider == provider); - var factory = Mock.Of(factory => factory.CreateScope() == scope); var monitor = Mock.Of>( monitor => monitor.CurrentValue == new OpenIddictQuartzOptions { TimeProvider = TimeProvider.System }); - var job = new OpenIddictQuartzJob(monitor, - Mock.Of(provider => provider.GetService(typeof(IServiceScopeFactory)) == factory)); + var provider = Mock.Of(provider => + provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of() && + provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of() && + provider.GetService(typeof(IOptionsMonitor)) == monitor); + + var scope = Mock.Of(scope => scope.ServiceProvider == provider); + var factory = Mock.Of(factory => factory.CreateScope() == scope); + + var job = new OpenIddictQuartzJob(Mock.Of(provider => provider.GetService(typeof(IServiceScopeFactory)) == factory)); // Act await job.Execute(Mock.Of()); @@ -47,14 +50,21 @@ public class OpenIddictQuartzJobTests public async Task Execute_IgnoresPruningWhenTokenPruningIsDisabled() { // Arrange + var monitor = Mock.Of>( + monitor => monitor.CurrentValue == new OpenIddictQuartzOptions + { + DisableTokenPruning = true, + TimeProvider = TimeProvider.System + }); var manager = new Mock(); var provider = Mock.Of(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of() && - provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object); + provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object && + provider.GetService(typeof(IOptionsMonitor)) == monitor); - var job = CreateJob(provider, options => options.DisableTokenPruning = true); + var job = CreateJob(provider); // Act await job.Execute(Mock.Of()); @@ -67,14 +77,21 @@ public class OpenIddictQuartzJobTests public async Task Execute_IgnoresPruningWhenAuthorizationPruningIsDisabled() { // Arrange + var monitor = Mock.Of>( + monitor => monitor.CurrentValue == new OpenIddictQuartzOptions + { + DisableAuthorizationPruning = true, + TimeProvider = TimeProvider.System + }); var manager = new Mock(); var provider = Mock.Of(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object && - provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of()); + provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of() && + provider.GetService(typeof(IOptionsMonitor)) == monitor); - var job = CreateJob(provider, options => options.DisableAuthorizationPruning = true); + var job = CreateJob(provider); // Act await job.Execute(Mock.Of()); @@ -87,14 +104,22 @@ public class OpenIddictQuartzJobTests public async Task Execute_UnschedulesTriggersWhenTokenManagerIsMissing() { // Arrange + var monitor = Mock.Of>( + monitor => monitor.CurrentValue == new OpenIddictQuartzOptions + { + TimeProvider = TimeProvider.System + }); + var provider = Mock.Of(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of() && - provider.GetService(typeof(IOpenIddictTokenManager)) == null); + provider.GetService(typeof(IOpenIddictTokenManager)) == null && + provider.GetService(typeof(IOptionsMonitor)) == monitor); var job = CreateJob(provider); // Act and assert - var exception = await Assert.ThrowsAsync(() => job.Execute(Mock.Of())); + var exception = await Assert.ThrowsAsync(async () => + await job.Execute(Mock.Of())); Assert.False(exception.RefireImmediately); Assert.True(exception.UnscheduleAllTriggers); @@ -108,13 +133,21 @@ public class OpenIddictQuartzJobTests public async Task Execute_UnschedulesTriggersWhenAuthorizationManagerIsMissing() { // Arrange + var monitor = Mock.Of>( + monitor => monitor.CurrentValue == new OpenIddictQuartzOptions + { + TimeProvider = TimeProvider.System + }); + var provider = Mock.Of(provider => - provider.GetService(typeof(IOpenIddictAuthorizationManager)) == null); + provider.GetService(typeof(IOpenIddictAuthorizationManager)) == null && + provider.GetService(typeof(IOptionsMonitor)) == monitor); var job = CreateJob(provider); // Act and assert - var exception = await Assert.ThrowsAsync(() => job.Execute(Mock.Of())); + var exception = await Assert.ThrowsAsync(async () => + await job.Execute(Mock.Of())); Assert.False(exception.RefireImmediately); Assert.True(exception.UnscheduleAllTriggers); @@ -128,36 +161,50 @@ public class OpenIddictQuartzJobTests public async Task Execute_RethrowsOutOfMemoryExceptionsThrownDuringTokenPruning() { // Arrange + var monitor = Mock.Of>( + monitor => monitor.CurrentValue == new OpenIddictQuartzOptions + { + TimeProvider = TimeProvider.System + }); + var manager = new Mock(); manager.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) .Throws(new OutOfMemoryException()); var provider = Mock.Of(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of() && - provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object); + provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object && + provider.GetService(typeof(IOptionsMonitor)) == monitor); var job = CreateJob(provider); // Act and assert - await Assert.ThrowsAsync(() => job.Execute(Mock.Of())); + await Assert.ThrowsAsync(async () => await job.Execute(Mock.Of())); } [Fact] public async Task Execute_RethrowsOutOfMemoryExceptionsThrownDuringAuthorizationPruning() { // Arrange + var monitor = Mock.Of>( + monitor => monitor.CurrentValue == new OpenIddictQuartzOptions + { + TimeProvider = TimeProvider.System + }); + var manager = new Mock(); manager.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) .Throws(new OutOfMemoryException()); var provider = Mock.Of(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object && - provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of()); + provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of() && + provider.GetService(typeof(IOptionsMonitor)) == monitor); var job = CreateJob(provider); // Act and assert - await Assert.ThrowsAsync(() => job.Execute(Mock.Of())); + await Assert.ThrowsAsync(async () => await job.Execute(Mock.Of())); } [Fact] @@ -166,20 +213,27 @@ public class OpenIddictQuartzJobTests // Arrange var token = new CancellationToken(canceled: true); + var monitor = Mock.Of>( + monitor => monitor.CurrentValue == new OpenIddictQuartzOptions + { + TimeProvider = TimeProvider.System + }); + var manager = new Mock(); manager.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) .Throws(new OperationCanceledException(token)); var provider = Mock.Of(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of() && - provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object); + provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object && + provider.GetService(typeof(IOptionsMonitor)) == monitor); var context = Mock.Of(context => context.CancellationToken == token); var job = CreateJob(provider); // Act and assert - var exception = await Assert.ThrowsAsync(() => job.Execute(context)); + var exception = await Assert.ThrowsAsync(async () => await job.Execute(context)); Assert.False(exception.RefireImmediately); @@ -192,20 +246,27 @@ public class OpenIddictQuartzJobTests // Arrange var token = new CancellationToken(canceled: true); + var monitor = Mock.Of>( + monitor => monitor.CurrentValue == new OpenIddictQuartzOptions + { + TimeProvider = TimeProvider.System + }); + var manager = new Mock(); manager.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) .Throws(new OperationCanceledException(token)); var provider = Mock.Of(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object && - provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of()); + provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of() && + provider.GetService(typeof(IOptionsMonitor)) == monitor); var context = Mock.Of(context => context.CancellationToken == token); var job = CreateJob(provider); // Act and assert - var exception = await Assert.ThrowsAsync(() => job.Execute(context)); + var exception = await Assert.ThrowsAsync(async () => await job.Execute(context)); Assert.False(exception.RefireImmediately); @@ -216,6 +277,12 @@ public class OpenIddictQuartzJobTests public async Task Execute_AllowsRefiringWhenExceptionsAreThrown() { // Arrange + var monitor = Mock.Of>( + monitor => monitor.CurrentValue == new OpenIddictQuartzOptions + { + TimeProvider = TimeProvider.System + }); + var provider = new Mock(); provider.Setup(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager))) .Returns(CreateAuthorizationManager(new ApplicationException())); @@ -223,12 +290,15 @@ public class OpenIddictQuartzJobTests provider.Setup(provider => provider.GetService(typeof(IOpenIddictTokenManager))) .Returns(CreateTokenManager(new ApplicationException())); + provider.Setup(provider => provider.GetService(typeof(IOptionsMonitor))) + .Returns(monitor); + var context = Mock.Of(context => context.RefireCount == 0); var job = CreateJob(provider.Object); // Act and assert - var exception = await Assert.ThrowsAsync(() => job.Execute(context)); + var exception = await Assert.ThrowsAsync(async () => await job.Execute(context)); Assert.True(exception.RefireImmediately); Assert.IsType(exception.InnerException); @@ -259,6 +329,12 @@ public class OpenIddictQuartzJobTests public async Task Execute_AllowsRefiringWhenAggregateExceptionsAreThrown() { // Arrange + var monitor = Mock.Of>( + monitor => monitor.CurrentValue == new OpenIddictQuartzOptions + { + TimeProvider = TimeProvider.System + }); + var provider = new Mock(); provider.Setup(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager))) .Returns(CreateAuthorizationManager(new AggregateException( @@ -268,12 +344,15 @@ public class OpenIddictQuartzJobTests .Returns(CreateTokenManager(new AggregateException( new InvalidOperationException(), new ApplicationException()))); + provider.Setup(provider => provider.GetService(typeof(IOptionsMonitor))) + .Returns(monitor); + var context = Mock.Of(context => context.RefireCount == 0); var job = CreateJob(provider.Object); // Act and assert - var exception = await Assert.ThrowsAsync(() => job.Execute(context)); + var exception = await Assert.ThrowsAsync(async () => await job.Execute(context)); Assert.True(exception.RefireImmediately); Assert.IsType(exception.InnerException); @@ -306,38 +385,38 @@ public class OpenIddictQuartzJobTests public async Task Execute_DisallowsRefiringWhenMaximumRefireCountIsReached() { // Arrange + var monitor = Mock.Of>( + monitor => monitor.CurrentValue == new OpenIddictQuartzOptions + { + MaximumRefireCount = 5, + TimeProvider = TimeProvider.System + }); + var manager = new Mock(); manager.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) .Throws(new ApplicationException()); var provider = Mock.Of(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object && - provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of()); + provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of() && + provider.GetService(typeof(IOptionsMonitor)) == monitor); var context = Mock.Of(context => context.RefireCount == 5); - var job = CreateJob(provider, options => options.MaximumRefireCount = 5); + var job = CreateJob(provider); // Act and assert - var exception = await Assert.ThrowsAsync(() => job.Execute(context)); + var exception = await Assert.ThrowsAsync(async () => await job.Execute(context)); Assert.False(exception.RefireImmediately); } - private static OpenIddictQuartzJob CreateJob(IServiceProvider provider, Action? configuration = null) + private static OpenIddictQuartzJob CreateJob(IServiceProvider provider) { var scope = Mock.Of(scope => scope.ServiceProvider == provider); var factory = Mock.Of(factory => factory.CreateScope() == scope); - var options = new OpenIddictQuartzOptions - { - TimeProvider = TimeProvider.System - }; - - configuration?.Invoke(options); - - var monitor = Mock.Of>(monitor => monitor.CurrentValue == options); - return new OpenIddictQuartzJob(monitor, - Mock.Of(provider => provider.GetService(typeof(IServiceScopeFactory)) == factory)); + return new OpenIddictQuartzJob(Mock.Of(provider => + provider.GetService(typeof(IServiceScopeFactory)) == factory)); } }