feat: expose terminal consumer failure hook
Allow consumers to react once after all processing retries are exhausted while preserving the base acknowledgement policy. Ref: IT-1033
This commit is contained in:
@@ -57,6 +57,33 @@ public sealed class RabbitMqConsumerBaseExtensionTests
|
||||
error.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RetriesExhaustedHook_ReceivesMessageContextAndTerminalException()
|
||||
{
|
||||
var consumer = new TerminalFailureConsumer(CreateOptions());
|
||||
var message = CreateMessage();
|
||||
var exception = new InvalidOperationException("terminal failure");
|
||||
|
||||
await consumer.InvokeRetriesExhaustedAsync(message, Context, exception);
|
||||
|
||||
consumer.FailedMessage.Should().BeSameAs(message);
|
||||
consumer.FailedContext.Should().BeSameAs(Context);
|
||||
consumer.TerminalException.Should().BeSameAs(exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RetriesExhaustedHook_IsNoOpByDefault()
|
||||
{
|
||||
var consumer = new LegacyConsumer(CreateOptions());
|
||||
|
||||
Func<Task> act = () => consumer.InvokeRetriesExhaustedAsync(
|
||||
CreateMessage(),
|
||||
Context,
|
||||
new InvalidOperationException("terminal failure"));
|
||||
|
||||
await act.Should().NotThrowAsync();
|
||||
}
|
||||
|
||||
private static TestMessage CreateMessage() => new()
|
||||
{
|
||||
CorrelationContext = new CorrelationContext { CorrelationId = "payload-correlation" },
|
||||
@@ -117,6 +144,51 @@ public sealed class RabbitMqConsumerBaseExtensionTests
|
||||
{
|
||||
return TryValidateMessage(message, context, out error);
|
||||
}
|
||||
|
||||
public Task InvokeRetriesExhaustedAsync(
|
||||
TestMessage message,
|
||||
RabbitMqMessageContext context,
|
||||
Exception exception)
|
||||
{
|
||||
return HandleMessageRetriesExhaustedAsync(
|
||||
message,
|
||||
context,
|
||||
exception,
|
||||
CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TerminalFailureConsumer(IOptionsMonitor<RabbitMqSettings> options)
|
||||
: RabbitMqConsumerBase<TestMessage, string>(options, NullLogger.Instance)
|
||||
{
|
||||
protected override string QueueName => "messages.test";
|
||||
public TestMessage? FailedMessage { get; private set; }
|
||||
public RabbitMqMessageContext? FailedContext { get; private set; }
|
||||
public Exception? TerminalException { get; private set; }
|
||||
|
||||
protected override Task HandleMessageRetriesExhaustedAsync(
|
||||
TestMessage message,
|
||||
RabbitMqMessageContext context,
|
||||
Exception exception,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
FailedMessage = message;
|
||||
FailedContext = context;
|
||||
TerminalException = exception;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task InvokeRetriesExhaustedAsync(
|
||||
TestMessage message,
|
||||
RabbitMqMessageContext context,
|
||||
Exception exception)
|
||||
{
|
||||
return HandleMessageRetriesExhaustedAsync(
|
||||
message,
|
||||
context,
|
||||
exception,
|
||||
CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record TestMessage : IRabbitMqMessage<string>
|
||||
|
||||
Reference in New Issue
Block a user