Skip to content

Commit 61dd689

Browse files
authored
Add InvokeHandlerAsync hook and test (#15)
Introduce a protected virtual InvokeHandlerAsync method on RoutedQueueHandler to allow subclasses to wrap handler execution with cross-cutting concerns (default implementation simply invokes the handler). Update the route dispatch to call InvokeHandlerAsync instead of invoking the handler directly. Add a unit test and WrappedInvocationHandler in RoutedQueueHandlerTests to verify the hook is used and that the resolved handler is still executed.
1 parent 332280e commit 61dd689

3 files changed

Lines changed: 70 additions & 4 deletions

File tree

DotQueue.Tests/RoutedQueueHandlerTests.cs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Collections.Generic;
1+
using System.Collections.Generic;
22
using System.Threading;
33
using System.Threading.Tasks;
44
using DotQueue;
@@ -57,6 +57,43 @@ private async Task HandleB(
5757
}
5858
}
5959

60+
private sealed class WrappedInvocationHandler : RoutedQueueHandler<TestMessage, Act>
61+
{
62+
public bool WasWrapped { get; private set; }
63+
public Act? LastAction { get; private set; }
64+
public int Calls { get; private set; }
65+
66+
public WrappedInvocationHandler(ILogger logger) : base(logger) { }
67+
68+
protected override Act GetAction(TestMessage message) => message.Action;
69+
70+
protected override void Configure(RouteBuilder r) => r
71+
.On(Act.A, HandleA);
72+
73+
protected override async Task InvokeHandlerAsync(
74+
Act action,
75+
HandlerDelegate handler,
76+
TestMessage message,
77+
IReadOnlyDictionary<string, string>? metadata,
78+
Func<Task> renewLock,
79+
CancellationToken ct)
80+
{
81+
WasWrapped = true;
82+
LastAction = action;
83+
await handler(message, metadata, renewLock, ct);
84+
}
85+
86+
private Task HandleA(
87+
TestMessage m,
88+
IReadOnlyDictionary<string, string>? meta,
89+
Func<Task> renew,
90+
CancellationToken ct)
91+
{
92+
Calls++;
93+
return Task.CompletedTask;
94+
}
95+
}
96+
6097
[Fact]
6198
public async Task Routes_To_Registered_Handlers_And_Passes_Metadata_And_RenewLock()
6299
{
@@ -93,4 +130,21 @@ public async Task Unknown_Action_Is_NonRetryable_From_DotQueue_Base()
93130
await act.Should().ThrowAsync<NonRetryableException>()
94131
.WithMessage("*No handler registered for action*");
95132
}
133+
134+
[Fact]
135+
public async Task HandleAsync_Uses_InvokeHandlerAsync_For_Resolved_Routes()
136+
{
137+
var logger = Mock.Of<ILogger>();
138+
var handler = new WrappedInvocationHandler(logger);
139+
140+
await handler.HandleAsync(
141+
new TestMessage { Action = Act.A },
142+
null,
143+
() => Task.CompletedTask,
144+
CancellationToken.None);
145+
146+
handler.WasWrapped.Should().BeTrue();
147+
handler.LastAction.Should().Be(Act.A);
148+
handler.Calls.Should().Be(1);
149+
}
96150
}

DotQueue/DotQueue.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<Nullable>enable</Nullable>
77
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
88
<PackageId>DotQueue</PackageId>
9-
<Version>1.1.0</Version>
9+
<Version>1.1.1</Version>
1010
<Authors>Alexander Kulyabin</Authors>
1111
<Company>Zionet</Company>
1212
<Description>Generic queue listener</Description>

DotQueue/RoutedQueueHandler.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.Extensions.Logging;
1+
using Microsoft.Extensions.Logging;
22

33
namespace DotQueue;
44

@@ -26,6 +26,18 @@ protected delegate Task HandlerDelegate(
2626

2727
protected void Register(TAction action, HandlerDelegate handler) => _routes[action] = handler;
2828

29+
/// <summary>
30+
/// Invokes the resolved handler for the given action.
31+
/// Override to wrap handler execution with cross-cutting concerns.
32+
/// </summary>
33+
protected virtual Task InvokeHandlerAsync(
34+
TAction action,
35+
HandlerDelegate handler,
36+
TMessage message,
37+
IReadOnlyDictionary<string, string>? metadata,
38+
Func<Task> renewLock,
39+
CancellationToken ct) => handler(message, metadata, renewLock, ct);
40+
2941
protected sealed class RouteBuilder
3042
{
3143
private readonly RoutedQueueHandler<TMessage, TAction> _owner;
@@ -48,7 +60,7 @@ public Task HandleAsync(
4860

4961
if (_routes.TryGetValue(action, out var handler))
5062
{
51-
return handler(message, metadata, renewLock, ct);
63+
return InvokeHandlerAsync(action, handler, message, metadata, renewLock, ct);
5264
}
5365

5466
_logger.LogWarning("No handler registered for action {Action}", action);

0 commit comments

Comments
 (0)