feat(p7-09): JetStream unit tests — versioning (12), dirstore (12), batching/errors deferred (66)

Port session P7-09: add tests from jetstream_versioning_test.go (T:1791–1808),
dirstore_test.go (T:285–296), jetstream_batching_test.go (T:716–744),
jetstream_errors_test.go (T:1381–1384), and accounts_test.go (T:80–110).

- JetStreamVersioningTests: 12 active unit tests + 6 deferred (server-required)
- DirectoryStoreTests: 12 filesystem tests using fake JWTs (no NKeys dependency)
- JetStreamBatchingTests: 29 deferred stubs (all require running JetStream cluster)
- JetStreamErrorsTests: 4 deferred stubs (NewJS* factories not yet ported)
- accounts_test.go T:80–110: 31 deferred (all use RunServerWithConfig)

Fix DirJwtStore.cs expiration bugs:
  - Use DateTimeOffset.UtcNow.UtcTicks (not Unix-relative ticks) for expiry comparison
  - Replace in-place JwtItem mutation with new-object replacement so DrainStale
    can detect stale heap entries via ReferenceEquals check

Add JetStreamVersioning.cs methods: SetStaticStreamMetadata,
SetDynamicStreamMetadata, CopyStreamMetadata, SetStaticConsumerMetadata,
SetDynamicConsumerMetadata, SetDynamicConsumerInfoMetadata, CopyConsumerMetadata.

Tests: 725 pass, 53 skipped/deferred, 0 failures.
DB: +24 complete, +66 deferred.
This commit is contained in:
Joseph Doherty
2026-02-26 20:02:00 -05:00
parent 6e90eea736
commit f0faaffe69
9 changed files with 1627 additions and 18 deletions

View File

@@ -624,7 +624,7 @@ public sealed class DirJwtStore : IDisposable
/// Deletes the JWT for <paramref name="publicKey"/> according to <see cref="_deleteType"/>.
/// Mirrors Go <c>DirJWTStore.delete</c>.
/// </summary>
private void Delete(string publicKey)
public void Delete(string publicKey)
{
if (_readonly)
{
@@ -795,7 +795,7 @@ public sealed class DirJwtStore : IDisposable
// Background timer — mirrors Go goroutine + time.Ticker.
var timer = new Timer(_ =>
{
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * TimeSpan.TicksPerMillisecond;
var now = DateTimeOffset.UtcNow.UtcTicks;
while (true)
{
@@ -1104,14 +1104,13 @@ internal sealed class ExpirationTracker
// Remove old hash contribution from rolling XOR.
XorAssign(_hash, existing.Hash);
// Update in-place.
existing.Expiration = exp;
existing.Hash = hash;
// Re-enqueue with updated priority (PriorityQueue does not support update;
// use a version counter approach — mark old entry stale, enqueue fresh).
existing.Version++;
_heap.Enqueue(existing, exp);
// Create a new JwtItem so the old heap entry becomes a stale orphan.
// DrainStale uses ReferenceEquals(current, top) to detect orphans:
// the old heap entry points to the old JwtItem object which is no longer
// in _idx, so it will be drained on the next PeekExpired call.
var updated = new JwtItem(publicKey, exp, hash);
_idx[publicKey] = updated;
_heap.Enqueue(updated, exp);
}
else
{
@@ -1141,9 +1140,11 @@ internal sealed class ExpirationTracker
? long.MaxValue
: (DateTimeOffset.UtcNow + Ttl).UtcTicks;
item.Expiration = newExp;
item.Version++;
_heap.Enqueue(item, newExp);
// Replace with a new JwtItem so the old heap entry becomes a stale orphan
// (DrainStale detects staleness via ReferenceEquals).
var updated = new JwtItem(publicKey, newExp, item.Hash);
_idx[publicKey] = updated;
_heap.Enqueue(updated, newExp);
}
if (EvictOnLimit)