Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
nulltoken committed Nov 17, 2024
1 parent 1a09c2a commit 49b209b
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 4 deletions.
35 changes: 34 additions & 1 deletion src/NCronJob/Configuration/Builder/DependencyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,43 @@ public sealed class DependencyBuilder<TPrincipalJob>
public DependencyBuilder<TPrincipalJob> RunJob<TJob>(object? parameter = null)
where TJob : IJob
{
dependentJobOptions.Add(new JobDefinition(typeof(TJob), parameter, null, null));
var jobDefinition = jobRegistry.FindJobDefinition(typeof(TJob));

if (jobDefinition is null)
{
dependentJobOptions.Add(new JobDefinition(typeof(TJob), parameter, null, null));
}
else
{
dependentJobOptions.Add(jobDefinition with { Parameter = parameter });

Check warning on line 31 in src/NCronJob/Configuration/Builder/DependencyBuilder.cs

View check run for this annotation

Codecov / codecov/patch

src/NCronJob/Configuration/Builder/DependencyBuilder.cs#L31

Added line #L31 was not covered by tests
}

return this;
}

/// <summary>
/// Adds a job that runs after the principal job has finished with a given <paramref name="parameter"/>.
/// <param name="jobName">The name of the registered job to run.</param>
/// <param name="parameter">An optional parameter that is passed down as the <see cref="JobExecutionContext"/> to the job.</param>
/// </summary>
/// <remarks>
/// </remarks>
public DependencyBuilder<TPrincipalJob> RunJob(string jobName, object? parameter = null)
{
var jobDefinition = jobRegistry.FindJobDefinition(jobName);

if (jobDefinition is not null)
{
dependentJobOptions.Add(jobDefinition with { Parameter = parameter });
return this;
}

throw new InvalidOperationException(
$"""
Invalid job reference detected. Job named '{jobName}' hasn't been previously registered.
""");

Check warning on line 57 in src/NCronJob/Configuration/Builder/DependencyBuilder.cs

View check run for this annotation

Codecov / codecov/patch

src/NCronJob/Configuration/Builder/DependencyBuilder.cs#L54-L57

Added lines #L54 - L57 were not covered by tests
}

/// <summary>
/// Adds an anonymous delegate job that runs after the principal job has finished.
/// </summary>
Expand Down
21 changes: 18 additions & 3 deletions src/NCronJob/Registry/JobRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,22 @@ private readonly Dictionary<JobDefinition, List<DependentJobRegistryEntry>> depe
public IReadOnlyCollection<JobDefinition> GetAllOneTimeJobs() => allJobs.Where(c => c.IsStartupJob).ToList();

public JobDefinition? FindJobDefinition(Type type)
=> allJobs.FirstOrDefault(j => j.Type == type);
{
var jobDefinitionsPerType = allJobs.Where(j => j.Type == type).ToList();

#pragma warning disable IDE0046 // 'if' statement can be simplified
if (jobDefinitionsPerType.Count <= 1)
{
return jobDefinitionsPerType.SingleOrDefault();
}

throw new InvalidOperationException(
$"""
Ambiguous job reference for type '{type.Name}' detected. Multiple jobs with the same type already exists.
Please either remove duplicated jobs, or assign a unique name to it if duplication is intended and reference it using its name.
""");
#pragma warning restore IDE0046 // 'if' statement can be simplified
}

public JobDefinition? FindJobDefinition(string jobName)
=> allJobs.FirstOrDefault(j => j.CustomName == jobName);
Expand Down Expand Up @@ -189,12 +204,12 @@ private sealed class DependentJobDefinitionEqualityComparer : IEqualityComparer<
public bool Equals(JobDefinition? x, JobDefinition? y) =>
(x is null && y is null) || (x is not null && y is not null
&& x.Type == y.Type && x.Type != typeof(DynamicJobFactory)
&& x.Parameter == y.Parameter
//&& x.Parameter == y.Parameter
&& x.CustomName == y.CustomName);

public int GetHashCode(JobDefinition obj) => HashCode.Combine(
obj.Type,
obj.Parameter,
//obj.Parameter,
obj.CustomName
);
}
Expand Down
20 changes: 20 additions & 0 deletions tests/NCronJob.Tests/NCronJobIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,26 @@ public async Task InstantJobShouldPassDownParameter()
content.ShouldBe("Hello from InstantJob");
}

[Fact]
public async Task ShouldThrowRuntimeExceptionWhenForceRunningAnAmbiguousTypeReference()
{
ServiceCollection.AddNCronJob(n =>
{
n.AddJob<ParameterJob>(s => s.WithCronExpression("* * 30 2 *"));
n.AddJob<ParameterJob>(s => s.WithCronExpression("* * 31 2 *"));
});

var provider = CreateServiceProvider();
await provider.GetRequiredService<IHostedService>().StartAsync(CancellationToken);

var instantJobRegistry = provider.GetRequiredService<IInstantJobRegistry>();

Action act = () => instantJobRegistry.ForceRunInstantJob<ParameterJob>("something", CancellationToken);

act.ShouldThrow<InvalidOperationException>()
.Message.ShouldContain($"Ambiguous job reference for type 'ParameterJob' detected.");
}

[Fact]
public async Task CronJobShouldInheritInitiallyDefinedParameter()
{
Expand Down
42 changes: 42 additions & 0 deletions tests/NCronJob.Tests/RunDependentJobTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,48 @@ public async Task ConfiguringDifferentDependentJobsForSchedulesShouldResultInInd
results.ShouldContain("2");
}

[Fact]
public void ShouldThrowRuntimeExceptionWhenRegistratingAnAmbiguousDependentJobTypeReference()
{
Action act = () => ServiceCollection.AddNCronJob(n =>
{
n.AddJob<DependentJob>(s => s.WithCronExpression("* * 30 2 *"))
.ExecuteWhen(s => s.RunJob((ChannelWriter<object> writer) => writer.WriteAsync("1").AsTask()));
n.AddJob<DependentJob>(s => s.WithCronExpression("* * 31 2 *"))
.ExecuteWhen(s => s.RunJob((ChannelWriter<object> writer) => writer.WriteAsync("2").AsTask()));
n.AddJob<PrincipalJob>(s => s.WithCronExpression("* * * * *")).ExecuteWhen(s => s.RunJob<DependentJob>());
});

act.ShouldThrow<InvalidOperationException>()
.Message.ShouldContain($"Ambiguous job reference for type 'DependentJob' detected.");
}

[Fact]
public async Task CanUniquelyIdentifyAJobAmongAmbiguousDependentJobTypeReference()
{
ServiceCollection.AddNCronJob(n =>
{
n.AddJob<DependentJob>(s => s.WithCronExpression("* * 30 2 *"))
.ExecuteWhen(s => s.RunJob((ChannelWriter<object> writer) => writer.WriteAsync("1").AsTask()));
n.AddJob<DependentJob>(s => s.WithCronExpression("* * 31 2 *").WithName("myJob").WithParameter("42"))
.ExecuteWhen(s => s.RunJob((ChannelWriter<object> writer) => writer.WriteAsync("2").AsTask()));
n.AddJob<PrincipalJob>(s => s.WithCronExpression("* * * * *").WithParameter(true)).ExecuteWhen(s => s.RunJob("myJob", "17"));
});

var provider = CreateServiceProvider();
await provider.GetRequiredService<IHostedService>().StartAsync(CancellationToken);

FakeTimer.Advance(TimeSpan.FromMinutes(1));

List<string?> results = [];
results.Add(await CommunicationChannel.Reader.ReadAsync(CancellationToken) as string);
results.Add(await CommunicationChannel.Reader.ReadAsync(CancellationToken) as string);
results.Add(await CommunicationChannel.Reader.ReadAsync(CancellationToken) as string);
results.ShouldContain("PrincipalJob: Success");
results.ShouldContain("DependentJob: 17 Parent: Success");
results.ShouldContain("2");
}

private sealed class PrincipalJob(ChannelWriter<object> writer) : IJob
{
public async Task RunAsync(IJobExecutionContext context, CancellationToken token)
Expand Down
17 changes: 17 additions & 0 deletions tests/NCronJob.Tests/RuntimeJobRegistryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,23 @@ public void ShouldThrowRuntimeExceptionWithDuplicateJob()
exception.ShouldBeOfType<InvalidOperationException>();
}

[Fact]
public void ShouldThrowRuntimeExceptionWhenRemovingAnAmbiguousTypeReference()
{
ServiceCollection.AddNCronJob(n =>
{
n.AddJob<SimpleJob>(s => s.WithCronExpression("* * 30 2 *"));
n.AddJob<SimpleJob>(s => s.WithCronExpression("* * 31 2 *"));
});

var runtimeJobRegistry = CreateServiceProvider().GetRequiredService<IRuntimeJobRegistry>();

Action act = runtimeJobRegistry.RemoveJob<SimpleJob>;

act.ShouldThrow<InvalidOperationException>()
.Message.ShouldContain($"Ambiguous job reference for type 'SimpleJob' detected.");
}

[Fact]
public void TryRegisteringShouldIndicateFailureWithAGivenException()
{
Expand Down

0 comments on commit 49b209b

Please sign in to comment.