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 @@ + + + + + + + +