refactor: rename remaining tests to NATS.Server.Core.Tests

- Rename tests/NATS.Server.Tests -> tests/NATS.Server.Core.Tests
- Update solution file, InternalsVisibleTo, and csproj references
- Remove JETSTREAM_INTEGRATION_MATRIX and NATS.NKeys from csproj (moved to JetStream.Tests and Auth.Tests)
- Update all namespaces from NATS.Server.Tests.* to NATS.Server.Core.Tests.*
- Replace private GetFreePort/ReadUntilAsync helpers with TestUtilities calls
- Fix stale namespace in Transport.Tests/NetworkingGoParityTests.cs
This commit is contained in:
Joseph Doherty
2026-03-12 16:14:02 -04:00
parent 78b4bc2486
commit 7fbffffd05
114 changed files with 576 additions and 1121 deletions

View File

@@ -0,0 +1,223 @@
// Go reference: server/client.go routeCache, maxResultCacheSize=8192.
using NATS.Server.Subscriptions;
using Shouldly;
namespace NATS.Server.Core.Tests.Subscriptions;
/// <summary>
/// Unit tests for RouteResultCache — the per-account LRU cache that stores
/// SubListResult objects keyed by subject string to avoid repeated SubList.Match() calls.
/// Go reference: server/client.go routeCache field, maxResultCacheSize=8192.
/// </summary>
public class RouteResultCacheTests
{
// -------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------
private static SubListResult MakeResult(string subject)
{
var sub = new Subscription { Subject = subject, Sid = "1" };
return new SubListResult([sub], []);
}
// =========================================================================
// Basic lookup behaviour
// =========================================================================
/// <summary>
/// An empty cache must return false for any subject.
/// </summary>
[Fact]
public void TryGet_returns_false_on_empty_cache()
{
var cache = new RouteResultCache(capacity: 16);
var found = cache.TryGet("foo.bar", out var result);
found.ShouldBeFalse();
result.ShouldBeNull();
}
/// <summary>
/// A result placed with Set must be retrievable via TryGet.
/// </summary>
[Fact]
public void Set_and_TryGet_round_trips_result()
{
var cache = new RouteResultCache(capacity: 16);
var expected = MakeResult("foo");
cache.Set("foo", expected);
var found = cache.TryGet("foo", out var actual);
found.ShouldBeTrue();
actual.ShouldBeSameAs(expected);
}
// =========================================================================
// LRU eviction
// =========================================================================
/// <summary>
/// When a capacity+1 entry is added, the oldest (LRU) entry must be evicted.
/// Go reference: maxResultCacheSize=8192 with LRU eviction.
/// </summary>
[Fact]
public void LRU_eviction_at_capacity()
{
const int cap = 4;
var cache = new RouteResultCache(capacity: cap);
// Fill to capacity — "first" is the oldest (LRU)
cache.Set("first", MakeResult("first"));
cache.Set("second", MakeResult("second"));
cache.Set("third", MakeResult("third"));
cache.Set("fourth", MakeResult("fourth"));
// Adding a 5th entry must evict "first"
cache.Set("fifth", MakeResult("fifth"));
cache.Count.ShouldBe(cap);
cache.TryGet("first", out _).ShouldBeFalse();
cache.TryGet("fifth", out _).ShouldBeTrue();
}
/// <summary>
/// After accessing A, B, C (capacity=3), accessing A promotes it to MRU.
/// The next Set evicts B (now the true LRU) rather than A.
/// </summary>
[Fact]
public void LRU_order_updated_on_access()
{
var cache = new RouteResultCache(capacity: 3);
cache.Set("A", MakeResult("A"));
cache.Set("B", MakeResult("B"));
cache.Set("C", MakeResult("C"));
// Access A so it becomes MRU; now LRU order is: B, A, C (front=C, back=B)
cache.TryGet("A", out _);
// D evicts the LRU entry — should be B, not A
cache.Set("D", MakeResult("D"));
cache.TryGet("A", out _).ShouldBeTrue();
cache.TryGet("B", out _).ShouldBeFalse();
cache.TryGet("C", out _).ShouldBeTrue();
cache.TryGet("D", out _).ShouldBeTrue();
}
// =========================================================================
// Generation / invalidation
// =========================================================================
/// <summary>
/// After Invalidate(), the next TryGet must detect the generation mismatch,
/// clear all entries, and return false.
/// </summary>
[Fact]
public void Invalidate_clears_on_next_access()
{
var cache = new RouteResultCache(capacity: 16);
cache.Set("a", MakeResult("a"));
cache.Set("b", MakeResult("b"));
cache.Invalidate();
cache.TryGet("a", out var result).ShouldBeFalse();
result.ShouldBeNull();
cache.Count.ShouldBe(0);
}
/// <summary>
/// Each call to Invalidate() must increment the generation counter by 1.
/// </summary>
[Fact]
public void Generation_increments_on_invalidate()
{
var cache = new RouteResultCache();
var gen0 = cache.Generation;
cache.Invalidate();
cache.Generation.ShouldBe(gen0 + 1);
cache.Invalidate();
cache.Generation.ShouldBe(gen0 + 2);
}
// =========================================================================
// Statistics
// =========================================================================
/// <summary>
/// Hits counter increments on each successful TryGet.
/// </summary>
[Fact]
public void Hits_incremented_on_cache_hit()
{
var cache = new RouteResultCache(capacity: 16);
cache.Set("subject", MakeResult("subject"));
cache.TryGet("subject", out _);
cache.TryGet("subject", out _);
cache.Hits.ShouldBe(2);
}
/// <summary>
/// Misses counter increments on each failed TryGet.
/// </summary>
[Fact]
public void Misses_incremented_on_cache_miss()
{
var cache = new RouteResultCache(capacity: 16);
cache.TryGet("nope", out _);
cache.TryGet("also.nope", out _);
cache.Misses.ShouldBe(2);
}
// =========================================================================
// Clear
// =========================================================================
/// <summary>
/// Clear() must remove all entries immediately.
/// </summary>
[Fact]
public void Clear_removes_all_entries()
{
var cache = new RouteResultCache(capacity: 16);
cache.Set("x", MakeResult("x"));
cache.Set("y", MakeResult("y"));
cache.Clear();
cache.Count.ShouldBe(0);
cache.TryGet("x", out _).ShouldBeFalse();
}
// =========================================================================
// Update existing entry
// =========================================================================
/// <summary>
/// Setting the same key twice must replace the value; TryGet returns the latest result.
/// </summary>
[Fact]
public void Set_updates_existing_entry()
{
var cache = new RouteResultCache(capacity: 16);
var first = MakeResult("v1");
var second = MakeResult("v2");
cache.Set("key", first);
cache.Set("key", second);
cache.Count.ShouldBe(1);
cache.TryGet("key", out var returned).ShouldBeTrue();
returned.ShouldBeSameAs(second);
}
}