feat: enforce jetstream ack and redelivery semantics
This commit is contained in:
17
tests/NATS.Server.Tests/JetStreamAckRedeliveryTests.cs
Normal file
17
tests/NATS.Server.Tests/JetStreamAckRedeliveryTests.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace NATS.Server.Tests;
|
||||
|
||||
public class JetStreamAckRedeliveryTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Unacked_message_is_redelivered_after_ack_wait()
|
||||
{
|
||||
await using var fixture = await JetStreamApiFixture.StartWithAckExplicitConsumerAsync(ackWaitMs: 50);
|
||||
await fixture.PublishAndGetAckAsync("orders.created", "1");
|
||||
|
||||
var first = await fixture.FetchAsync("ORDERS", "PULL", batch: 1);
|
||||
var second = await fixture.FetchAfterDelayAsync("ORDERS", "PULL", delayMs: 75, batch: 1);
|
||||
|
||||
second.Messages.Single().Sequence.ShouldBe(first.Messages.Single().Sequence);
|
||||
second.Messages.Single().Redelivered.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,14 @@ internal sealed class JetStreamApiFixture : IAsyncDisposable
|
||||
return fixture;
|
||||
}
|
||||
|
||||
public static async Task<JetStreamApiFixture> StartWithAckExplicitConsumerAsync(int ackWaitMs)
|
||||
{
|
||||
var fixture = await StartWithStreamAsync("ORDERS", "orders.*");
|
||||
_ = await fixture.CreateConsumerAsync("ORDERS", "PULL", "orders.created",
|
||||
ackPolicy: AckPolicy.Explicit, ackWaitMs: ackWaitMs);
|
||||
return fixture;
|
||||
}
|
||||
|
||||
public Task<PubAck> PublishAndGetAckAsync(string subject, string payload, string? msgId = null, bool expectError = false)
|
||||
{
|
||||
if (_publisher.TryCapture(subject, Encoding.UTF8.GetBytes(payload), msgId, out var ack))
|
||||
@@ -83,9 +91,9 @@ internal sealed class JetStreamApiFixture : IAsyncDisposable
|
||||
return _streamManager.GetStateAsync(streamName, default).AsTask();
|
||||
}
|
||||
|
||||
public Task<JetStreamApiResponse> CreateConsumerAsync(string stream, string durableName, string filterSubject, bool push = false, int heartbeatMs = 0)
|
||||
public Task<JetStreamApiResponse> CreateConsumerAsync(string stream, string durableName, string filterSubject, bool push = false, int heartbeatMs = 0, AckPolicy ackPolicy = AckPolicy.None, int ackWaitMs = 30_000)
|
||||
{
|
||||
var payload = $@"{{""durable_name"":""{durableName}"",""filter_subject"":""{filterSubject}"",""push"":{push.ToString().ToLowerInvariant()},""heartbeat_ms"":{heartbeatMs}}}";
|
||||
var payload = $@"{{""durable_name"":""{durableName}"",""filter_subject"":""{filterSubject}"",""push"":{push.ToString().ToLowerInvariant()},""heartbeat_ms"":{heartbeatMs},""ack_policy"":""{ackPolicy.ToString().ToLowerInvariant()}"",""ack_wait_ms"":{ackWaitMs}}}";
|
||||
return RequestLocalAsync($"$JS.API.CONSUMER.CREATE.{stream}.{durableName}", payload);
|
||||
}
|
||||
|
||||
@@ -100,6 +108,12 @@ internal sealed class JetStreamApiFixture : IAsyncDisposable
|
||||
return _consumerManager.FetchAsync(stream, durableName, batch, _streamManager, default).AsTask();
|
||||
}
|
||||
|
||||
public async Task<PullFetchBatch> FetchAfterDelayAsync(string stream, string durableName, int delayMs, int batch)
|
||||
{
|
||||
await Task.Delay(delayMs);
|
||||
return await FetchAsync(stream, durableName, batch);
|
||||
}
|
||||
|
||||
public Task<PushFrame> ReadPushFrameAsync(string stream = "ORDERS", string durableName = "PUSH")
|
||||
{
|
||||
var frame = _consumerManager.ReadPushFrame(stream, durableName);
|
||||
|
||||
Reference in New Issue
Block a user