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:
2026-08-04 11:38:24 +03:00
parent c0e83bdb77
commit 33f4b80ff8
3 changed files with 114 additions and 0 deletions
@@ -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>