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,185 @@
|
||||
// Go: consumer.go:2550 (processAckMsg, processNak, processTerm, processAckProgress)
|
||||
using NATS.Server.JetStream.Consumers;
|
||||
|
||||
namespace NATS.Server.JetStream.Tests.JetStream.Consumers;
|
||||
|
||||
public class AckProcessorNakTests
|
||||
{
|
||||
// Test 1: ProcessAck with empty payload acks the sequence
|
||||
[Fact]
|
||||
public void ProcessAck_empty_payload_acks_sequence()
|
||||
{
|
||||
// Go: consumer.go — empty ack payload treated as "+ACK"
|
||||
var ack = new AckProcessor();
|
||||
ack.Register(1, ackWaitMs: 5000);
|
||||
|
||||
ack.ProcessAck(1, ReadOnlySpan<byte>.Empty);
|
||||
|
||||
ack.PendingCount.ShouldBe(0);
|
||||
ack.AckFloor.ShouldBe((ulong)1);
|
||||
}
|
||||
|
||||
// Test 2: ProcessAck with -NAK schedules redelivery
|
||||
[Fact]
|
||||
public async Task ProcessAck_nak_payload_schedules_redelivery()
|
||||
{
|
||||
// Go: consumer.go — "-NAK" triggers rescheduled redelivery
|
||||
var ack = new AckProcessor();
|
||||
ack.Register(1, ackWaitMs: 5000);
|
||||
|
||||
ack.ProcessAck(1, "-NAK"u8);
|
||||
|
||||
// Should still be pending (redelivery scheduled)
|
||||
ack.PendingCount.ShouldBe(1);
|
||||
|
||||
// Should expire quickly (using ackWait fallback of 5000ms — verify it is still pending now)
|
||||
ack.TryGetExpired(out _, out _).ShouldBeFalse();
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Test 3: ProcessAck with -NAK {delay} uses custom delay
|
||||
[Fact]
|
||||
public async Task ProcessAck_nak_with_delay_uses_custom_delay()
|
||||
{
|
||||
// Go: consumer.go — "-NAK {delay}" parses optional explicit delay in milliseconds
|
||||
var ack = new AckProcessor();
|
||||
ack.Register(1, ackWaitMs: 5000);
|
||||
|
||||
ack.ProcessAck(1, "-NAK 1"u8);
|
||||
|
||||
// Sequence still pending
|
||||
ack.PendingCount.ShouldBe(1);
|
||||
|
||||
// With a 1ms delay, should expire quickly
|
||||
await Task.Delay(10);
|
||||
ack.TryGetExpired(out var seq, out _).ShouldBeTrue();
|
||||
seq.ShouldBe((ulong)1);
|
||||
}
|
||||
|
||||
// Test 4: ProcessAck with +TERM removes from pending
|
||||
[Fact]
|
||||
public void ProcessAck_term_removes_from_pending()
|
||||
{
|
||||
// Go: consumer.go — "+TERM" permanently terminates delivery; sequence never redelivered
|
||||
var ack = new AckProcessor();
|
||||
ack.Register(1, ackWaitMs: 5000);
|
||||
|
||||
ack.ProcessAck(1, "+TERM"u8);
|
||||
|
||||
ack.PendingCount.ShouldBe(0);
|
||||
ack.HasPending.ShouldBeFalse();
|
||||
}
|
||||
|
||||
// Test 5: ProcessAck with +WPI resets deadline without incrementing delivery count
|
||||
[Fact]
|
||||
public async Task ProcessAck_wpi_resets_deadline_without_incrementing_deliveries()
|
||||
{
|
||||
// Go: consumer.go — "+WPI" resets ack deadline; delivery count must not change
|
||||
var ack = new AckProcessor();
|
||||
ack.Register(1, ackWaitMs: 10);
|
||||
|
||||
// Wait for the deadline to approach, then reset it via progress
|
||||
await Task.Delay(5);
|
||||
ack.ProcessAck(1, "+WPI"u8);
|
||||
|
||||
// Deadline was just reset — should not be expired yet
|
||||
ack.TryGetExpired(out _, out var deliveries).ShouldBeFalse();
|
||||
|
||||
// Deliveries count must remain at 1 (not incremented by WPI)
|
||||
deliveries.ShouldBe(0);
|
||||
|
||||
// Sequence still pending
|
||||
ack.PendingCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
// Test 6: Backoff array applies correct delay per redelivery attempt
|
||||
[Fact]
|
||||
public async Task ProcessNak_backoff_array_applies_delay_by_delivery_count()
|
||||
{
|
||||
// Go: consumer.go — backoff array indexes by (deliveries - 1)
|
||||
var ack = new AckProcessor(backoffMs: [1, 50, 5000]);
|
||||
ack.Register(1, ackWaitMs: 5000);
|
||||
|
||||
// First NAK — delivery count is 1 → backoff[0] = 1ms
|
||||
ack.ProcessNak(1);
|
||||
|
||||
await Task.Delay(10);
|
||||
ack.TryGetExpired(out _, out _).ShouldBeTrue();
|
||||
|
||||
// Now delivery count is 2 → backoff[1] = 50ms
|
||||
ack.ProcessNak(1);
|
||||
ack.TryGetExpired(out _, out _).ShouldBeFalse();
|
||||
}
|
||||
|
||||
// Test 7: Backoff array clamps at last entry for high delivery counts
|
||||
[Fact]
|
||||
public async Task ProcessNak_backoff_clamps_at_last_entry_for_high_delivery_count()
|
||||
{
|
||||
// Go: consumer.go — backoff index clamped to backoff.Length-1 when deliveries exceed array size
|
||||
var ack = new AckProcessor(backoffMs: [1, 2]);
|
||||
ack.Register(1, ackWaitMs: 5000);
|
||||
|
||||
// Drive deliveries up: NAK twice to advance delivery count past array length
|
||||
ack.ProcessNak(1); // deliveries becomes 2 (index 1 = 2ms)
|
||||
await Task.Delay(10);
|
||||
ack.TryGetExpired(out _, out _).ShouldBeTrue();
|
||||
|
||||
ack.ProcessNak(1); // deliveries becomes 3 (index clamps to 1 = 2ms)
|
||||
await Task.Delay(10);
|
||||
ack.TryGetExpired(out var seq, out _).ShouldBeTrue();
|
||||
seq.ShouldBe((ulong)1);
|
||||
}
|
||||
|
||||
// Test 8: AckSequence advances AckFloor when contiguous
|
||||
[Fact]
|
||||
public void AckSequence_advances_ackfloor_for_contiguous_sequences()
|
||||
{
|
||||
// Go: consumer.go — acking contiguous sequences from floor advances AckFloor monotonically
|
||||
var ack = new AckProcessor();
|
||||
ack.Register(1, ackWaitMs: 5000);
|
||||
ack.Register(2, ackWaitMs: 5000);
|
||||
ack.Register(3, ackWaitMs: 5000);
|
||||
|
||||
ack.AckSequence(1);
|
||||
ack.AckFloor.ShouldBe((ulong)1);
|
||||
|
||||
ack.AckSequence(2);
|
||||
ack.AckFloor.ShouldBe((ulong)2);
|
||||
}
|
||||
|
||||
// Test 9: ProcessTerm increments TerminatedCount
|
||||
[Fact]
|
||||
public void ProcessTerm_increments_terminated_count()
|
||||
{
|
||||
// Go: consumer.go — terminated sequences tracked separately from acked sequences
|
||||
var ack = new AckProcessor();
|
||||
ack.Register(1, ackWaitMs: 5000);
|
||||
ack.Register(2, ackWaitMs: 5000);
|
||||
|
||||
ack.TerminatedCount.ShouldBe(0);
|
||||
|
||||
ack.ProcessTerm(1);
|
||||
ack.TerminatedCount.ShouldBe(1);
|
||||
|
||||
ack.ProcessTerm(2);
|
||||
ack.TerminatedCount.ShouldBe(2);
|
||||
}
|
||||
|
||||
// Test 10: NAK after TERM is ignored (sequence already terminated)
|
||||
[Fact]
|
||||
public void ProcessNak_after_term_is_ignored()
|
||||
{
|
||||
// Go: consumer.go — once terminated, a sequence cannot be rescheduled via NAK
|
||||
var ack = new AckProcessor(backoffMs: [1]);
|
||||
ack.Register(1, ackWaitMs: 5000);
|
||||
|
||||
ack.ProcessTerm(1);
|
||||
ack.PendingCount.ShouldBe(0);
|
||||
|
||||
// Attempting to NAK a terminated sequence has no effect
|
||||
ack.ProcessNak(1);
|
||||
ack.PendingCount.ShouldBe(0);
|
||||
ack.TerminatedCount.ShouldBe(1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user