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,229 @@
// Ported from golang/nats-server/server/jetstream_consumer_test.go
// Covers: consumer creation, deliver policies (All, Last, New, ByStartSequence, ByStartTime),
// and ack policies (None, Explicit, All) as modelled in the .NET port.
//
// Go reference tests:
// TestJetStreamConsumerCreate (~line 2967)
// TestJetStreamConsumerWithStartTime (~line 3160)
// TestJetStreamConsumerMaxDeliveries (~line 3265)
// TestJetStreamConsumerAckFloorFill (~line 3404)
// TestJetStreamConsumerReplayRateNoAck (~line 4505)
using System.Text;
using NATS.Server.JetStream;
using NATS.Server.JetStream.Consumers;
using NATS.Server.JetStream.Models;
using NATS.Server.JetStream.Storage;
namespace NATS.Server.JetStream.Tests.JetStream;
/// <summary>
/// Consumer delivery parity tests ported from the Go reference implementation.
/// These tests exercise push/pull delivery, deliver policies, and ack policies against
/// the in-process ConsumerManager + StreamManager, mirroring the semantics validated in
/// golang/nats-server/server/jetstream_consumer_test.go.
/// </summary>
public class ConsumerDeliveryParityTests
{
// -------------------------------------------------------------------------
// Test 1 Pull consumer with DeliverPolicy.All returns all published msgs
//
// Go reference: TestJetStreamConsumerCreate verifies that a durable pull
// consumer created with default settings fetches all stored messages in
// sequence order.
// -------------------------------------------------------------------------
[Fact]
public async Task Pull_consumer_deliver_all_returns_messages_in_sequence_order()
{
var streams = new StreamManager();
streams.CreateOrUpdate(new StreamConfig
{
Name = "ORDERS",
Subjects = ["orders.*"],
}).Error.ShouldBeNull();
var consumers = new ConsumerManager();
consumers.CreateOrUpdate("ORDERS", new ConsumerConfig
{
DurableName = "PULL",
DeliverPolicy = DeliverPolicy.All,
}).Error.ShouldBeNull();
streams.Capture("orders.created", "msg-1"u8.ToArray());
streams.Capture("orders.updated", "msg-2"u8.ToArray());
streams.Capture("orders.created", "msg-3"u8.ToArray());
var batch = await consumers.FetchAsync("ORDERS", "PULL", 3, streams, default);
batch.Messages.Count.ShouldBe(3);
batch.Messages[0].Sequence.ShouldBe((ulong)1);
batch.Messages[1].Sequence.ShouldBe((ulong)2);
batch.Messages[2].Sequence.ShouldBe((ulong)3);
}
// -------------------------------------------------------------------------
// Test 2 Deliver policy Last starts at the final stored sequence
//
// Go reference: TestJetStreamConsumerWithMultipleStartOptions verifies
// that DeliverLast causes the consumer cursor to begin at the last message
// in the stream rather than seq 1.
// -------------------------------------------------------------------------
[Fact]
public async Task Pull_consumer_deliver_last_starts_at_final_sequence()
{
var streams = new StreamManager();
streams.CreateOrUpdate(new StreamConfig
{
Name = "ORDERS",
Subjects = ["orders.*"],
}).Error.ShouldBeNull();
streams.Capture("orders.a", "first"u8.ToArray());
streams.Capture("orders.b", "second"u8.ToArray());
streams.Capture("orders.c", "third"u8.ToArray());
var consumers = new ConsumerManager();
consumers.CreateOrUpdate("ORDERS", new ConsumerConfig
{
DurableName = "LAST",
DeliverPolicy = DeliverPolicy.Last,
}).Error.ShouldBeNull();
var batch = await consumers.FetchAsync("ORDERS", "LAST", 5, streams, default);
// DeliverLast cursor resolves to sequence 3 (last stored).
batch.Messages.Count.ShouldBe(1);
batch.Messages[0].Sequence.ShouldBe((ulong)3);
}
// -------------------------------------------------------------------------
// Test 3 Deliver policy New skips all messages present at first-fetch time
//
// Go reference: TestJetStreamConsumerDeliverNewNotConsumingBeforeRestart
// (~line 6213) validates that DeliverNew positions the cursor past the
// last stored sequence so that messages already in the stream when the
// consumer first fetches are not returned.
//
// In the .NET port the initial sequence is resolved on the first FetchAsync
// call (when NextSequence == 1). DeliverPolicy.New sets the cursor to
// lastSeq + 1, so every message present at fetch time is skipped and only
// subsequent publishes are visible.
// -------------------------------------------------------------------------
[Fact]
public async Task Pull_consumer_deliver_new_skips_messages_present_at_first_fetch()
{
var streams = new StreamManager();
streams.CreateOrUpdate(new StreamConfig
{
Name = "ORDERS",
Subjects = ["orders.*"],
}).Error.ShouldBeNull();
streams.Capture("orders.a", "pre-1"u8.ToArray());
streams.Capture("orders.b", "pre-2"u8.ToArray());
var consumers = new ConsumerManager();
consumers.CreateOrUpdate("ORDERS", new ConsumerConfig
{
DurableName = "NEW",
DeliverPolicy = DeliverPolicy.New,
}).Error.ShouldBeNull();
// First fetch: resolves cursor to lastSeq+1 = 3, which has no message yet.
var empty = await consumers.FetchAsync("ORDERS", "NEW", 5, streams, default);
empty.Messages.Count.ShouldBe(0);
// Now publish a new message this is the "new" message after the cursor.
streams.Capture("orders.c", "post-1"u8.ToArray());
// Second fetch: cursor is already at 3, the newly published message is at 3.
var batch = await consumers.FetchAsync("ORDERS", "NEW", 5, streams, default);
batch.Messages.Count.ShouldBe(1);
batch.Messages[0].Sequence.ShouldBe((ulong)3);
}
// -------------------------------------------------------------------------
// Test 4 Deliver policy ByStartTime resolves cursor at the correct seq
//
// Go reference: TestJetStreamConsumerWithStartTime (~line 3160) publishes
// messages before a recorded timestamp, then creates a consumer with
// DeliverByStartTime and verifies the first delivered sequence matches the
// first message after that timestamp.
// -------------------------------------------------------------------------
[Fact]
public async Task Pull_consumer_deliver_by_start_time_resolves_correct_starting_sequence()
{
var streams = new StreamManager();
streams.CreateOrUpdate(new StreamConfig
{
Name = "ORDERS",
Subjects = ["orders.*"],
}).Error.ShouldBeNull();
streams.Capture("orders.a", "before-1"u8.ToArray());
streams.Capture("orders.b", "before-2"u8.ToArray());
// Brief pause so that stored timestamps of pre-existing messages are
// strictly before the cut point we are about to record.
await Task.Delay(10);
var startTime = DateTime.UtcNow;
streams.Capture("orders.c", "after-1"u8.ToArray());
streams.Capture("orders.d", "after-2"u8.ToArray());
var consumers = new ConsumerManager();
consumers.CreateOrUpdate("ORDERS", new ConsumerConfig
{
DurableName = "BYTIME",
DeliverPolicy = DeliverPolicy.ByStartTime,
OptStartTimeUtc = startTime,
}).Error.ShouldBeNull();
var batch = await consumers.FetchAsync("ORDERS", "BYTIME", 5, streams, default);
// Only messages with timestamp >= startTime should be returned.
batch.Messages.Count.ShouldBe(2);
batch.Messages.All(m => m.Sequence >= 3).ShouldBeTrue();
}
// -------------------------------------------------------------------------
// Test 5 AckAll advances the ack floor and blocks re-delivery of acked msgs
//
// Go reference: TestJetStreamConsumerAckFloorFill (~line 3404) publishes
// four messages, acks all via AckAll on seq 4, and then verifies that a
// subsequent fetch returns zero messages because every sequence is at or
// below the ack floor.
// -------------------------------------------------------------------------
[Fact]
public async Task Explicit_ack_all_advances_floor_and_suppresses_redelivery()
{
var streams = new StreamManager();
streams.CreateOrUpdate(new StreamConfig
{
Name = "ORDERS",
Subjects = ["orders.*"],
}).Error.ShouldBeNull();
var consumers = new ConsumerManager();
consumers.CreateOrUpdate("ORDERS", new ConsumerConfig
{
DurableName = "ACK",
AckPolicy = AckPolicy.Explicit,
AckWaitMs = 100,
}).Error.ShouldBeNull();
for (var i = 1; i <= 4; i++)
streams.Capture("orders.created", Encoding.UTF8.GetBytes($"msg-{i}"));
var first = await consumers.FetchAsync("ORDERS", "ACK", 4, streams, default);
first.Messages.Count.ShouldBe(4);
// AckAll up to sequence 4 should advance floor and clear all pending.
consumers.AckAll("ORDERS", "ACK", 4);
// A subsequent fetch must return no messages because the ack floor
// now covers all published sequences and there are no new messages.
var second = await consumers.FetchAsync("ORDERS", "ACK", 4, streams, default);
second.Messages.Count.ShouldBe(0);
}
}