feat: expose RabbitMQ delivery context

Add backward-compatible context-aware handling and permanent validation hooks.

Ref: IT-1033
This commit is contained in:
2026-08-02 22:56:57 +03:00
parent 09c4b8b4d2
commit c0e83bdb77
4 changed files with 264 additions and 6 deletions
@@ -0,0 +1,134 @@
namespace Hrynco.RabbitMq.Tests;
using System;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Xunit;
public sealed class RabbitMqConsumerBaseExtensionTests
{
private static readonly RabbitMqMessageContext Context = new()
{
QueueName = "messages.test",
Exchange = string.Empty,
RoutingKey = "messages.test",
MessageId = "message-123",
MessageType = "Example.Message.v1",
CorrelationId = "correlation-123",
ReplyTo = "messages.reply",
ContentType = "application/json",
Redelivered = true
};
[Fact]
public async Task ContextAwareOverload_ReceivesTransportMetadata()
{
var consumer = new ContextAwareConsumer(CreateOptions());
var message = CreateMessage();
await consumer.InvokeAsync(message, Context);
consumer.HandledMessage.Should().BeSameAs(message);
consumer.HandledContext.Should().BeSameAs(Context);
}
[Fact]
public async Task ContextAwareOverload_DelegatesToLegacyOverride()
{
var consumer = new LegacyConsumer(CreateOptions());
var message = CreateMessage();
await consumer.InvokeAsync(message, Context);
consumer.HandledMessage.Should().BeSameAs(message);
}
[Fact]
public void ValidationHook_IsValidByDefault()
{
var consumer = new LegacyConsumer(CreateOptions());
bool isValid = consumer.Validate(CreateMessage(), Context, out string? error);
isValid.Should().BeTrue();
error.Should().BeNull();
}
private static TestMessage CreateMessage() => new()
{
CorrelationContext = new CorrelationContext { CorrelationId = "payload-correlation" },
Data = "payload"
};
private static IOptionsMonitor<RabbitMqSettings> CreateOptions()
{
return new TestOptionsMonitor(new RabbitMqSettings
{
Host = "localhost",
User = "guest",
Password = "guest"
});
}
private sealed class ContextAwareConsumer(IOptionsMonitor<RabbitMqSettings> options)
: RabbitMqConsumerBase<TestMessage, string>(options, NullLogger.Instance)
{
protected override string QueueName => "messages.test";
public TestMessage? HandledMessage { get; private set; }
public RabbitMqMessageContext? HandledContext { get; private set; }
protected override Task HandleMessageAsync(
TestMessage message,
RabbitMqMessageContext context,
CancellationToken cancellationToken)
{
HandledMessage = message;
HandledContext = context;
return Task.CompletedTask;
}
public Task InvokeAsync(TestMessage message, RabbitMqMessageContext context)
{
return HandleMessageAsync(message, context, CancellationToken.None);
}
}
private sealed class LegacyConsumer(IOptionsMonitor<RabbitMqSettings> options)
: RabbitMqConsumerBase<TestMessage, string>(options, NullLogger.Instance)
{
protected override string QueueName => "messages.test";
public TestMessage? HandledMessage { get; private set; }
protected override Task HandleMessageAsync(TestMessage message, CancellationToken cancellationToken)
{
HandledMessage = message;
return Task.CompletedTask;
}
public Task InvokeAsync(TestMessage message, RabbitMqMessageContext context)
{
return HandleMessageAsync(message, context, CancellationToken.None);
}
public bool Validate(TestMessage message, RabbitMqMessageContext context, out string? error)
{
return TryValidateMessage(message, context, out error);
}
}
private sealed record TestMessage : IRabbitMqMessage<string>
{
public CorrelationContext CorrelationContext { get; set; } = null!;
public string Data { get; set; } = string.Empty;
}
private sealed class TestOptionsMonitor(RabbitMqSettings settings) : IOptionsMonitor<RabbitMqSettings>
{
public RabbitMqSettings CurrentValue => settings;
public RabbitMqSettings Get(string? name) => settings;
public IDisposable? OnChange(Action<RabbitMqSettings, string?> listener) => null;
}
}