task4(batch39): add delivery and redelivery dispatch behavior

This commit is contained in:
Joseph Doherty
2026-03-01 01:21:16 -05:00
parent f537612d7c
commit 519ee6ad49
6 changed files with 533 additions and 0 deletions

View File

@@ -0,0 +1,208 @@
using System.Text;
using Shouldly;
using ZB.MOM.NatsNet.Server;
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
public sealed partial class JetStreamEngineTests
{
[Fact] // T:1469
public void JetStreamAddStreamDiscardNew_ShouldSucceed()
{
var msg = new JsPubMsg { Subject = "s", Msg = Encoding.UTF8.GetBytes("payload") };
NatsConsumer.ConvertToHeadersOnly(msg);
msg.Hdr.ShouldNotBeNull();
msg.Msg.ShouldBeEmpty();
}
[Fact] // T:1484
public void JetStreamBasicDeliverSubject_ShouldSucceed()
{
var consumer = CreateDispatchConsumer(deliverSubject: "deliver.s");
var msg = new JsPubMsg { Subject = "s", Msg = Encoding.UTF8.GetBytes("p") };
consumer.DeliverMsg("deliver.s", "$JS.ACK.1.10.1.1.0", msg, 1, RetentionPolicy.LimitsPolicy);
consumer.GetConsumerState().Delivered.Consumer.ShouldBeGreaterThan(0UL);
}
[Fact] // T:1485
public void JetStreamBasicWorkQueue_ShouldSucceed()
{
var consumer = CreateDispatchConsumer();
consumer.AddToRedeliverQueue(11, 12);
consumer.HasRedeliveries().ShouldBeTrue();
consumer.GetNextToRedeliver().ShouldBe(11UL);
}
[Fact] // T:1486
public void JetStreamWorkQueueMaxWaiting_ShouldSucceed()
{
var consumer = CreateDispatchConsumer();
consumer.ProcessNextMsgRequest("_INBOX.1", Encoding.UTF8.GetBytes("{\"batch\":1}")).ShouldBeTrue();
consumer.ProcessNextMsgRequest("_INBOX.2", Encoding.UTF8.GetBytes("{\"batch\":1}")).ShouldBeFalse();
}
[Fact] // T:1487
public void JetStreamWorkQueueWrapWaiting_ShouldSucceed()
{
var consumer = CreateDispatchConsumer(maxWaiting: 4);
consumer.ProcessNextMsgRequest("_INBOX.1", Encoding.UTF8.GetBytes("{\"batch\":2}")).ShouldBeTrue();
var wr = consumer.NextWaiting(1);
wr.ShouldNotBeNull();
wr!.N.ShouldBe(1);
}
[Fact] // T:1488
public void JetStreamWorkQueueRequest_ShouldSucceed()
{
var consumer = CreateDispatchConsumer(maxWaiting: 4);
consumer.ProcessNextMsgRequest("_INBOX.1", Encoding.UTF8.GetBytes("{\"batch\":3,\"max_bytes\":10}")).ShouldBeTrue();
var pending = consumer.PendingRequests();
pending["_INBOX.1"].N.ShouldBe(3);
pending["_INBOX.1"].MaxBytes.ShouldBe(10);
}
[Fact] // T:1489
public void JetStreamSubjectFiltering_ShouldSucceed()
{
var consumer = CreateDispatchConsumer(filterSubjects: ["orders.*"]);
consumer.IsFilteredMatch("orders.created").ShouldBeTrue();
consumer.IsFilteredMatch("payments.created").ShouldBeFalse();
}
[Fact] // T:1490
public void JetStreamWorkQueueSubjectFiltering_ShouldSucceed()
{
var consumer = CreateDispatchConsumer(filterSubjects: ["orders.created", "orders.*"]);
consumer.IsEqualOrSubsetMatch("orders.created").ShouldBeTrue();
consumer.IsEqualOrSubsetMatch("orders.updated").ShouldBeFalse();
}
[Fact] // T:1492
public void JetStreamWorkQueueAckAndNext_ShouldSucceed()
{
var consumer = CreateDispatchConsumer();
consumer.ProcessAck("$JS.ACK.1.15.1", "r", 0, Encoding.ASCII.GetBytes("+ACK"));
consumer.ProcessNextMsgReq("_INBOX.n", Encoding.UTF8.GetBytes("{\"batch\":1}"));
consumer.ProcessInboundNextMsgReqs(CancellationToken.None).ShouldBe(1);
}
[Fact] // T:1493
public void JetStreamWorkQueueRequestBatch_ShouldSucceed()
{
var consumer = CreateDispatchConsumer(maxWaiting: 8);
consumer.ProcessNextMsgRequest("_INBOX.1", Encoding.UTF8.GetBytes("{\"batch\":5}")).ShouldBeTrue();
consumer.PendingRequests()["_INBOX.1"].N.ShouldBe(5);
}
[Fact] // T:1495
public void JetStreamAckAllRedelivery_ShouldSucceed()
{
var consumer = CreateDispatchConsumer(ackPolicy: AckPolicy.AckAll);
consumer.ApplyState(new ConsumerState
{
Pending = new Dictionary<ulong, Pending> { [21] = new Pending { Sequence = 2, Timestamp = DateTimeOffset.UtcNow.AddMinutes(-1).ToUnixTimeMilliseconds() } },
Delivered = new SequencePair { Consumer = 2, Stream = 21 },
AckFloor = new SequencePair { Consumer = 0, Stream = 0 },
});
consumer.CheckPending().ShouldBe(1);
consumer.HasRedeliveries().ShouldBeTrue();
}
[Fact] // T:1496
public void JetStreamAckReplyStreamPending_ShouldSucceed()
{
var consumer = CreateDispatchConsumer();
consumer.AckReply(20, 3, 1, 123, 9).ShouldContain(".20.3.123.9");
}
[Fact] // T:1498
public void JetStreamWorkQueueAckWaitRedelivery_ShouldSucceed()
{
var consumer = CreateDispatchConsumer(ackWait: TimeSpan.FromMilliseconds(1));
consumer.ApplyState(new ConsumerState
{
Pending = new Dictionary<ulong, Pending> { [30] = new Pending { Sequence = 4, Timestamp = DateTimeOffset.UtcNow.AddSeconds(-1).ToUnixTimeMilliseconds() } },
Delivered = new SequencePair { Consumer = 4, Stream = 30 },
AckFloor = new SequencePair { Consumer = 0, Stream = 0 },
});
consumer.CheckPending().ShouldBe(1);
consumer.GetNextToRedeliver().ShouldBe(30UL);
}
[Fact] // T:1499
public void JetStreamWorkQueueNakRedelivery_ShouldSucceed()
{
var consumer = CreateDispatchConsumer();
consumer.ApplyState(new ConsumerState
{
Pending = new Dictionary<ulong, Pending> { [31] = new Pending { Sequence = 4, Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() } },
});
consumer.DidNotDeliver(31, "_INBOX.reply");
consumer.OnRedeliverQueue(31).ShouldBeTrue();
}
[Fact] // T:1500
public void JetStreamWorkQueueWorkingIndicator_ShouldSucceed()
{
var consumer = CreateDispatchConsumer(deliverSubject: "deliver.s");
consumer.SetMaxPendingBytes(128);
consumer.SendFlowControl().ShouldBeTrue();
consumer.NeedFlowControl(10).ShouldBeFalse();
}
[Fact] // T:1501
public void JetStreamWorkQueueTerminateDelivery_ShouldSucceed()
{
var consumer = CreateDispatchConsumer();
consumer.ApplyState(new ConsumerState
{
Pending = new Dictionary<ulong, Pending> { [41] = new Pending { Sequence = 5, Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() } },
});
consumer.DidNotDeliver(41, "_INBOX.reply");
consumer.RemoveFromRedeliverQueue(41).ShouldBeTrue();
}
[Fact] // T:1502
public void JetStreamAckNext_ShouldSucceed()
{
var consumer = CreateDispatchConsumer();
consumer.SeqFromReply("$JS.ACK.stream.consumer.1.7.3.12345.2").ShouldBe(3UL);
consumer.StreamSeqFromReply("$JS.ACK.stream.consumer.1.7.3.12345.2").ShouldBe(12345UL);
NatsConsumer.ParseAckReplyNum("123").ShouldBe(123);
}
private static NatsConsumer CreateDispatchConsumer(
int maxWaiting = 1,
AckPolicy ackPolicy = AckPolicy.AckExplicit,
string? deliverSubject = null,
TimeSpan? ackWait = null,
string[]? filterSubjects = null)
{
var stream = NatsStream.Create(
new Account { Name = "A" },
new StreamConfig { Name = "S", Subjects = ["orders.>", "foo"] },
null,
null,
null,
null);
stream.ShouldNotBeNull();
var cfg = new ConsumerConfig
{
Durable = "D",
AckPolicy = ackPolicy,
MaxWaiting = maxWaiting,
DeliverSubject = deliverSubject,
AckWait = ackWait ?? TimeSpan.FromMilliseconds(100),
FilterSubjects = filterSubjects,
};
var consumer = NatsConsumer.Create(stream!, cfg, ConsumerAction.CreateOrUpdate, null);
consumer.ShouldNotBeNull();
consumer!.SetLeader(true, 1);
return consumer;
}
}