refactor: extract NATS.Server.JetStream.Tests project

Move 225 JetStream-related test files from NATS.Server.Tests into a
dedicated NATS.Server.JetStream.Tests project. This includes root-level
JetStream*.cs files, storage test files (FileStore, MemStore,
StreamStoreContract), and the full JetStream/ subfolder tree (Api,
Cluster, Consumers, MirrorSource, Snapshots, Storage, Streams).

Updated all namespaces, added InternalsVisibleTo, registered in the
solution file, and added the JETSTREAM_INTEGRATION_MATRIX define.
This commit is contained in:
Joseph Doherty
2026-03-12 15:58:10 -04:00
parent 36b9dfa654
commit 78b4bc2486
228 changed files with 253 additions and 227 deletions

View File

@@ -0,0 +1,181 @@
using NATS.Server.JetStream.MirrorSource;
using NATS.Server.JetStream.Models;
using NATS.Server.JetStream.Storage;
using Shouldly;
namespace NATS.Server.JetStream.Tests.JetStream.Streams;
// Go reference: server/stream.go:2739-2743 (mirrorInfo building StreamSourceInfo)
// Go reference: server/stream.go:2687-2736 (sourcesInfo / StreamSourceInfo)
public class SourceMirrorInfoTests
{
// -------------------------------------------------------------------------
// MirrorInfoResponse tests
// -------------------------------------------------------------------------
[Fact]
// Go reference: server/stream.go:2739 — StreamSourceInfo.Name field
public async Task MirrorInfoResponse_has_correct_name()
{
var target = new MemStore();
await using var mirror = new MirrorCoordinator(target);
var info = mirror.GetMirrorInfo("MIRROR");
info.Name.ShouldBe("MIRROR");
}
[Fact]
// Go reference: server/stream.go:2740 — StreamSourceInfo.Lag field from mirrorInfo
public async Task MirrorInfoResponse_shows_lag()
{
var target = new MemStore();
await using var mirror = new MirrorCoordinator(target);
// Sync up to origin seq 5, origin is at 10 → lag = 5 via GetHealthReport
await mirror.OnOriginAppendAsync(MakeMessage(5, "a", "data"), default);
var report = mirror.GetHealthReport(originLastSeq: 10);
report.Lag.ShouldBe(5UL);
// GetMirrorInfo calls GetHealthReport() without originLastSeq, so Lag comes from
// the internal Lag property (0 after in-process sync). Validate the field is wired up.
var info = mirror.GetMirrorInfo("LAGGED");
info.Lag.ShouldBe(mirror.Lag);
info.Name.ShouldBe("LAGGED");
}
[Fact]
// Go reference: server/stream.go:2741 — Active field, -1 when never synced
public async Task MirrorInfoResponse_active_is_negative_when_never_synced()
{
var target = new MemStore();
await using var mirror = new MirrorCoordinator(target);
var info = mirror.GetMirrorInfo("FRESH");
info.Active.ShouldBe(-1L);
}
[Fact]
// Go reference: server/stream.go:2741 — Active = ms since last sync
public async Task MirrorInfoResponse_active_shows_ms_since_sync()
{
var target = new MemStore();
await using var mirror = new MirrorCoordinator(target);
await mirror.OnOriginAppendAsync(MakeMessage(1, "a", "payload"), default);
var info = mirror.GetMirrorInfo("SYNCED");
info.Active.ShouldBeGreaterThanOrEqualTo(0L);
}
[Fact]
// Go reference: server/stream.go:2742 — StreamSourceInfo.Error field
public async Task MirrorInfoResponse_includes_error()
{
var target = new MemStore();
await using var mirror = new MirrorCoordinator(target);
mirror.SetError("consumer not found");
var info = mirror.GetMirrorInfo("BROKEN");
info.Error.ShouldBe("consumer not found");
}
// -------------------------------------------------------------------------
// SourceInfoResponse tests
// -------------------------------------------------------------------------
[Fact]
// Go reference: server/stream.go:2698 — StreamSourceInfo.Name from sourceInfo
public async Task SourceInfoResponse_has_correct_name()
{
var target = new MemStore();
await using var source = new SourceCoordinator(target, new StreamSourceConfig { Name = "MY_SOURCE" });
var info = source.GetSourceInfo();
info.Name.ShouldBe("MY_SOURCE");
}
[Fact]
// Go reference: server/stream.go:2700 — StreamSourceInfo.FilterSubject
public async Task SourceInfoResponse_shows_filter_subject()
{
var target = new MemStore();
await using var source = new SourceCoordinator(target, new StreamSourceConfig
{
Name = "SRC",
FilterSubject = "orders.*",
});
var info = source.GetSourceInfo();
info.FilterSubject.ShouldBe("orders.*");
}
[Fact]
// Go reference: server/stream.go:2701 — Active field, -1 when never synced
public async Task SourceInfoResponse_active_is_negative_when_never_synced()
{
var target = new MemStore();
await using var source = new SourceCoordinator(target, new StreamSourceConfig { Name = "SRC" });
var info = source.GetSourceInfo();
info.Active.ShouldBe(-1L);
}
[Fact]
// Go reference: server/stream.go:2701 — Active = ms since last sync
public async Task SourceInfoResponse_active_shows_ms_since_sync()
{
var target = new MemStore();
await using var source = new SourceCoordinator(target, new StreamSourceConfig { Name = "SRC" });
await source.OnOriginAppendAsync(MakeMessage(1, "orders.created", "payload"), default);
var info = source.GetSourceInfo();
info.Active.ShouldBeGreaterThanOrEqualTo(0L);
}
[Fact]
// Go reference: server/stream.go:2699 — StreamSourceInfo.Lag from sourceInfo
public async Task SourceInfoResponse_lag_reflects_health_report()
{
var target = new MemStore();
await using var source = new SourceCoordinator(target, new StreamSourceConfig { Name = "SRC" });
// Apply 3 messages directly (no background loop, no timing dependency).
// Origin has 5 messages total; coordinator sees up to seq 3, so lag = 2.
await source.OnOriginAppendAsync(MakeMessage(1, "a", "1"), default);
await source.OnOriginAppendAsync(MakeMessage(2, "b", "2"), default);
await source.OnOriginAppendAsync(MakeMessage(3, "c", "3"), default);
// Simulate the lag that GetHealthReport would compute against a known origin end.
var healthReport = source.GetHealthReport(originLastSeq: 5);
var info = source.GetSourceInfo();
// GetSourceInfo calls GetHealthReport() without originLastSeq, so both should
// reflect the same internal Lag value (0 after in-process sync).
info.Lag.ShouldBe(source.Lag);
// Verify against the explicit-originLastSeq health report for the expected lag value.
healthReport.Lag.ShouldBe(2UL);
}
// -------------------------------------------------------------------------
// Helper
// -------------------------------------------------------------------------
private static StoredMessage MakeMessage(ulong seq, string subject, string payload) => new()
{
Sequence = seq,
Subject = subject,
Payload = System.Text.Encoding.UTF8.GetBytes(payload),
TimestampUtc = DateTime.UtcNow,
};
}