Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions src/NatsDistributedCache/NatsCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ public class CacheEntry
public byte[]? Data { get; set; }
}

/// <summary>
/// JsonSerializerContext for CacheEntry
/// </summary>
[JsonSerializable(typeof(CacheEntry))]
public partial class CacheEntryJsonContext : JsonSerializerContext
{
}

/// <summary>
/// Distributed cache implementation using NATS Key-Value Store.
/// </summary>
Expand Down Expand Up @@ -364,8 +372,3 @@ await kvStore.DeleteAsync(GetPrefixedKey(key), natsKvDeleteOpts, cancellationTok
.ConfigureAwait(false);
}
}

[JsonSerializable(typeof(CacheEntry))]
internal partial class CacheEntryJsonContext : JsonSerializerContext
{
}
89 changes: 89 additions & 0 deletions test/IntegrationTests/Cache/HybridCacheSetAndRemoveTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System.Buffers;
using System.Globalization;
using System.Text;
using Microsoft.Extensions.Caching.Hybrid;
using NATS.Client.Core;
using NATS.Client.KeyValueStore;
using NATS.Net;
using StreamJsonRpc;

namespace CodeCargo.Nats.DistributedCache.IntegrationTests.Cache;

public class HybridCacheGetSetRemoveTests(NatsIntegrationFixture fixture) : TestBase(fixture)
{
[Fact]
public async Task HybridCacheGetSetRemoveTest()
{
// Arrange
var key = MethodKey();
var value = Encoding.UTF8.GetBytes($"test-value-{Guid.NewGuid()}");
var options = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(10)
};

// Assert - Verify the value is not stored in NATS KV store
var kvStore = await NatsConnection.CreateKeyValueStoreContext().GetStoreAsync("cache");
await Assert.ThrowsAsync<NatsKVKeyNotFoundException>(async () => await kvStore.GetEntryAsync<byte[]>(key));

// Act
await HybridCache.SetAsync(key, value, options);

// Assert - Verify the value is stored in NATS KV store
var kvEntry = await kvStore.GetEntryAsync<byte[]>(key);
Assert.NotNull(kvEntry.Value);

// Assert - Verify the value is retrievable from hybrid cache
var result = await HybridCache.GetOrCreateAsync(key, async ct => await Task.FromResult(Array.Empty<byte>()));
Assert.NotEmpty(result);
Assert.Equal(value, result);

// Act
await HybridCache.RemoveAsync(key);

// Assert - Verify the value is not stored in NATS KV store
await Assert.ThrowsAsync<NatsKVKeyDeletedException>(async () => await kvStore.GetEntryAsync<byte[]>(key));

// Assert - Verify the value is not retrievable from hybrid cache
result = await HybridCache.GetOrCreateAsync(key, async ct => await Task.FromResult(Array.Empty<byte>()));
Assert.Empty(result);
}

[Fact]
public async Task HybridCacheSerializesDateTime()
{
// Arrange
var key = MethodKey();
const string invariant = "2025-05-15T17:18:58.7503097Z";
var date = DateTime.Parse(invariant, CultureInfo.InvariantCulture);

var options = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(10)
};

// Act - Store the complex object in the cache
await HybridCache.SetAsync(key, date, options);

// Assert - date is serialized as expected
var writer = new ArrayBufferWriter<byte>();
new NatsUtf8PrimitivesSerializer<DateTime>().Serialize(writer, date);
var serializedDateString = Encoding.ASCII.GetString(writer.WrittenSpan.ToArray());

var kvStore = await NatsConnection.CreateKeyValueStoreContext().GetStoreAsync("cache");
NatsJsonContextSerializer<CacheEntry> cacheEntrySerializer = new(CacheEntryJsonContext.Default);
var kvEntry = await kvStore.GetEntryAsync(key, serializer: cacheEntrySerializer);
Assert.NotNull(kvEntry.Value?.Data);
var storedDateString = Encoding.ASCII.GetString(kvEntry.Value.Data);

// HybridCache adds additional data to the front of the serialized value, so we're matching only the relevant data
Assert.Contains(serializedDateString, storedDateString);

// Assert - date is deserialized as expected
var retrieved = await HybridCache.GetOrCreateAsync(key, async ct => await Task.FromResult(DateTime.UnixEpoch));
Assert.Equal(date, retrieved);

// Cleanup
await HybridCache.RemoveAsync(key);
}
}
9 changes: 9 additions & 0 deletions test/IntegrationTests/TestBase.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System.Runtime.CompilerServices;
using CodeCargo.Nats.DistributedCache.TestUtils;
using CodeCargo.Nats.DistributedCache.TestUtils.Services.Logging;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Hybrid;
using Microsoft.Extensions.Logging;
using NATS.Client.Core;
using NATS.Client.JetStream.Models;
using NATS.Client.KeyValueStore;
using NATS.Net;

namespace CodeCargo.Nats.DistributedCache.IntegrationTests;
Expand Down Expand Up @@ -41,6 +44,7 @@ protected TestBase(NatsIntegrationFixture fixture)

// Add the cache
services.AddNatsDistributedCache(options => options.BucketName = "cache");
services.AddHybridCacheTestClient();

// Build service provider
ServiceProvider = services.BuildServiceProvider();
Expand All @@ -61,6 +65,11 @@ protected TestBase(NatsIntegrationFixture fixture)
/// </summary>
protected IDistributedCache Cache => ServiceProvider.GetRequiredService<IDistributedCache>();

/// <summary>
/// Gets the cache from the service provider
/// </summary>
protected HybridCache HybridCache => ServiceProvider.GetRequiredService<HybridCache>();

/// <summary>
/// Purge stream before test run
/// </summary>
Expand Down
24 changes: 24 additions & 0 deletions test/IntegrationTests/packages.linux-x64.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,29 @@
"Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.Caching.Hybrid": {
"type": "Transitive",
"resolved": "9.4.0",
"contentHash": "GtHP+DRraRM6RLq7TzUV8Iiyqm+WemJRLDNqy7uvA+Dgf6fjvxpmHnzgPb+RAcRNjADz961DMHHZ4i5EQjpDPw==",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "9.0.4",
"Microsoft.Extensions.Caching.Memory": "9.0.4",
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
"Microsoft.Extensions.Options": "9.0.4"
}
},
"Microsoft.Extensions.Caching.Memory": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "G5rEq1Qez5VJDTEyRsRUnewAspKjaY57VGsdZ8g8Ja6sXXzoiI3PpTd1t43HjHqNWD5A06MQveb2lscn+2CU+w==",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "9.0.4",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
"Microsoft.Extensions.Options": "9.0.4",
"Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.Compliance.Abstractions": {
"type": "Transitive",
"resolved": "9.4.0",
Expand Down Expand Up @@ -1074,6 +1097,7 @@
"type": "Project",
"dependencies": {
"CodeCargo.Nats.DistributedCache": "[1.0.0, )",
"Microsoft.Extensions.Caching.Hybrid": "[9.4.0, )",
"Microsoft.Extensions.Logging": "[9.0.4, )",
"NATS.Net": "[2.6.0, )",
"xunit.v3.assert": "[2.0.2, )",
Expand Down
24 changes: 24 additions & 0 deletions test/IntegrationTests/packages.osx-arm64.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,29 @@
"Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.Caching.Hybrid": {
"type": "Transitive",
"resolved": "9.4.0",
"contentHash": "GtHP+DRraRM6RLq7TzUV8Iiyqm+WemJRLDNqy7uvA+Dgf6fjvxpmHnzgPb+RAcRNjADz961DMHHZ4i5EQjpDPw==",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "9.0.4",
"Microsoft.Extensions.Caching.Memory": "9.0.4",
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
"Microsoft.Extensions.Options": "9.0.4"
}
},
"Microsoft.Extensions.Caching.Memory": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "G5rEq1Qez5VJDTEyRsRUnewAspKjaY57VGsdZ8g8Ja6sXXzoiI3PpTd1t43HjHqNWD5A06MQveb2lscn+2CU+w==",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "9.0.4",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
"Microsoft.Extensions.Options": "9.0.4",
"Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.Compliance.Abstractions": {
"type": "Transitive",
"resolved": "9.4.0",
Expand Down Expand Up @@ -1074,6 +1097,7 @@
"type": "Project",
"dependencies": {
"CodeCargo.Nats.DistributedCache": "[1.0.0, )",
"Microsoft.Extensions.Caching.Hybrid": "[9.4.0, )",
"Microsoft.Extensions.Logging": "[9.0.4, )",
"NATS.Net": "[2.6.0, )",
"xunit.v3.assert": "[2.0.2, )",
Expand Down
24 changes: 24 additions & 0 deletions test/IntegrationTests/packages.win-x64.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,29 @@
"Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.Caching.Hybrid": {
"type": "Transitive",
"resolved": "9.4.0",
"contentHash": "GtHP+DRraRM6RLq7TzUV8Iiyqm+WemJRLDNqy7uvA+Dgf6fjvxpmHnzgPb+RAcRNjADz961DMHHZ4i5EQjpDPw==",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "9.0.4",
"Microsoft.Extensions.Caching.Memory": "9.0.4",
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
"Microsoft.Extensions.Options": "9.0.4"
}
},
"Microsoft.Extensions.Caching.Memory": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "G5rEq1Qez5VJDTEyRsRUnewAspKjaY57VGsdZ8g8Ja6sXXzoiI3PpTd1t43HjHqNWD5A06MQveb2lscn+2CU+w==",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "9.0.4",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
"Microsoft.Extensions.Options": "9.0.4",
"Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.Compliance.Abstractions": {
"type": "Transitive",
"resolved": "9.4.0",
Expand Down Expand Up @@ -1074,6 +1097,7 @@
"type": "Project",
"dependencies": {
"CodeCargo.Nats.DistributedCache": "[1.0.0, )",
"Microsoft.Extensions.Caching.Hybrid": "[9.4.0, )",
"Microsoft.Extensions.Logging": "[9.0.4, )",
"NATS.Net": "[2.6.0, )",
"xunit.v3.assert": "[2.0.2, )",
Expand Down
12 changes: 12 additions & 0 deletions test/TestUtils/NatsTestExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,16 @@ options with
Url = natsConnectionString,
RequestReplyMode = NatsRequestReplyMode.Direct,
});

public static IServiceCollection AddHybridCacheTestClient(this IServiceCollection services)
{
// Add HybridCache
var hybridCacheServices = services.AddHybridCache();

// Use NATS Serializer for HybridCache
var natsOpts = NatsOpts.Default;
hybridCacheServices.AddSerializerFactory(
natsOpts.SerializerRegistry.ToHybridCacheSerializerFactory());
return services;
}
}
1 change: 1 addition & 0 deletions test/TestUtils/TestUtils.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.4.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.4" />
<PackageReference Include="NATS.Net" Version="2.6.0" />
<PackageReference Include="xunit.v3.assert" Version="2.0.2" />
Expand Down
24 changes: 24 additions & 0 deletions test/TestUtils/packages.linux-x64.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@
"version": 1,
"dependencies": {
"net8.0": {
"Microsoft.Extensions.Caching.Hybrid": {
"type": "Direct",
"requested": "[9.4.0, )",
"resolved": "9.4.0",
"contentHash": "GtHP+DRraRM6RLq7TzUV8Iiyqm+WemJRLDNqy7uvA+Dgf6fjvxpmHnzgPb+RAcRNjADz961DMHHZ4i5EQjpDPw==",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "9.0.4",
"Microsoft.Extensions.Caching.Memory": "9.0.4",
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
"Microsoft.Extensions.Options": "9.0.4"
}
},
"Microsoft.Extensions.Logging": {
"type": "Direct",
"requested": "[9.0.4, )",
Expand Down Expand Up @@ -66,6 +78,18 @@
"Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.Caching.Memory": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "G5rEq1Qez5VJDTEyRsRUnewAspKjaY57VGsdZ8g8Ja6sXXzoiI3PpTd1t43HjHqNWD5A06MQveb2lscn+2CU+w==",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "9.0.4",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
"Microsoft.Extensions.Options": "9.0.4",
"Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "8.0.0",
Expand Down
24 changes: 24 additions & 0 deletions test/TestUtils/packages.osx-arm64.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@
"version": 1,
"dependencies": {
"net8.0": {
"Microsoft.Extensions.Caching.Hybrid": {
"type": "Direct",
"requested": "[9.4.0, )",
"resolved": "9.4.0",
"contentHash": "GtHP+DRraRM6RLq7TzUV8Iiyqm+WemJRLDNqy7uvA+Dgf6fjvxpmHnzgPb+RAcRNjADz961DMHHZ4i5EQjpDPw==",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "9.0.4",
"Microsoft.Extensions.Caching.Memory": "9.0.4",
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
"Microsoft.Extensions.Options": "9.0.4"
}
},
"Microsoft.Extensions.Logging": {
"type": "Direct",
"requested": "[9.0.4, )",
Expand Down Expand Up @@ -66,6 +78,18 @@
"Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.Caching.Memory": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "G5rEq1Qez5VJDTEyRsRUnewAspKjaY57VGsdZ8g8Ja6sXXzoiI3PpTd1t43HjHqNWD5A06MQveb2lscn+2CU+w==",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "9.0.4",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
"Microsoft.Extensions.Options": "9.0.4",
"Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "8.0.0",
Expand Down
24 changes: 24 additions & 0 deletions test/TestUtils/packages.win-x64.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@
"version": 1,
"dependencies": {
"net8.0": {
"Microsoft.Extensions.Caching.Hybrid": {
"type": "Direct",
"requested": "[9.4.0, )",
"resolved": "9.4.0",
"contentHash": "GtHP+DRraRM6RLq7TzUV8Iiyqm+WemJRLDNqy7uvA+Dgf6fjvxpmHnzgPb+RAcRNjADz961DMHHZ4i5EQjpDPw==",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "9.0.4",
"Microsoft.Extensions.Caching.Memory": "9.0.4",
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
"Microsoft.Extensions.Options": "9.0.4"
}
},
"Microsoft.Extensions.Logging": {
"type": "Direct",
"requested": "[9.0.4, )",
Expand Down Expand Up @@ -66,6 +78,18 @@
"Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.Caching.Memory": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "G5rEq1Qez5VJDTEyRsRUnewAspKjaY57VGsdZ8g8Ja6sXXzoiI3PpTd1t43HjHqNWD5A06MQveb2lscn+2CU+w==",
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "9.0.4",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
"Microsoft.Extensions.Options": "9.0.4",
"Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "8.0.0",
Expand Down
Loading