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 b57be4c..0ef5697 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: 8.0.101 + # 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 lit-redis.sln --verify-no-changes diff --git a/.github/workflows/build-test-and-release.yml b/.github/workflows/build-test-and-release.yml index 00251ac..54b7886 100644 --- a/.github/workflows/build-test-and-release.yml +++ b/.github/workflows/build-test-and-release.yml @@ -12,11 +12,18 @@ jobs: steps: - name: Setup 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 @@ -34,12 +41,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: 8.0.101 - - name: Install dependencies if: steps.versionize.outcome == 'success' run: dotnet restore lit-redis.sln diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..5db5b8c --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,15 @@ + + + + + net9.0;net10.0 + + net10.0 + + + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..9a5743e --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,44 @@ + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LitRedis.Core/Implementations/LitRedisCacheStore.cs b/LitRedis.Core/Implementations/LitRedisCacheStore.cs index 8673be0..bd0d301 100644 --- a/LitRedis.Core/Implementations/LitRedisCacheStore.cs +++ b/LitRedis.Core/Implementations/LitRedisCacheStore.cs @@ -6,6 +6,7 @@ using LitRedis.Core.Interfaces; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Primitives; +using StackExchange.Redis; namespace LitRedis.Core.Implementations; @@ -48,7 +49,7 @@ public async Task PutAsync(string key, T model, TimeSpan? expiry, Cancellatio var json = _jsonSerializer.Serialize(model); - await _litRedisConnectionService.UseDbAsync((db, _) => db.StringSetAsync(key, json, expiry), cancellationToken); + await _litRedisConnectionService.UseDbAsync((db, _) => db.StringSetAsync(key, json, expiry.HasValue ? new Expiration(expiry.Value) : Expiration.Default), cancellationToken); } /// diff --git a/LitRedis.Core/Interfaces/ILitRedisCacheStore.cs b/LitRedis.Core/Interfaces/ILitRedisCacheStore.cs index a5102b9..a679b14 100644 --- a/LitRedis.Core/Interfaces/ILitRedisCacheStore.cs +++ b/LitRedis.Core/Interfaces/ILitRedisCacheStore.cs @@ -7,17 +7,17 @@ namespace LitRedis.Core.Interfaces; public interface ILitRedisCacheStore { - Task PutAsync(string key, T model, TimeSpan? expiry, CancellationToken cancellationToken); + public Task PutAsync(string key, T model, TimeSpan? expiry, CancellationToken cancellationToken); - Task GetAsync(string key, CancellationToken cancellationToken); + public Task GetAsync(string key, CancellationToken cancellationToken); - Task GetAsync(string key, CancellationToken cancellationToken); + public Task GetAsync(string key, CancellationToken cancellationToken); - Task ClearAsync(string key, CancellationToken cancellationToken); + public Task ClearAsync(string key, CancellationToken cancellationToken); - Task> GetAllKeys(CancellationToken cancellationToken); + public Task> GetAllKeys(CancellationToken cancellationToken); - Task ClearAllAsync(CancellationToken cancellationToken); + public Task ClearAllAsync(CancellationToken cancellationToken); - Task SetExpiryAsync(string key, TimeSpan span, CancellationToken cancellationToken); + public Task SetExpiryAsync(string key, TimeSpan span, CancellationToken cancellationToken); } diff --git a/LitRedis.Core/Interfaces/ILitRedisConnection.cs b/LitRedis.Core/Interfaces/ILitRedisConnection.cs index c0ab22a..b83ee66 100644 --- a/LitRedis.Core/Interfaces/ILitRedisConnection.cs +++ b/LitRedis.Core/Interfaces/ILitRedisConnection.cs @@ -5,7 +5,7 @@ namespace LitRedis.Core.Interfaces; public interface ILitRedisConnection { - void ForceReconnect(); + public void ForceReconnect(); - Task GetConnectionMultiplexer(); + public Task GetConnectionMultiplexer(); } diff --git a/LitRedis.Core/Interfaces/ILitRedisConnectionService.cs b/LitRedis.Core/Interfaces/ILitRedisConnectionService.cs index 874e8bd..e6cc464 100644 --- a/LitRedis.Core/Interfaces/ILitRedisConnectionService.cs +++ b/LitRedis.Core/Interfaces/ILitRedisConnectionService.cs @@ -7,9 +7,9 @@ namespace LitRedis.Core.Interfaces; public interface ILitRedisConnectionService { - Task UseDbAsync(Func> fn, CancellationToken cancellationToken); + public Task UseDbAsync(Func> fn, CancellationToken cancellationToken); - Task UseServerAsync(Func> fn, CancellationToken cancellationToken); + public Task UseServerAsync(Func> fn, CancellationToken cancellationToken); - Task UseServerAsync(Func fn, CancellationToken cancellationToken); + public Task UseServerAsync(Func fn, CancellationToken cancellationToken); } diff --git a/LitRedis.Core/Interfaces/ILitRedisDistributedLock.cs b/LitRedis.Core/Interfaces/ILitRedisDistributedLock.cs index 38cc352..1f7a6a5 100644 --- a/LitRedis.Core/Interfaces/ILitRedisDistributedLock.cs +++ b/LitRedis.Core/Interfaces/ILitRedisDistributedLock.cs @@ -6,18 +6,18 @@ namespace LitRedis.Core.Interfaces; public interface ILitRedisDistributedLock { - Task TakeLockAsync( + public Task TakeLockAsync( string key, string token, TimeSpan expiryTime, CancellationToken cancellationToken); - Task ReleaseLockAsync( + public Task ReleaseLockAsync( string key, string token, CancellationToken cancellationToken); - Task ExtendLockAsync( + public Task ExtendLockAsync( string key, string token, TimeSpan expiryTime, diff --git a/LitRedis.Core/Interfaces/ILitRedisDistributedLockService.cs b/LitRedis.Core/Interfaces/ILitRedisDistributedLockService.cs index 3feaacb..faa2b3a 100644 --- a/LitRedis.Core/Interfaces/ILitRedisDistributedLockService.cs +++ b/LitRedis.Core/Interfaces/ILitRedisDistributedLockService.cs @@ -6,5 +6,5 @@ namespace LitRedis.Core.Interfaces; public interface ILitRedisDistributedLockService { - Task AcquireLockAsync(RequestLockModel model, CancellationToken cancellationToken); + public Task AcquireLockAsync(RequestLockModel model, CancellationToken cancellationToken); } diff --git a/LitRedis.Core/Interfaces/ILitRedisJsonSerializer.cs b/LitRedis.Core/Interfaces/ILitRedisJsonSerializer.cs index ff37d22..87466f4 100644 --- a/LitRedis.Core/Interfaces/ILitRedisJsonSerializer.cs +++ b/LitRedis.Core/Interfaces/ILitRedisJsonSerializer.cs @@ -2,7 +2,7 @@ namespace LitRedis.Core.Interfaces; public interface ILitRedisJsonSerializer { - string Serialize(T value); + public string Serialize(T value); - T Deserialize(string value); + public T Deserialize(string value); } diff --git a/LitRedis.Core/Interfaces/ILitRedisSystemTextJsonOptionsProvider.cs b/LitRedis.Core/Interfaces/ILitRedisSystemTextJsonOptionsProvider.cs index 27dbfeb..dec0529 100644 --- a/LitRedis.Core/Interfaces/ILitRedisSystemTextJsonOptionsProvider.cs +++ b/LitRedis.Core/Interfaces/ILitRedisSystemTextJsonOptionsProvider.cs @@ -4,5 +4,5 @@ namespace LitRedis.Core.Interfaces; public interface ILitRedisSystemTextJsonOptionsProvider { - JsonSerializerOptions GetOptions(); + public JsonSerializerOptions GetOptions(); } diff --git a/LitRedis.Core/LitRedis.Core.csproj b/LitRedis.Core/LitRedis.Core.csproj index 2ddfa57..6456189 100644 --- a/LitRedis.Core/LitRedis.Core.csproj +++ b/LitRedis.Core/LitRedis.Core.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + $(FirebendTargetFrameworks) 5.0.0 latestmajor 4 @@ -13,17 +13,16 @@ firebend;redis;cache;caching;lock;distributed;locking Firebend https://github.com/firebend/lit-redis - https://github.com/firebend/lit-redis/blob/main/LICENSE + MIT https://github.com/firebend/lit-redis - https://github.com/firebend/lit-redis/blob/main/nuget-image.png?raw=true + nuget-image.png - - - - - + + + + diff --git a/LitRedis.Sample/LitRedis.Sample.csproj b/LitRedis.Sample/LitRedis.Sample.csproj index 5435458..ad71dec 100644 --- a/LitRedis.Sample/LitRedis.Sample.csproj +++ b/LitRedis.Sample/LitRedis.Sample.csproj @@ -2,13 +2,11 @@ Exe - net8.0 + $(FirebendAppTargetFramework) - - - + diff --git a/LitRedis.Tests/Core/Implementations/LitRedisCacheStoreTests.cs b/LitRedis.Tests/Core/Implementations/LitRedisCacheStoreTests.cs index c768ca4..7d11b9e 100644 --- a/LitRedis.Tests/Core/Implementations/LitRedisCacheStoreTests.cs +++ b/LitRedis.Tests/Core/Implementations/LitRedisCacheStoreTests.cs @@ -100,4 +100,59 @@ public async Task Lit_Redis_Cache_Store_Should_Put_Value_Object() mockRedisConnectionService.Verify(x => x.UseDbAsync(It.IsAny>>(), It.IsAny()), Times.Once); } + + [TestMethod] + public async Task Lit_Redis_Cache_Store_Should_Put_Without_Expiry_When_Expiry_Is_Null() + { + //act + var expiration = await PutAndCaptureExpirationAsync(null); + + //assert + // Expiration.Default sends a plain SET, which clears any existing TTL (it is not KEEPTTL). + expiration.Should().Be(Expiration.Default); + expiration.Should().NotBe(Expiration.KeepTtl); + } + + [TestMethod] + public async Task Lit_Redis_Cache_Store_Should_Put_With_Relative_Expiry_When_Expiry_Is_Set() + { + //arrange + var expiry = TimeSpan.FromMinutes(5); + + //act + var expiration = await PutAndCaptureExpirationAsync(expiry); + + //assert + expiration.Should().Be(new Expiration(expiry)); + } + + private static async Task PutAndCaptureExpirationAsync(TimeSpan? expiry) + { + var fixture = new Fixture(); + fixture.Customize(new AutoMoqCustomization()); + + var mockRedisConnectionService = fixture.Freeze>(); + var mockDatabase = new Mock(); + Expiration? captured = null; + + mockDatabase.Setup(x => x.StringSetAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((_, _, expiration, _, _) => captured = expiration) + .ReturnsAsync(true); + + mockRedisConnectionService.Setup(x => + x.UseDbAsync(It.IsAny>>(), It.IsAny())) + .Returns>, CancellationToken>((func, ct) => func(mockDatabase.Object, ct)); + + var cacheStore = fixture.Create(); + + await cacheStore.PutAsync("fake-key", new FakeCacheObject { Value = "Fakey Fake" }, expiry, default); + + mockDatabase.Verify(x => x.StringSetAsync( + "fake-key", It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + + captured.Should().NotBeNull(); + + return captured.Value; + } } diff --git a/LitRedis.Tests/LitRedis.Tests.csproj b/LitRedis.Tests/LitRedis.Tests.csproj index 05780dd..358fad4 100644 --- a/LitRedis.Tests/LitRedis.Tests.csproj +++ b/LitRedis.Tests/LitRedis.Tests.csproj @@ -1,26 +1,25 @@ - net8.0 + $(FirebendTargetFrameworks) false - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - diff --git a/README.md b/README.md index 5df454e..6ba3f98 100644 --- a/README.md +++ b/README.md @@ -251,3 +251,9 @@ When the lock is lost, the release callback will _not_ attempt to release the ke If the background renewal loop repeatedly fails to extend the lock the library will mark the lock as lost. The number of consecutive extension failures tolerated before marking the lock lost is configurable via `RequestLockModel.MaxExtendRetries` (default: 3). You can set it fluently with `WithMaxExtendRetries(int)` on the request model. When a lock is marked lost the `Status` becomes `Lost` and `LockLostToken` will be cancelled so callers can react promptly. + +## 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..c6ad81a --- /dev/null +++ b/nuget.config @@ -0,0 +1,8 @@ + + + + + + + +