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:
@@ -0,0 +1,361 @@
|
||||
// Reference: golang/nats-server/server/memstore_test.go and filestore_test.go
|
||||
// Tests ported from: TestMemStoreBasics, TestMemStorePurge, TestMemStoreMsgHeaders,
|
||||
// TestMemStoreTimeStamps, TestMemStoreEraseMsg,
|
||||
// TestMemStoreMsgLimit, TestMemStoreBytesLimit,
|
||||
// TestMemStoreAgeLimit, plus parity tests matching
|
||||
// filestore behavior in MemStore.
|
||||
|
||||
using System.Text;
|
||||
using NATS.Server.JetStream.Storage;
|
||||
|
||||
namespace NATS.Server.JetStream.Tests.JetStream.Storage;
|
||||
|
||||
public sealed class MemStoreTests
|
||||
{
|
||||
// Go: TestMemStoreBasics server/memstore_test.go
|
||||
[Fact]
|
||||
public async Task Store_and_load_messages()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
var seq1 = await store.AppendAsync("foo", "Hello World"u8.ToArray(), default);
|
||||
var seq2 = await store.AppendAsync("bar", "Second"u8.ToArray(), default);
|
||||
|
||||
seq1.ShouldBe((ulong)1);
|
||||
seq2.ShouldBe((ulong)2);
|
||||
|
||||
var state = await store.GetStateAsync(default);
|
||||
state.Messages.ShouldBe((ulong)2);
|
||||
state.FirstSeq.ShouldBe((ulong)1);
|
||||
state.LastSeq.ShouldBe((ulong)2);
|
||||
|
||||
var msg1 = await store.LoadAsync(1, default);
|
||||
msg1.ShouldNotBeNull();
|
||||
msg1!.Subject.ShouldBe("foo");
|
||||
msg1.Payload.ToArray().ShouldBe("Hello World"u8.ToArray());
|
||||
|
||||
var msg2 = await store.LoadAsync(2, default);
|
||||
msg2.ShouldNotBeNull();
|
||||
msg2!.Subject.ShouldBe("bar");
|
||||
}
|
||||
|
||||
// Go: TestMemStoreBasics server/memstore_test.go
|
||||
[Fact]
|
||||
public async Task Load_non_existent_returns_null()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
await store.AppendAsync("foo", "data"u8.ToArray(), default);
|
||||
|
||||
(await store.LoadAsync(99, default)).ShouldBeNull();
|
||||
(await store.LoadAsync(0, default)).ShouldBeNull();
|
||||
}
|
||||
|
||||
// Go: TestMemStoreEraseMsg server/memstore_test.go
|
||||
[Fact]
|
||||
public async Task Remove_messages()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
await store.AppendAsync("foo", Encoding.UTF8.GetBytes($"msg-{i}"), default);
|
||||
|
||||
(await store.RemoveAsync(2, default)).ShouldBeTrue();
|
||||
(await store.RemoveAsync(4, default)).ShouldBeTrue();
|
||||
|
||||
var state = await store.GetStateAsync(default);
|
||||
state.Messages.ShouldBe((ulong)3);
|
||||
|
||||
(await store.LoadAsync(2, default)).ShouldBeNull();
|
||||
(await store.LoadAsync(4, default)).ShouldBeNull();
|
||||
(await store.LoadAsync(1, default)).ShouldNotBeNull();
|
||||
(await store.LoadAsync(3, default)).ShouldNotBeNull();
|
||||
(await store.LoadAsync(5, default)).ShouldNotBeNull();
|
||||
}
|
||||
|
||||
// Go: TestMemStoreEraseMsg server/memstore_test.go
|
||||
[Fact]
|
||||
public async Task Remove_non_existent_returns_false()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
await store.AppendAsync("foo", "data"u8.ToArray(), default);
|
||||
|
||||
(await store.RemoveAsync(99, default)).ShouldBeFalse();
|
||||
}
|
||||
|
||||
// Go: TestMemStorePurge server/memstore_test.go
|
||||
[Fact]
|
||||
public async Task Purge_clears_all()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
await store.AppendAsync("foo", "data"u8.ToArray(), default);
|
||||
|
||||
(await store.GetStateAsync(default)).Messages.ShouldBe((ulong)10);
|
||||
|
||||
await store.PurgeAsync(default);
|
||||
|
||||
var state = await store.GetStateAsync(default);
|
||||
state.Messages.ShouldBe((ulong)0);
|
||||
state.Bytes.ShouldBe((ulong)0);
|
||||
}
|
||||
|
||||
// Go: TestMemStorePurge server/memstore_test.go
|
||||
[Fact]
|
||||
public async Task Purge_empty_store_is_safe()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
await store.PurgeAsync(default);
|
||||
|
||||
(await store.GetStateAsync(default)).Messages.ShouldBe((ulong)0);
|
||||
}
|
||||
|
||||
// Go: TestMemStoreTimeStamps server/memstore_test.go
|
||||
[Fact]
|
||||
public async Task Timestamps_non_decreasing()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
await store.AppendAsync("foo", "data"u8.ToArray(), default);
|
||||
|
||||
var messages = await store.ListAsync(default);
|
||||
messages.Count.ShouldBe(10);
|
||||
|
||||
DateTime? prev = null;
|
||||
foreach (var msg in messages)
|
||||
{
|
||||
if (prev.HasValue)
|
||||
msg.TimestampUtc.ShouldBeGreaterThanOrEqualTo(prev.Value);
|
||||
prev = msg.TimestampUtc;
|
||||
}
|
||||
}
|
||||
|
||||
// Go: TestMemStoreMsgHeaders (adapted) server/memstore_test.go
|
||||
[Fact]
|
||||
public async Task Payload_with_header_bytes_round_trips()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
var headerBytes = "NATS/1.0\r\nName: derek\r\n\r\n"u8.ToArray();
|
||||
var bodyBytes = "Hello World"u8.ToArray();
|
||||
byte[] combined = [.. headerBytes, .. bodyBytes];
|
||||
|
||||
await store.AppendAsync("foo", combined, default);
|
||||
|
||||
var msg = await store.LoadAsync(1, default);
|
||||
msg.ShouldNotBeNull();
|
||||
msg!.Payload.ToArray().ShouldBe(combined);
|
||||
}
|
||||
|
||||
// Go: TestMemStoreBasics server/memstore_test.go
|
||||
[Fact]
|
||||
public async Task LoadLastBySubject_returns_most_recent()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
await store.AppendAsync("foo", "first"u8.ToArray(), default);
|
||||
await store.AppendAsync("bar", "other"u8.ToArray(), default);
|
||||
await store.AppendAsync("foo", "second"u8.ToArray(), default);
|
||||
await store.AppendAsync("foo", "third"u8.ToArray(), default);
|
||||
|
||||
var last = await store.LoadLastBySubjectAsync("foo", default);
|
||||
last.ShouldNotBeNull();
|
||||
last!.Payload.ToArray().ShouldBe("third"u8.ToArray());
|
||||
last.Sequence.ShouldBe((ulong)4);
|
||||
|
||||
(await store.LoadLastBySubjectAsync("does.not.exist", default)).ShouldBeNull();
|
||||
}
|
||||
|
||||
// Go: TestMemStoreMsgLimit server/memstore_test.go
|
||||
[Fact]
|
||||
public async Task TrimToMaxMessages_evicts_oldest()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
for (var i = 0; i < 20; i++)
|
||||
await store.AppendAsync("foo", Encoding.UTF8.GetBytes($"msg-{i}"), default);
|
||||
|
||||
store.TrimToMaxMessages(10);
|
||||
|
||||
var state = await store.GetStateAsync(default);
|
||||
state.Messages.ShouldBe((ulong)10);
|
||||
state.FirstSeq.ShouldBe((ulong)11);
|
||||
state.LastSeq.ShouldBe((ulong)20);
|
||||
|
||||
(await store.LoadAsync(1, default)).ShouldBeNull();
|
||||
(await store.LoadAsync(10, default)).ShouldBeNull();
|
||||
(await store.LoadAsync(11, default)).ShouldNotBeNull();
|
||||
}
|
||||
|
||||
// Go: TestMemStoreMsgLimit server/memstore_test.go
|
||||
[Fact]
|
||||
public async Task TrimToMaxMessages_to_zero()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
await store.AppendAsync("foo", "data"u8.ToArray(), default);
|
||||
|
||||
store.TrimToMaxMessages(0);
|
||||
|
||||
(await store.GetStateAsync(default)).Messages.ShouldBe((ulong)0);
|
||||
}
|
||||
|
||||
// Go: TestMemStoreBytesLimit server/memstore_test.go
|
||||
[Fact]
|
||||
public async Task Bytes_tracks_payload_sizes()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
var payload = new byte[100];
|
||||
for (var i = 0; i < 5; i++)
|
||||
await store.AppendAsync("foo", payload, default);
|
||||
|
||||
var state = await store.GetStateAsync(default);
|
||||
// Go parity: MsgSize = subj.Length + hdr + data + 16 overhead
|
||||
// "foo"(3) + 100 + 16 = 119 per msg × 5 = 595
|
||||
state.Bytes.ShouldBe((ulong)595);
|
||||
}
|
||||
|
||||
// Go: TestMemStoreBytesLimit server/memstore_test.go
|
||||
[Fact]
|
||||
public async Task Bytes_decrease_after_remove()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
var payload = new byte[100];
|
||||
for (var i = 0; i < 5; i++)
|
||||
await store.AppendAsync("foo", payload, default);
|
||||
|
||||
await store.RemoveAsync(1, default);
|
||||
await store.RemoveAsync(3, default);
|
||||
|
||||
var state = await store.GetStateAsync(default);
|
||||
// Go parity: MsgSize = subj.Length + hdr + data + 16 overhead
|
||||
// "foo"(3) + 100 + 16 = 119 per msg × 3 remaining = 357
|
||||
state.Bytes.ShouldBe((ulong)357);
|
||||
}
|
||||
|
||||
// Snapshot and restore.
|
||||
[Fact]
|
||||
public async Task Snapshot_and_restore()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
for (var i = 0; i < 20; i++)
|
||||
await store.AppendAsync("foo", Encoding.UTF8.GetBytes($"msg-{i}"), default);
|
||||
|
||||
var snap = await store.CreateSnapshotAsync(default);
|
||||
snap.Length.ShouldBeGreaterThan(0);
|
||||
|
||||
var restored = new MemStore();
|
||||
await restored.RestoreSnapshotAsync(snap, default);
|
||||
|
||||
var srcState = await store.GetStateAsync(default);
|
||||
var dstState = await restored.GetStateAsync(default);
|
||||
dstState.Messages.ShouldBe(srcState.Messages);
|
||||
dstState.FirstSeq.ShouldBe(srcState.FirstSeq);
|
||||
dstState.LastSeq.ShouldBe(srcState.LastSeq);
|
||||
|
||||
for (ulong i = 1; i <= srcState.Messages; i++)
|
||||
{
|
||||
var original = await store.LoadAsync(i, default);
|
||||
var copy = await restored.LoadAsync(i, default);
|
||||
copy.ShouldNotBeNull();
|
||||
copy!.Payload.ToArray().ShouldBe(original!.Payload.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
// Snapshot after removes.
|
||||
[Fact]
|
||||
public async Task Snapshot_after_removes()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
await store.AppendAsync("foo", Encoding.UTF8.GetBytes($"msg-{i}"), default);
|
||||
|
||||
await store.RemoveAsync(2, default);
|
||||
await store.RemoveAsync(5, default);
|
||||
await store.RemoveAsync(8, default);
|
||||
|
||||
var snap = await store.CreateSnapshotAsync(default);
|
||||
|
||||
var restored = new MemStore();
|
||||
await restored.RestoreSnapshotAsync(snap, default);
|
||||
|
||||
var dstState = await restored.GetStateAsync(default);
|
||||
dstState.Messages.ShouldBe((ulong)7);
|
||||
|
||||
(await restored.LoadAsync(2, default)).ShouldBeNull();
|
||||
(await restored.LoadAsync(5, default)).ShouldBeNull();
|
||||
(await restored.LoadAsync(8, default)).ShouldBeNull();
|
||||
(await restored.LoadAsync(1, default)).ShouldNotBeNull();
|
||||
}
|
||||
|
||||
// ListAsync ordered.
|
||||
[Fact]
|
||||
public async Task ListAsync_returns_ordered()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
await store.AppendAsync("c", "three"u8.ToArray(), default);
|
||||
await store.AppendAsync("a", "one"u8.ToArray(), default);
|
||||
await store.AppendAsync("b", "two"u8.ToArray(), default);
|
||||
|
||||
var messages = await store.ListAsync(default);
|
||||
messages.Count.ShouldBe(3);
|
||||
messages[0].Sequence.ShouldBe((ulong)1);
|
||||
messages[1].Sequence.ShouldBe((ulong)2);
|
||||
messages[2].Sequence.ShouldBe((ulong)3);
|
||||
}
|
||||
|
||||
// Purge then append.
|
||||
[Fact]
|
||||
public async Task Purge_then_append()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
await store.AppendAsync("foo", "data"u8.ToArray(), default);
|
||||
|
||||
await store.PurgeAsync(default);
|
||||
|
||||
var seq = await store.AppendAsync("foo", "after purge"u8.ToArray(), default);
|
||||
seq.ShouldBeGreaterThan((ulong)0);
|
||||
|
||||
var msg = await store.LoadAsync(seq, default);
|
||||
msg.ShouldNotBeNull();
|
||||
msg!.Payload.ToArray().ShouldBe("after purge"u8.ToArray());
|
||||
}
|
||||
|
||||
// Empty payload.
|
||||
[Fact]
|
||||
public async Task Empty_payload_round_trips()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
await store.AppendAsync("foo", ReadOnlyMemory<byte>.Empty, default);
|
||||
|
||||
var msg = await store.LoadAsync(1, default);
|
||||
msg.ShouldNotBeNull();
|
||||
msg!.Payload.Length.ShouldBe(0);
|
||||
}
|
||||
|
||||
// State on empty store.
|
||||
[Fact]
|
||||
public async Task Empty_store_state()
|
||||
{
|
||||
var store = new MemStore();
|
||||
|
||||
var state = await store.GetStateAsync(default);
|
||||
state.Messages.ShouldBe((ulong)0);
|
||||
state.Bytes.ShouldBe((ulong)0);
|
||||
state.FirstSeq.ShouldBe((ulong)0);
|
||||
state.LastSeq.ShouldBe((ulong)0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user