diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 51c71e5..6ceb3fc 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
- "version": "9.0.7",
+ "version": "10.0.12",
"commands": [
"dotnet-ef"
]
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index eb80fbb..84e52b8 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -12,3 +12,23 @@ updates:
commit-message:
include: scope
prefix: "fix: "
+ # Framework-aligned packages are versioned per target framework in Directory.Packages.props.
+ # A new major version means a new target framework block, which is added by hand.
+ ignore:
+ - dependency-name: "Microsoft.Extensions.*"
+ update-types: ["version-update:semver-major"]
+ - dependency-name: "Microsoft.EntityFrameworkCore*"
+ update-types: ["version-update:semver-major"]
+ - dependency-name: "Microsoft.AspNetCore.Authentication.JwtBearer"
+ update-types: ["version-update:semver-major"]
+ - dependency-name: "Microsoft.AspNetCore.JsonPatch"
+ update-types: ["version-update:semver-major"]
+ - dependency-name: "Microsoft.AspNetCore.Mvc.NewtonsoftJson"
+ update-types: ["version-update:semver-major"]
+ - package-ecosystem: "dotnet-sdk"
+ directory: "/"
+ schedule:
+ interval: "monthly"
+ commit-message:
+ include: scope
+ prefix: "chore: "
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index ceefca6..f08e78a 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -13,12 +13,14 @@ jobs:
steps:
- name: Checkout Repo
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Setup .NET Core
- uses: actions/setup-dotnet@v1
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: '9.0.x'
+ # The .NET 9 SDK supplies the net9.0 runtime for tests; global.json pins the SDK that builds.
+ dotnet-version: 9.0.x
+ global-json-file: global.json
- name: Run dotnet format
run: dotnet format jobba.sln --verify-no-changes
diff --git a/.github/workflows/build-test-and-release.yml b/.github/workflows/build-test-and-release.yml
index 8764aa4..816e7b2 100644
--- a/.github/workflows/build-test-and-release.yml
+++ b/.github/workflows/build-test-and-release.yml
@@ -11,11 +11,18 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Git
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
token : ${{ secrets.FIREBEND_GIT_PUSH_PAT}}
fetch-depth: 0
+ - name: Setup .NET Core
+ uses: actions/setup-dotnet@v4
+ with:
+ # The .NET 9 SDK supplies the net9.0 runtime for tests; global.json pins the SDK that builds.
+ dotnet-version: 9.0.x
+ global-json-file: global.json
+
- name: Install Versionize
run: dotnet tool install --global Versionize
@@ -33,12 +40,6 @@ jobs:
if: steps.versionize.outcome != 'success'
run: echo "Skipping publishing. No release required."
- - name: Setup .NET Core
- if: steps.versionize.outcome == 'success'
- uses: actions/setup-dotnet@v1
- with:
- dotnet-version: '9.0.x'
-
- name: Install dependencies
if: steps.versionize.outcome == 'success'
run: dotnet restore jobba.sln
@@ -87,7 +88,6 @@ jobs:
if: steps.versionize.outcome == 'success'
run: dotnet nuget push ./out/pack/*.nupkg --api-key ${{secrets.FIREBEND_NUGET_KEY}} --skip-duplicate --source https://api.nuget.org/v3/index.json
-
- name: Push version changes
if: steps.versionize.outcome == 'success'
uses: CasperWA/push-protected@v2
diff --git a/.versionize b/.versionize
index 77fa5f2..a165f91 100644
--- a/.versionize
+++ b/.versionize
@@ -1,6 +1,6 @@
{
"changelog": {
- "header": "Lit Redis Changelog",
+ "header": "Jobba Changelog",
"includeAllCommits": true,
"sections": [
{
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a29be29..cead949 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-Lit Redis Changelog
+Jobba Changelog
## [12.0.1](https://www.github.com/firebend/jobba/releases/tag/v12.0.1) (2026-09-23)
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..d51d7e4
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,12 @@
+
+
+
+ net9.0;net10.0
+
+ net10.0
+
+
+
+
+
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 0000000..718277b
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,70 @@
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Jobba.Core/Jobba.Core.csproj b/Jobba.Core/Jobba.Core.csproj
index 605c660..694b465 100644
--- a/Jobba.Core/Jobba.Core.csproj
+++ b/Jobba.Core/Jobba.Core.csproj
@@ -4,7 +4,7 @@
12.0.1
latestmajor
disable
- net9.0
+ $(FirebendTargetFrameworks)
1
@@ -14,16 +14,16 @@
firebend;Jobba;job;scheduling;durable;background;jobs
Firebend
https://github.com/firebend/Jobba
- https://github.com/firebend/Jobba/blob/main/LICENSE
+ MIT
https://github.com/firebend/Jobba
- https://github.com/firebend/Jobba/blob/main/nuget-image.png?raw=true
+ nuget-image.png
-
-
-
-
+
+
+
+
diff --git a/Jobba.Core/Models/JobRegistration.cs b/Jobba.Core/Models/JobRegistration.cs
index 336fbb5..6559b11 100644
--- a/Jobba.Core/Models/JobRegistration.cs
+++ b/Jobba.Core/Models/JobRegistration.cs
@@ -8,8 +8,10 @@ namespace Jobba.Core.Models;
///
public class JobRegistration : IJobbaEntity
{
- private string _timeZoneId = "UTC";
+ // The TimeZoneId setter clears this cache, so it has to stay an explicit field rather than a field-backed property.
+#pragma warning disable IDE0032
private TimeZoneInfo _timeZoneInfo;
+#pragma warning restore IDE0032
///
/// The Id
@@ -91,13 +93,13 @@ public class JobRegistration : IJobbaEntity
///
public string TimeZoneId
{
- get => _timeZoneId;
+ get;
set
{
- _timeZoneId = value;
+ field = value;
_timeZoneInfo = null;
}
- }
+ } = "UTC";
public TimeZoneInfo TimeZoneInfo => _timeZoneInfo ??= TimeZoneInfo.FindSystemTimeZoneById(TimeZoneId ?? "UTC");
diff --git a/Jobba.Core/Models/JobbaCoreOptions.cs b/Jobba.Core/Models/JobbaCoreOptions.cs
index f4c9ab3..3f16a79 100644
--- a/Jobba.Core/Models/JobbaCoreOptions.cs
+++ b/Jobba.Core/Models/JobbaCoreOptions.cs
@@ -17,8 +17,6 @@ public class JobbaCoreOptions
///
public static readonly TimeSpan DefaultJobWatchInterval = TimeSpan.FromSeconds(10);
- private int _staleMultiplier = 3;
-
///
/// The multiplier applied to JobWatchInterval to determine when an InProgress job is considered stale.
/// If a job's LastHeartbeatTime is older than (JobWatchInterval × StaleMultiplier), it will be reclaimed.
@@ -27,9 +25,9 @@ public class JobbaCoreOptions
/// Thrown when value is less than 1.
public int StaleMultiplier
{
- get => _staleMultiplier;
- set => _staleMultiplier = value < 1
+ get;
+ set => field = value < 1
? throw new ArgumentOutOfRangeException(nameof(StaleMultiplier), "StaleMultiplier must be at least 1.")
: value;
- }
+ } = 3;
}
diff --git a/Jobba.Cron/Jobba.Cron.csproj b/Jobba.Cron/Jobba.Cron.csproj
index af201d9..5aa835a 100644
--- a/Jobba.Cron/Jobba.Cron.csproj
+++ b/Jobba.Cron/Jobba.Cron.csproj
@@ -1,7 +1,7 @@
- net9.0
+ $(FirebendTargetFrameworks)
12.0.1
latestmajor
@@ -12,9 +12,9 @@
firebend;Jobba;job;scheduling;durable;background;jobs;cron
Firebend
https://github.com/firebend/Jobba
- https://github.com/firebend/Jobba/blob/main/LICENSE
+ MIT
https://github.com/firebend/Jobba
- https://github.com/firebend/Jobba/blob/main/nuget-image.png?raw=true
+ nuget-image.png
@@ -22,7 +22,7 @@
-
+
diff --git a/Jobba.IntegrationTests/Jobba.IntegrationTests.csproj b/Jobba.IntegrationTests/Jobba.IntegrationTests.csproj
index 25ed85c..acf3f84 100644
--- a/Jobba.IntegrationTests/Jobba.IntegrationTests.csproj
+++ b/Jobba.IntegrationTests/Jobba.IntegrationTests.csproj
@@ -1,22 +1,22 @@
- net9.0
+ $(FirebendTargetFrameworks)
false
-
-
-
-
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
-
-
-
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/Jobba.MassTransit/Jobba.MassTransit.csproj b/Jobba.MassTransit/Jobba.MassTransit.csproj
index 190fea1..e6aa675 100644
--- a/Jobba.MassTransit/Jobba.MassTransit.csproj
+++ b/Jobba.MassTransit/Jobba.MassTransit.csproj
@@ -1,7 +1,7 @@
- net9.0
+ $(FirebendTargetFrameworks)
12.0.1
latestmajor
disable
@@ -13,9 +13,9 @@
firebend;Jobba;job;scheduling;durable;background;jobs;masstransit;events
Firebend
https://github.com/firebend/Jobba
- https://github.com/firebend/Jobba/blob/main/LICENSE
+ MIT
https://github.com/firebend/Jobba
- https://github.com/firebend/Jobba/blob/main/nuget-image.png?raw=true
+ nuget-image.png
@@ -23,7 +23,7 @@
-
+
diff --git a/Jobba.Redis/Jobba.Redis.csproj b/Jobba.Redis/Jobba.Redis.csproj
index ee3153b..793227c 100644
--- a/Jobba.Redis/Jobba.Redis.csproj
+++ b/Jobba.Redis/Jobba.Redis.csproj
@@ -1,7 +1,7 @@
- net9.0
+ $(FirebendTargetFrameworks)
12.0.1
latestmajor
disable
@@ -13,13 +13,13 @@
firebend;Jobba;job;scheduling;durable;background;jobs;redis;locking;distributed
Firebend
https://github.com/firebend/Jobba
- https://github.com/firebend/Jobba/blob/main/LICENSE
+ MIT
https://github.com/firebend/Jobba
- https://github.com/firebend/Jobba/blob/main/nuget-image.png?raw=true
+ nuget-image.png
-
+
diff --git a/Jobba.Sample/Jobba.Sample.csproj b/Jobba.Sample/Jobba.Sample.csproj
index a059cb6..2853cf2 100644
--- a/Jobba.Sample/Jobba.Sample.csproj
+++ b/Jobba.Sample/Jobba.Sample.csproj
@@ -2,22 +2,22 @@
Exe
- net9.0
+ $(FirebendAppTargetFramework)
-
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/Jobba.Store.EF.Sql/Jobba.Store.EF.Sql.csproj b/Jobba.Store.EF.Sql/Jobba.Store.EF.Sql.csproj
index b95f043..97789ff 100644
--- a/Jobba.Store.EF.Sql/Jobba.Store.EF.Sql.csproj
+++ b/Jobba.Store.EF.Sql/Jobba.Store.EF.Sql.csproj
@@ -1,7 +1,7 @@
- net9.0
+ $(FirebendTargetFrameworks)
12.0.1
latestmajor
disable
@@ -13,19 +13,19 @@
firebend;Jobba;job;scheduling;durable;background;jobs;ef;sql;data;store
Firebend
https://github.com/firebend/Jobba
- https://github.com/firebend/Jobba/blob/main/LICENSE
+ MIT
https://github.com/firebend/Jobba
- https://github.com/firebend/Jobba/blob/main/nuget-image.png?raw=true
+ nuget-image.png
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
diff --git a/Jobba.Store.EF.Sqlite/Jobba.Store.EF.Sqlite.csproj b/Jobba.Store.EF.Sqlite/Jobba.Store.EF.Sqlite.csproj
index aa334b2..4993da6 100644
--- a/Jobba.Store.EF.Sqlite/Jobba.Store.EF.Sqlite.csproj
+++ b/Jobba.Store.EF.Sqlite/Jobba.Store.EF.Sqlite.csproj
@@ -1,7 +1,7 @@
- net9.0
+ $(FirebendTargetFrameworks)
12.0.1
latestmajor
disable
@@ -13,19 +13,19 @@
firebend;Jobba;job;scheduling;durable;background;jobs;ef;sqlite;data;store
Firebend
https://github.com/firebend/Jobba
- https://github.com/firebend/Jobba/blob/main/LICENSE
+ MIT
https://github.com/firebend/Jobba
- https://github.com/firebend/Jobba/blob/main/nuget-image.png?raw=true
+ nuget-image.png
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
diff --git a/Jobba.Store.EF/Jobba.Store.EF.csproj b/Jobba.Store.EF/Jobba.Store.EF.csproj
index 53cca49..932a1ce 100644
--- a/Jobba.Store.EF/Jobba.Store.EF.csproj
+++ b/Jobba.Store.EF/Jobba.Store.EF.csproj
@@ -1,7 +1,7 @@
- net9.0
+ $(FirebendTargetFrameworks)
12.0.1
latestmajor
enable
@@ -13,9 +13,9 @@
firebend;Jobba;job;scheduling;durable;background;jobs;ef;data;store
Firebend
https://github.com/firebend/Jobba
- https://github.com/firebend/Jobba/blob/main/LICENSE
+ MIT
https://github.com/firebend/Jobba
- https://github.com/firebend/Jobba/blob/main/nuget-image.png?raw=true
+ nuget-image.png
@@ -23,13 +23,13 @@
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
diff --git a/Jobba.Store.Mongo/Jobba.Store.Mongo.csproj b/Jobba.Store.Mongo/Jobba.Store.Mongo.csproj
index 22ef97d..079a03d 100644
--- a/Jobba.Store.Mongo/Jobba.Store.Mongo.csproj
+++ b/Jobba.Store.Mongo/Jobba.Store.Mongo.csproj
@@ -1,7 +1,7 @@
- net9.0
+ $(FirebendTargetFrameworks)
12.0.1
latestmajor
disable
@@ -13,9 +13,9 @@
firebend;Jobba;job;scheduling;durable;background;jobs;mongo;data;store
Firebend
https://github.com/firebend/Jobba
- https://github.com/firebend/Jobba/blob/main/LICENSE
+ MIT
https://github.com/firebend/Jobba
- https://github.com/firebend/Jobba/blob/main/nuget-image.png?raw=true
+ nuget-image.png
@@ -23,8 +23,8 @@
-
-
+
+
diff --git a/Jobba.Tests/Core/HostedServices/JobbaHostedServiceTests.cs b/Jobba.Tests/Core/HostedServices/JobbaHostedServiceTests.cs
index f63a53a..ac5d7b8 100644
--- a/Jobba.Tests/Core/HostedServices/JobbaHostedServiceTests.cs
+++ b/Jobba.Tests/Core/HostedServices/JobbaHostedServiceTests.cs
@@ -4,11 +4,13 @@
using System.Threading.Tasks;
using AutoFixture;
using AutoFixture.AutoMoq;
+using FluentAssertions;
using Jobba.Core.HostedServices;
using Jobba.Core.Interfaces;
using Jobba.Core.Interfaces.Repositories;
using Jobba.Core.Models;
using Jobba.Tests.AutoMoqCustomizations;
+using Microsoft.Extensions.Hosting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
@@ -36,7 +38,9 @@ public async Task Jobba_Hosted_Service_Should_Execute()
var hostedService = fixture.Create();
//act
+ // On .NET 10 BackgroundService.StartAsync no longer runs ExecuteAsync inline, so wait for the work itself.
await hostedService.StartAsync(default);
+ await hostedService.ExecuteTask;
//assert
rescheduler.Verify(x => x.RestartFaultedJobsAsync(It.IsAny()), Times.Once);
@@ -102,7 +106,11 @@ public async Task Jobba_Hosted_Service_Should_Register_Jobs_With_Store_On_Startu
var hostedService = fixture.Create();
//act
+ // On .NET 10 BackgroundService.StartAsync no longer runs ExecuteAsync inline, so wait for the work itself.
await hostedService.StartAsync(default);
+ await hostedService.ExecuteTask;
+
+ //assert
registrationStore.VerifyAll();
}
@@ -129,11 +137,34 @@ public async Task Jobba_Hosted_Service_Should_Throw_When_Ready_Gate_Fails()
var hostedService = fixture.Create();
- //act
- await Assert.ThrowsExactlyAsync(
- () => hostedService.StartAsync(default));
+ // BackgroundService comes from Microsoft.Extensions.Hosting.Abstractions, whose version tracks the
+ // runtime here (9.0.x on net9.0, 10.0.x on net10.0). Guard that so the branch below stays honest.
+ var hostingMajor = typeof(BackgroundService).Assembly.GetName().Version.Major;
+ hostingMajor.Should().Be(Environment.Version.Major);
+
+ //act + assert
+ if (Environment.Version.Major < 10)
+ {
+ // Before .NET 10, StartAsync runs ExecuteAsync inline up to its first incomplete await,
+ // so the gate's synchronous failure is thrown from StartAsync and aborts host startup.
+ var thrown = await Assert.ThrowsExactlyAsync(
+ () => hostedService.StartAsync(default));
+ thrown.Message.Should().Be("gate failed");
+ }
+ else
+ {
+ // From .NET 10, StartAsync queues all of ExecuteAsync on the thread pool and returns a completed task;
+ // the failure surfaces only through ExecuteTask, which the host observes.
+ var startTask = hostedService.StartAsync(default);
+ startTask.IsCompletedSuccessfully.Should().BeTrue();
+ await startTask;
+
+ hostedService.ExecuteTask.Should().NotBeNull();
+ var thrown = await Assert.ThrowsExactlyAsync(() => hostedService.ExecuteTask);
+ thrown.Message.Should().Be("gate failed");
+ hostedService.ExecuteTask.IsFaulted.Should().BeTrue();
+ }
- //assert
readyGate.Verify(x => x.WaitAsync(It.IsAny()), Times.Once);
rescheduler.Verify(x => x.RestartFaultedJobsAsync(It.IsAny()), Times.Never);
}
diff --git a/Jobba.Tests/EF/JobbaEfJobRegistrationStoreTests.cs b/Jobba.Tests/EF/JobbaEfJobRegistrationStoreTests.cs
index d636bb8..8575d73 100644
--- a/Jobba.Tests/EF/JobbaEfJobRegistrationStoreTests.cs
+++ b/Jobba.Tests/EF/JobbaEfJobRegistrationStoreTests.cs
@@ -9,6 +9,7 @@
using Jobba.Core.Models;
using Jobba.Store.EF.DbContexts;
using Jobba.Store.EF.Implementations;
+using Microsoft.EntityFrameworkCore;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
@@ -218,4 +219,77 @@ public async Task Should_Set_Job_Registration_Inactive()
jobRegistration.Should().NotBeNull();
jobRegistration!.IsInactive.Should().BeTrue();
}
+
+ [TestMethod]
+ public async Task Should_Round_Trip_TimeZoneId()
+ {
+ //arrange
+ var registration = _fixture.JobCronRegistrationBuilder()
+ .With(x => x.TimeZoneId, "America/Chicago")
+ .Create();
+ var store = _fixture.Create();
+
+ //act
+ var result = await store.RegisterJobAsync(registration, default);
+ _dbContext.ChangeTracker.Clear();
+ var column = await _dbContext.Database
+ .SqlQuery($"SELECT TimeZoneId AS Value FROM JobRegistrations WHERE JobName = {registration.JobName}")
+ .SingleAsync();
+ var jobRegistration = await store.GetJobRegistrationAsync(result.Id, default);
+
+ //assert
+ column.Should().Be("America/Chicago");
+ jobRegistration.Should().NotBeNull();
+ jobRegistration!.TimeZoneId.Should().Be("America/Chicago");
+ jobRegistration.TimeZoneInfo.Id.Should().Be("America/Chicago");
+ }
+
+ [TestMethod]
+ public async Task Should_Default_TimeZoneId_To_Utc_When_Not_Set()
+ {
+ //arrange
+ var registration = _fixture.JobCronRegistrationBuilder()
+ .Without(x => x.TimeZoneId)
+ .Create();
+ var store = _fixture.Create();
+
+ //act
+ var result = await store.RegisterJobAsync(registration, default);
+ _dbContext.ChangeTracker.Clear();
+ var jobRegistration = await store.GetJobRegistrationAsync(result.Id, default);
+
+ //assert
+ registration.TimeZoneId.Should().Be("UTC");
+ jobRegistration.Should().NotBeNull();
+ jobRegistration!.TimeZoneId.Should().Be("UTC");
+ jobRegistration.TimeZoneInfo.Should().Be(TimeZoneInfo.Utc);
+ }
+
+ [TestMethod]
+ public async Task Should_Update_TimeZoneId_And_Reset_TimeZoneInfo_On_Reregister()
+ {
+ //arrange
+ var registration = _fixture.JobCronRegistrationBuilder()
+ .With(x => x.TimeZoneId, "America/Chicago")
+ .Create();
+ var store = _fixture.Create();
+ var original = await store.RegisterJobAsync(registration, default);
+ original.TimeZoneInfo.Id.Should().Be("America/Chicago");
+
+ var reRegistration = _fixture.JobCronRegistrationBuilder()
+ .With(x => x.TimeZoneId, "Asia/Tokyo")
+ .Create();
+
+ //act
+ var updated = await store.RegisterJobAsync(reRegistration, default);
+ _dbContext.ChangeTracker.Clear();
+ var jobRegistration = await store.GetJobRegistrationAsync(original.Id, default);
+
+ //assert
+ updated.Should().BeSameAs(original);
+ updated.TimeZoneInfo.Id.Should().Be("Asia/Tokyo");
+ jobRegistration.Should().NotBeNull();
+ jobRegistration!.TimeZoneId.Should().Be("Asia/Tokyo");
+ jobRegistration.TimeZoneInfo.Id.Should().Be("Asia/Tokyo");
+ }
}
diff --git a/Jobba.Tests/Jobba.Tests.csproj b/Jobba.Tests/Jobba.Tests.csproj
index 3266fc7..6742db6 100644
--- a/Jobba.Tests/Jobba.Tests.csproj
+++ b/Jobba.Tests/Jobba.Tests.csproj
@@ -1,31 +1,31 @@
- net9.0
+ $(FirebendTargetFrameworks)
false
+ latestmajor
-
-
-
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
-
-
-
-
-
-
+
+
+
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
-
-
-
+
+
+
diff --git a/Jobba.Tests/MassTransit/MassTransitJobEventPublisherTests.cs b/Jobba.Tests/MassTransit/MassTransitJobEventPublisherTests.cs
index bfb1867..ecf1e80 100644
--- a/Jobba.Tests/MassTransit/MassTransitJobEventPublisherTests.cs
+++ b/Jobba.Tests/MassTransit/MassTransitJobEventPublisherTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Jobba.Core.Builders;
@@ -95,8 +96,13 @@ private static async Task PublishEventHelper(TMessage message, Func x is MassTransitJobbaReceiverHostedService);
hostedService.Should().NotBeNull();
- // ReSharper disable once PossibleNullReferenceException
- await hostedService.StartAsync(default);
+
+ // harness.Start() already started every hosted service, including this one. From .NET 10 its StartAsync
+ // returns before ExecuteAsync connects the receive endpoints, so publishing straight away races the
+ // endpoint and the message is never consumed. Wait on the service's ready gate instead.
+ using var readyTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
+ await ((IJobbaReadyGate)hostedService).WaitAsync(readyTimeout.Token);
+
var publisher = serviceProvider.GetService();
publisher.Should().NotBeNull().And.BeOfType();
diff --git a/Jobba.Tests/Mongo/MongoUpdateDefinitionAsserter.cs b/Jobba.Tests/Mongo/MongoUpdateDefinitionAsserter.cs
index 899d592..957e19b 100644
--- a/Jobba.Tests/Mongo/MongoUpdateDefinitionAsserter.cs
+++ b/Jobba.Tests/Mongo/MongoUpdateDefinitionAsserter.cs
@@ -18,17 +18,15 @@ public static BsonValue SerializeToBsonValue(this T value)
public class MongoUpdateDefinitionAsserter
{
private readonly UpdateDefinition _updateDefinition;
- private string _json;
- private BsonDocument _setDoc;
- public string Json => _json ??= _updateDefinition.Render(new RenderArgs(
+ public string Json => field ??= _updateDefinition.Render(new RenderArgs(
BsonSerializer.SerializerRegistry.GetSerializer(),
BsonSerializer.SerializerRegistry)
)
.ToBsonDocument()
.ToString();
- public BsonDocument SetDoc => _setDoc ??= BsonDocument.Parse(Json)["$set"].AsBsonDocument;
+ public BsonDocument SetDoc => field ??= BsonDocument.Parse(Json)["$set"].AsBsonDocument;
public MongoUpdateDefinitionAsserter(UpdateDefinition updateDefinition)
{
diff --git a/Jobba.Web.Sample/Jobba.Web.Sample.csproj b/Jobba.Web.Sample/Jobba.Web.Sample.csproj
index 227e72e..57862c4 100644
--- a/Jobba.Web.Sample/Jobba.Web.Sample.csproj
+++ b/Jobba.Web.Sample/Jobba.Web.Sample.csproj
@@ -1,13 +1,13 @@
- net9.0
+ $(FirebendAppTargetFramework)
-
-
-
+
+
+
diff --git a/Jobba.Web.Sample/Startup.cs b/Jobba.Web.Sample/Startup.cs
index f959773..3466831 100644
--- a/Jobba.Web.Sample/Startup.cs
+++ b/Jobba.Web.Sample/Startup.cs
@@ -16,7 +16,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
namespace Jobba.Web.Sample;
diff --git a/README.md b/README.md
index 0da1c66..308c078 100644
--- a/README.md
+++ b/README.md
@@ -448,3 +448,8 @@ instance can also be wired up as an `IHostedService` (as shown above) to drive i
Implementations must be thread-safe, idempotent across concurrent `WaitAsync` callers, and must observe the
supplied `CancellationToken`.
+## Building from source
+
+- Building requires the .NET SDK version in [`global.json`](global.json) (10.0.401 or a later 10.0.4xx patch). Running the tests also needs the .NET 9 runtime.
+- Target frameworks are set in [`Directory.Build.props`](Directory.Build.props). `FirebendTargetFrameworks` lists the frameworks every library and test project builds for (currently `net9.0;net10.0`). `FirebendAppTargetFramework` is the single framework for samples and other runnable apps.
+- Package versions are managed centrally in [`Directory.Packages.props`](Directory.Packages.props). Microsoft framework packages have one version block per target framework, so adding a framework means adding it to `FirebendTargetFrameworks` and adding a matching block there.
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..f3e8020
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "10.0.401",
+ "rollForward": "latestPatch"
+ }
+}
diff --git a/nuget.config b/nuget.config
new file mode 100644
index 0000000..3e2607e
--- /dev/null
+++ b/nuget.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+