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,244 @@
|
||||
// Go reference: consumer.go:4241 (processResetReq)
|
||||
using NATS.Server.JetStream;
|
||||
using NATS.Server.JetStream.Consumers;
|
||||
using NATS.Server.JetStream.Models;
|
||||
using NATS.Server.JetStream.Storage;
|
||||
using System.Text;
|
||||
|
||||
namespace NATS.Server.JetStream.Tests.JetStream.Consumers;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for consumer reset-to-sequence (Gap 3.12) and AckProcessor.ClearAll / SetAckFloor.
|
||||
/// Go reference: consumer.go:4241 processResetReq.
|
||||
/// </summary>
|
||||
public class ConsumerResetTests
|
||||
{
|
||||
private static ConsumerManager CreateManager() => new();
|
||||
|
||||
private static void CreateConsumer(ConsumerManager mgr, string stream, string name,
|
||||
Action<ConsumerConfig>? configure = null)
|
||||
{
|
||||
var config = new ConsumerConfig { DurableName = name };
|
||||
configure?.Invoke(config);
|
||||
mgr.CreateOrUpdate(stream, config);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ResetToSequence tests
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Go reference: consumer.go:4241 — processResetReq sets consumer.sseq to
|
||||
// the requested sequence so the next fetch starts there.
|
||||
[Fact]
|
||||
public void ResetToSequence_updates_next_sequence()
|
||||
{
|
||||
var mgr = CreateManager();
|
||||
CreateConsumer(mgr, "ORDERS", "oc1");
|
||||
|
||||
// Advance the consumer naturally so NextSequence is not 1
|
||||
mgr.TryGet("ORDERS", "oc1", out var before);
|
||||
before.NextSequence = 10;
|
||||
|
||||
mgr.ResetToSequence("ORDERS", "oc1", 5);
|
||||
|
||||
mgr.TryGet("ORDERS", "oc1", out var after);
|
||||
after.NextSequence.ShouldBe(5UL);
|
||||
}
|
||||
|
||||
// Go reference: consumer.go:4241 — reset clears the pending ack map so
|
||||
// stale ack tokens from before the reset cannot be accepted.
|
||||
[Fact]
|
||||
public void ResetToSequence_clears_pending_acks()
|
||||
{
|
||||
var mgr = CreateManager();
|
||||
CreateConsumer(mgr, "ORDERS", "oc2");
|
||||
|
||||
mgr.TryGet("ORDERS", "oc2", out var handle);
|
||||
handle.AckProcessor.Register(3, ackWaitMs: 5000);
|
||||
handle.AckProcessor.Register(7, ackWaitMs: 5000);
|
||||
handle.AckProcessor.PendingCount.ShouldBe(2);
|
||||
|
||||
mgr.ResetToSequence("ORDERS", "oc2", 1);
|
||||
|
||||
handle.AckProcessor.PendingCount.ShouldBe(0);
|
||||
}
|
||||
|
||||
// Go reference: consumer.go:4241 — pendingBytes must be zeroed on reset
|
||||
// so the idle heartbeat header is correct after the reset.
|
||||
[Fact]
|
||||
public void ResetToSequence_clears_pending_bytes()
|
||||
{
|
||||
var mgr = CreateManager();
|
||||
CreateConsumer(mgr, "ORDERS", "oc3");
|
||||
|
||||
mgr.TryGet("ORDERS", "oc3", out var handle);
|
||||
handle.PendingBytes = 12345;
|
||||
|
||||
mgr.ResetToSequence("ORDERS", "oc3", 1);
|
||||
|
||||
handle.PendingBytes.ShouldBe(0L);
|
||||
}
|
||||
|
||||
// Go reference: consumer.go:4241 — returns false when the consumer does
|
||||
// not exist (unknown stream or durable name).
|
||||
[Fact]
|
||||
public void ResetToSequence_returns_false_for_missing_consumer()
|
||||
{
|
||||
var mgr = CreateManager();
|
||||
|
||||
mgr.ResetToSequence("NO-STREAM", "NO-CONSUMER", 1).ShouldBeFalse();
|
||||
}
|
||||
|
||||
// Go reference: consumer.go:4241 — returns true when the consumer exists
|
||||
// and the reset is applied.
|
||||
[Fact]
|
||||
public void ResetToSequence_returns_true_for_existing_consumer()
|
||||
{
|
||||
var mgr = CreateManager();
|
||||
CreateConsumer(mgr, "ORDERS", "oc4");
|
||||
|
||||
mgr.ResetToSequence("ORDERS", "oc4", 42).ShouldBeTrue();
|
||||
}
|
||||
|
||||
// Go reference: consumer.go:4241 — consumer config (subject filters, ack
|
||||
// policy, etc.) is immutable during reset; only positional / tracking state
|
||||
// is cleared.
|
||||
[Fact]
|
||||
public void ResetToSequence_preserves_config()
|
||||
{
|
||||
var mgr = CreateManager();
|
||||
CreateConsumer(mgr, "ORDERS", "oc5", cfg =>
|
||||
{
|
||||
cfg.FilterSubject = "orders.>";
|
||||
cfg.AckPolicy = AckPolicy.Explicit;
|
||||
});
|
||||
|
||||
mgr.ResetToSequence("ORDERS", "oc5", 1);
|
||||
|
||||
mgr.TryGet("ORDERS", "oc5", out var handle);
|
||||
handle.Config.FilterSubject.ShouldBe("orders.>");
|
||||
handle.Config.AckPolicy.ShouldBe(AckPolicy.Explicit);
|
||||
}
|
||||
|
||||
// Go reference: consumer.go:4241 — after reset the push engine can
|
||||
// re-enqueue messages starting at the reset sequence.
|
||||
[Fact]
|
||||
public void ResetToSequence_allows_re_delivery_from_sequence()
|
||||
{
|
||||
var mgr = CreateManager();
|
||||
CreateConsumer(mgr, "ORDERS", "oc6", cfg =>
|
||||
{
|
||||
cfg.Push = true;
|
||||
cfg.DeliverSubject = "deliver.test";
|
||||
});
|
||||
|
||||
mgr.TryGet("ORDERS", "oc6", out var handle);
|
||||
handle.NextSequence = 50;
|
||||
|
||||
mgr.ResetToSequence("ORDERS", "oc6", 10);
|
||||
|
||||
// After reset the consumer reads from sequence 10
|
||||
handle.NextSequence.ShouldBe(10UL);
|
||||
|
||||
// Simulate re-enqueueing a message at that sequence via OnPublished
|
||||
var msg = new StoredMessage
|
||||
{
|
||||
Sequence = 10,
|
||||
Subject = "orders.new",
|
||||
Payload = Encoding.UTF8.GetBytes("data"),
|
||||
TimestampUtc = DateTime.UtcNow,
|
||||
};
|
||||
mgr.OnPublished("ORDERS", msg);
|
||||
|
||||
// Message should be in the push frame queue
|
||||
handle.PushFrames.Count.ShouldBeGreaterThan(0);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// AckProcessor.ClearAll tests
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Go reference: consumer.go processResetReq — pending ack map cleared
|
||||
[Fact]
|
||||
public void ClearAll_clears_pending()
|
||||
{
|
||||
var processor = new AckProcessor();
|
||||
processor.Register(1, ackWaitMs: 5000);
|
||||
processor.Register(2, ackWaitMs: 5000);
|
||||
processor.Register(3, ackWaitMs: 5000);
|
||||
processor.PendingCount.ShouldBe(3);
|
||||
|
||||
processor.ClearAll();
|
||||
|
||||
processor.PendingCount.ShouldBe(0);
|
||||
}
|
||||
|
||||
// Go reference: consumer.go processResetReq — terminated set cleared
|
||||
[Fact]
|
||||
public void ClearAll_clears_terminated()
|
||||
{
|
||||
var processor = new AckProcessor();
|
||||
processor.Register(1, ackWaitMs: 5000);
|
||||
processor.Register(2, ackWaitMs: 5000);
|
||||
processor.ProcessTerm(1);
|
||||
processor.ProcessTerm(2);
|
||||
processor.TerminatedCount.ShouldBe(2);
|
||||
|
||||
processor.ClearAll();
|
||||
|
||||
processor.TerminatedCount.ShouldBe(0);
|
||||
}
|
||||
|
||||
// Go reference: consumer.go processResetReq — ack floor reset to 0
|
||||
[Fact]
|
||||
public void ClearAll_resets_ack_floor()
|
||||
{
|
||||
var processor = new AckProcessor();
|
||||
processor.Register(1, ackWaitMs: 5000);
|
||||
processor.Register(2, ackWaitMs: 5000);
|
||||
processor.AckSequence(1);
|
||||
processor.AckSequence(2);
|
||||
processor.AckFloor.ShouldBeGreaterThan(0UL);
|
||||
|
||||
processor.ClearAll();
|
||||
|
||||
processor.AckFloor.ShouldBe(0UL);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// AckProcessor.SetAckFloor tests
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Go reference: consumer.go processResetReq — ack floor can be set to a
|
||||
// specific sequence to reflect the stream state after reset.
|
||||
[Fact]
|
||||
public void SetAckFloor_updates_floor()
|
||||
{
|
||||
var processor = new AckProcessor();
|
||||
|
||||
processor.SetAckFloor(99);
|
||||
|
||||
processor.AckFloor.ShouldBe(99UL);
|
||||
}
|
||||
|
||||
// Go reference: consumer.go processResetReq — any pending sequences below
|
||||
// the new floor are irrelevant (already delivered before the floor) and
|
||||
// must be pruned to avoid ghost acks.
|
||||
[Fact]
|
||||
public void SetAckFloor_removes_entries_below_floor()
|
||||
{
|
||||
var processor = new AckProcessor();
|
||||
processor.Register(1, ackWaitMs: 5000);
|
||||
processor.Register(2, ackWaitMs: 5000);
|
||||
processor.Register(5, ackWaitMs: 5000);
|
||||
processor.Register(10, ackWaitMs: 5000);
|
||||
processor.PendingCount.ShouldBe(4);
|
||||
|
||||
processor.SetAckFloor(5);
|
||||
|
||||
// Sequences 1, 2, and 5 (<=5) are below or at the new floor and must be removed
|
||||
processor.PendingCount.ShouldBe(1);
|
||||
// Sequence 10 is above the floor and must remain
|
||||
processor.HasPending.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user