Skip to content

Commit

Permalink
ScheduleJob shorthand: Job name should match trigger name by default
Browse files Browse the repository at this point in the history
ScheduleJob(trigger => trigger.WithIdentity()), without a job configurator, now sets the job identity to match the trigger's (instead of "DEFAULT-[UUID]").

Co-authored-by: Timo van Zijll Langhout <t.zijll@buckaroo.nl>
  • Loading branch information
Timovzl and Timo van Zijll Langhout committed Jul 29, 2021
1 parent 41846f4 commit aa53de9
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public static class ServiceCollectionExtensions
c.WithIdentity(jobKey);
}

var jobDetail = ConfigureAndBuildJobDetail(jobType, c, configure);
var jobDetail = ConfigureAndBuildJobDetail(jobType, c, configure, hasCustomKey: out _);

options.Services.Configure<QuartzOptions>(x =>
{
Expand Down Expand Up @@ -176,7 +176,7 @@ public static class ServiceCollectionExtensions
}

var jobConfigurator = new JobConfigurator();
var jobDetail = ConfigureAndBuildJobDetail(typeof(T),jobConfigurator, job);
var jobDetail = ConfigureAndBuildJobDetail(typeof(T), jobConfigurator, job, out var jobHasCustomKey);

options.Services.Configure<QuartzOptions>(quartzOptions =>
{
Expand All @@ -189,6 +189,16 @@ public static class ServiceCollectionExtensions
trigger.Invoke(triggerConfigurator);
var t = triggerConfigurator.Build();

// The job configurator is optional and omitted in most examples
// If no job key was specified, have the job key match the trigger key
if (!jobHasCustomKey)
{
((JobDetailImpl)jobDetail).Key = new JobKey(t.Key.Name, t.Key.Group);

// Keep ITrigger.JobKey in sync with IJobDetail.Key
((IMutableTrigger)t).JobKey = jobDetail.Key;
}

if (t.JobKey is null || !t.JobKey.Equals(jobDetail.Key))
{
throw new InvalidOperationException("Trigger doesn't refer to job being scheduled");
Expand All @@ -207,10 +217,12 @@ public static class ServiceCollectionExtensions
private static IJobDetail ConfigureAndBuildJobDetail(
Type type,
JobConfigurator builder,
Action<IJobConfigurator>? configure)
Action<IJobConfigurator>? configure,
out bool hasCustomKey)
{
builder.OfType(type);
configure?.Invoke(builder);
hasCustomKey = builder.Key is not null;
var jobDetail = builder.Build();
return jobDetail;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NUnit.Framework;

namespace Quartz.Tests.Unit.Extensions.DependencyInjection
{
[TestFixture()]
public class ServiceCollectionExtensionsTests
{
[Test]
public void ScheduleJob_WithJobIdentity_ShouldHonorIt()
{
var services = new ServiceCollection();

// Go through AddQuartz(), because the IServiceCollectionQuartzConfigurator interface refuses mocking or implementation, due to an internal default-implemented property
services.AddQuartz(quartz => quartz.ScheduleJob<DummyJob>(
trigger => trigger.WithIdentity("TriggerName", "TriggerGroup"),
job => job.WithIdentity("JobName", "JobGroup")));

using var serviceProvider = services.BuildServiceProvider();

var quartzOptions = serviceProvider.GetRequiredService<IOptions<QuartzOptions>>().Value;

Assert.That(quartzOptions.Triggers, Has.Exactly(1).Items);
Assert.That(quartzOptions.JobDetails, Has.Exactly(1).Items);

var trigger = quartzOptions.Triggers.Single();
var job = quartzOptions.JobDetails.Single();

// The trigger key should have its own manual configuration
Assert.AreEqual("TriggerName", trigger.Key.Name);
Assert.AreEqual("TriggerGroup", trigger.Key.Group);

// The job key should have its own manual configuration
Assert.AreEqual("JobName", job.Key.Name);
Assert.AreEqual("JobGroup", job.Key.Group);

// Also validate that the trigger knows the correct job key
Assert.AreEqual(job.Key.Name, trigger.JobKey.Name);
Assert.AreEqual(job.Key.Group, trigger.JobKey.Group);
}

[Test]
public void ScheduleJob_WithoutJobIdentityWithoutTriggerIdentity_ShouldCopyFromTriggerIdentity()
{
var services = new ServiceCollection();

// Go through AddQuartz(), because the IServiceCollectionQuartzConfigurator interface refuses mocking or implementation, due to an internal default-implemented property
services.AddQuartz(quartz => quartz.ScheduleJob<DummyJob>(
trigger => { }));

using var serviceProvider = services.BuildServiceProvider();

var quartzOptions = serviceProvider.GetRequiredService<IOptions<QuartzOptions>>().Value;

Assert.That(quartzOptions.Triggers, Has.Exactly(1).Items);
Assert.That(quartzOptions.JobDetails, Has.Exactly(1).Items);

var trigger = quartzOptions.Triggers.Single();
var job = quartzOptions.JobDetails.Single();

// The job's key should match the trigger's (auto-generated) key
Assert.AreEqual(trigger.Key.Name, job.Key.Name);
Assert.AreEqual(trigger.Key.Group, job.Key.Group);

// Also validate that the trigger knows the correct job key
Assert.AreEqual(job.Key.Name, trigger.JobKey.Name);
Assert.AreEqual(job.Key.Group, trigger.JobKey.Group);
}

[Test]
public void ScheduleJob_WithoutJobIdentityWithTriggerIdentity_ShouldCopyFromTriggerIdentity()
{
var services = new ServiceCollection();

// Go through AddQuartz(), because the IServiceCollectionQuartzConfigurator interface refuses mocking or implementation, due to an internal default-implemented property
services.AddQuartz(quartz => quartz.ScheduleJob<DummyJob>(
trigger => trigger.WithIdentity("TriggerName", "TriggerGroup")));

using var serviceProvider = services.BuildServiceProvider();

var quartzOptions = serviceProvider.GetRequiredService<IOptions<QuartzOptions>>().Value;

Assert.That(quartzOptions.Triggers, Has.Exactly(1).Items);
Assert.That(quartzOptions.JobDetails, Has.Exactly(1).Items);

var trigger = quartzOptions.Triggers.Single();
var job = quartzOptions.JobDetails.Single();

// The trigger key should have its own manual configuration
Assert.AreEqual("TriggerName", trigger.Key.Name);
Assert.AreEqual("TriggerGroup", trigger.Key.Group);

// The job's key should match the trigger's (auto-generated) key
Assert.AreEqual(trigger.Key.Name, job.Key.Name);
Assert.AreEqual(trigger.Key.Group, job.Key.Group);

// Also validate that the trigger knows the correct job key
Assert.AreEqual(job.Key.Name, trigger.JobKey.Name);
Assert.AreEqual(job.Key.Group, trigger.JobKey.Group);
}

private sealed class DummyJob : IJob
{
public Task Execute(IJobExecutionContext context)
{
return Task.CompletedTask;
}
}
}
}
2 changes: 2 additions & 0 deletions src/Quartz.Tests.Unit/Quartz.Tests.Unit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
<PackageReference Include="FakeItEasy" Version="6.2.1" />
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="NUnit" Version="3.13.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Quartz.Extensions.DependencyInjection\Quartz.Extensions.DependencyInjection.csproj" />
<ProjectReference Include="..\Quartz\Quartz.csproj" />
<ProjectReference Include="..\Quartz.Jobs\Quartz.Jobs.csproj" />
<ProjectReference Include="..\Quartz.Plugins\Quartz.Plugins.csproj" />
Expand Down
7 changes: 6 additions & 1 deletion src/Quartz/JobBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#region License
#region License

/*
* All content copyright Marko Lahma, unless otherwise indicated. All rights reserved.
Expand Down Expand Up @@ -73,6 +73,11 @@ public class JobBuilder : IJobConfigurator

private JobDataMap jobDataMap = new JobDataMap();

/// <summary>
/// The key that identifies the job uniquely.
/// </summary>
internal JobKey? Key => key;

protected JobBuilder()
{
}
Expand Down

0 comments on commit aa53de9

Please sign in to comment.