From 2f4c31650549f84180ff3ae19f07d41155a9f1ed Mon Sep 17 00:00:00 2001 From: Benjamin SPETH Date: Fri, 10 Apr 2026 13:32:40 +0200 Subject: [PATCH] Updated IMessageMetadataAccessor interface to allow to be decorated --- docs/CHANGELOG.md | 4 + .../IMessageMetadataAccessor.cs | 3 +- .../Management/MessageMetadataAccessor.cs | 2 +- .../Reception/MessageReceptionHandler.cs | 5 +- .../Ev.ServiceBus.UnitTests.csproj | 4 + .../MessageMetadataAccessorDecoratorTests.cs | 178 ++++++++++++++++++ 6 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 tests/Ev.ServiceBus.UnitTests/MessageMetadataAccessorDecoratorTests.cs diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4374a0e..01f021f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 5.7.1 +- Changed + - Updated IMessageMetadataAccessor interface to allow to be decorated + ## 5.7.0 - Changed - Updated IMessagePublisher and Dispatch contract to allow for setting more properties of the underlying ServiceBusMessage diff --git a/src/Ev.ServiceBus.Abstractions/MessageReception/IMessageMetadataAccessor.cs b/src/Ev.ServiceBus.Abstractions/MessageReception/IMessageMetadataAccessor.cs index cdc2892..37d67ab 100644 --- a/src/Ev.ServiceBus.Abstractions/MessageReception/IMessageMetadataAccessor.cs +++ b/src/Ev.ServiceBus.Abstractions/MessageReception/IMessageMetadataAccessor.cs @@ -2,5 +2,6 @@ public interface IMessageMetadataAccessor { - public IMessageMetadata? Metadata { get; } + IMessageMetadata? Metadata { get; } + void SetData(MessageContext context); } \ No newline at end of file diff --git a/src/Ev.ServiceBus/Management/MessageMetadataAccessor.cs b/src/Ev.ServiceBus/Management/MessageMetadataAccessor.cs index 667413d..aace08e 100644 --- a/src/Ev.ServiceBus/Management/MessageMetadataAccessor.cs +++ b/src/Ev.ServiceBus/Management/MessageMetadataAccessor.cs @@ -7,7 +7,7 @@ public class MessageMetadataAccessor : IMessageMetadataAccessor { public IMessageMetadata? Metadata { get; private set; } - internal void SetData(MessageContext context) + public void SetData(MessageContext context) { if (context.SessionArgs != null) { diff --git a/src/Ev.ServiceBus/Reception/MessageReceptionHandler.cs b/src/Ev.ServiceBus/Reception/MessageReceptionHandler.cs index f75e619..8af7f7a 100644 --- a/src/Ev.ServiceBus/Reception/MessageReceptionHandler.cs +++ b/src/Ev.ServiceBus/Reception/MessageReceptionHandler.cs @@ -9,7 +9,6 @@ using Ev.ServiceBus.Diagnostics; using Ev.ServiceBus.Exceptions; using Ev.ServiceBus.Isolation; -using Ev.ServiceBus.Management; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -20,7 +19,7 @@ public class MessageReceptionHandler private readonly MethodInfo _callHandlerInfo; private readonly IMessagePayloadSerializer _messagePayloadSerializer; private readonly ILogger _logger; - private readonly MessageMetadataAccessor _messageMetadataAccessor; + private readonly IMessageMetadataAccessor _messageMetadataAccessor; private readonly IEnumerable _eventListeners; private readonly IServiceProvider _provider; private readonly IsolationService _isolationService; @@ -36,7 +35,7 @@ public MessageReceptionHandler( _provider = provider; _messagePayloadSerializer = messagePayloadSerializer; _logger = logger; - _messageMetadataAccessor = (MessageMetadataAccessor)messageMetadataAccessor; + _messageMetadataAccessor = messageMetadataAccessor; _eventListeners = eventListeners; _callHandlerInfo = GetType().GetMethod(nameof(CallHandler), BindingFlags.NonPublic | BindingFlags.Instance)!; _isolationService = isolationService; diff --git a/tests/Ev.ServiceBus.UnitTests/Ev.ServiceBus.UnitTests.csproj b/tests/Ev.ServiceBus.UnitTests/Ev.ServiceBus.UnitTests.csproj index ba5ac9a..ed62131 100644 --- a/tests/Ev.ServiceBus.UnitTests/Ev.ServiceBus.UnitTests.csproj +++ b/tests/Ev.ServiceBus.UnitTests/Ev.ServiceBus.UnitTests.csproj @@ -7,6 +7,10 @@ enable + + + + diff --git a/tests/Ev.ServiceBus.UnitTests/MessageMetadataAccessorDecoratorTests.cs b/tests/Ev.ServiceBus.UnitTests/MessageMetadataAccessorDecoratorTests.cs new file mode 100644 index 0000000..597519b --- /dev/null +++ b/tests/Ev.ServiceBus.UnitTests/MessageMetadataAccessorDecoratorTests.cs @@ -0,0 +1,178 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Azure.Messaging.ServiceBus; +using Ev.ServiceBus.Abstractions; +using Ev.ServiceBus.Abstractions.MessageReception; +using Ev.ServiceBus.Reception; +using Ev.ServiceBus.TestHelpers; +using Ev.ServiceBus.UnitTests.Helpers; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Ev.ServiceBus.UnitTests; + +public class MessageMetadataAccessorDecoratorTests +{ + [Fact] + public async Task DecoratorSetDataIsCalledDuringReception() + { + var composer = new Composer(); + + composer.WithAdditionalServices(services => + { + services.AddSingleton(); + services.Decorate(); + services.RegisterServiceBusReception().FromQueue("testQueue", builder => + { + builder.RegisterReception(); + }); + }); + + var provider = await composer.Compose(); + var clientMock = provider.GetProcessorMock("testQueue"); + + await TriggerReception(clientMock); + + var tracker = provider.GetRequiredService(); + tracker.SetDataCallCount.Should().Be(1); + } + + [Fact] + public async Task DecoratorMetadataIsAccessibleFromHandler() + { + var composer = new Composer(); + + composer.WithAdditionalServices(services => + { + services.AddSingleton(); + services.AddSingleton>(); + services.Decorate(); + services.RegisterServiceBusReception().FromQueue("testQueue", builder => + { + builder.RegisterReception(); + }); + }); + + var provider = await composer.Compose(); + var clientMock = provider.GetProcessorMock("testQueue"); + + await TriggerReception(clientMock); + + var captured = provider.GetRequiredService>(); + captured.Count.Should().Be(1); + captured[0].Should().NotBeNull(); + captured[0]!.Subject.Should().Be("test subject"); + } + + [Fact] + public async Task DecoratorSetDataIsCalledDuringSessionReception() + { + var composer = new Composer(); + + composer.WithAdditionalServices(services => + { + services.AddSingleton(); + services.Decorate(); + services.RegisterServiceBusReception().FromQueue("testQueue", builder => + { + builder.EnableSessionHandling(options => { }); + builder.RegisterReception(); + }); + }); + + var provider = await composer.Compose(); + var clientMock = provider.GetSessionProcessorMock("testQueue"); + + await TriggerSessionReception(clientMock); + + var tracker = provider.GetRequiredService(); + tracker.SetDataCallCount.Should().Be(1); + } + + private static async Task TriggerReception(ProcessorMock client, CancellationToken cancellationToken = default) + { + var serializer = new TextJsonPayloadSerializer(); + var body = serializer.SerializeBody(new { }); + var message = new ServiceBusMessage(body.Body) + { + ContentType = body.ContentType, + Subject = "test subject", + ApplicationProperties = + { + { UserProperties.MessageTypeProperty, "IntegrationEvent" }, + { UserProperties.PayloadTypeIdProperty, "Payload" } + } + }; + await client.TriggerMessageReception(message, cancellationToken); + } + + private static async Task TriggerSessionReception(SessionProcessorMock client, CancellationToken cancellationToken = default) + { + var serializer = new TextJsonPayloadSerializer(); + var body = serializer.SerializeBody(new { }); + var message = new ServiceBusMessage(body.Body) + { + ContentType = body.ContentType, + Subject = "test subject", + ApplicationProperties = + { + { UserProperties.MessageTypeProperty, "IntegrationEvent" }, + { UserProperties.PayloadTypeIdProperty, "Payload" } + } + }; + await client.TriggerMessageReception(message, cancellationToken); + } + + internal class Payload { } + + private class NoopHandler : IMessageReceptionHandler + { + public Task Handle(Payload @event, CancellationToken cancellationToken) => Task.CompletedTask; + } + + private class MetadataCaptureHandler : IMessageReceptionHandler + { + private readonly IMessageMetadataAccessor _accessor; + private readonly List _captured; + + public MetadataCaptureHandler(IMessageMetadataAccessor accessor, List captured) + { + _accessor = accessor; + _captured = captured; + } + + public Task Handle(Payload @event, CancellationToken cancellationToken) + { + _captured.Add(_accessor.Metadata); + return Task.CompletedTask; + } + } + + internal class DecoratorCallTracker + { + public int SetDataCallCount { get; private set; } + public void RecordSetData() => SetDataCallCount++; + } + + private class TrackingMetadataAccessorDecorator : IMessageMetadataAccessor + { + private readonly IMessageMetadataAccessor _inner; + private readonly DecoratorCallTracker _tracker; + + public TrackingMetadataAccessorDecorator(IMessageMetadataAccessor inner, DecoratorCallTracker tracker) + { + _inner = inner; + _tracker = tracker; + } + + public IMessageMetadata? Metadata => _inner.Metadata; + + public void SetData(MessageContext context) + { + _tracker.RecordSetData(); + _inner.SetData(context); + } + } +} \ No newline at end of file