-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Propagate exceptions from command handlers correctly #7218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
sfmskywalker
merged 5 commits into
elsa-workflows:main
from
avin3sh:avin3sh-fix-command-handler-invoker-exception-handling
Feb 9, 2026
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
219b352
Propagate exceptions from command handlers correctly
avin3sh 9583a9e
Address review comments
avin3sh ef1a631
Merge remote-tracking branch 'upstream/main' into avin3sh-fix-command…
avin3sh 7d571f9
Address review comments
avin3sh 9fa9cac
add ConfigureAwait(false) consistently to middlewares
avin3sh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
test/unit/Elsa.Mediator.UnitTests/CommandCancellationBehaviorTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| using Elsa.Mediator.Contracts; | ||
| using Elsa.Mediator.Models; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| namespace Elsa.Mediator.UnitTests; | ||
|
|
||
| public class CommandCancellationBehaviorTests | ||
| { | ||
| [Fact] | ||
| public async Task SendAsync_WithSuccessfulCommand_ReturnsResult() | ||
| { | ||
| // Arrange | ||
| using var fixture = CreateCommandSender<EchoCommandHandler>(); | ||
|
|
||
| // Act | ||
| var result = await fixture.CommandSender.SendAsync(new EchoCommand("Hello")); | ||
|
|
||
| // Assert | ||
| Assert.Equal("Hello", result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task SendAsync_WithCancelledToken_ThrowsOperationCanceledException() | ||
| { | ||
| // Arrange | ||
| using var fixture = CreateCommandSender<SlowCommandHandler>(); | ||
| using var cts = new CancellationTokenSource(); | ||
| cts.Cancel(); | ||
|
|
||
| // Act & Assert | ||
| await Assert.ThrowsAnyAsync<OperationCanceledException>( | ||
| () => fixture.CommandSender.SendAsync(new SlowCommand(), cts.Token)); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task SendAsync_WithTimeout_ThrowsOperationCanceledException() | ||
| { | ||
| // Arrange | ||
| using var fixture = CreateCommandSender<SlowCommandHandler>(); | ||
| using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(100)); | ||
|
|
||
| // Act & Assert | ||
| await Assert.ThrowsAnyAsync<OperationCanceledException>( | ||
| () => fixture.CommandSender.SendAsync(new SlowCommand(), cts.Token)); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task SendAsync_WithSelfCancellingHandler_ThrowsTaskCanceledException() | ||
| { | ||
| // Arrange | ||
| using var fixture = CreateCommandSender<SelfCancellingCommandHandler>(); | ||
| using var cts = new CancellationTokenSource(); | ||
|
|
||
| // Act & Assert | ||
| await Assert.ThrowsAsync<TaskCanceledException>( | ||
| () => fixture.CommandSender.SendAsync(new SelfCancellingCommand(cts))); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task SendAsync_WithFailingHandler_ThrowsOriginalException() | ||
| { | ||
| // Arrange | ||
| using var fixture = CreateCommandSender<FailingCommandHandler>(); | ||
|
|
||
| // Act & Assert | ||
| var ex = await Assert.ThrowsAsync<InvalidOperationException>( | ||
| () => fixture.CommandSender.SendAsync(new FailingCommand("Test error"))); | ||
|
|
||
| Assert.Equal("Test error", ex.Message); | ||
| } | ||
|
|
||
| #region Helpers | ||
|
|
||
| private static CommandSenderFixture CreateCommandSender<THandler>() where THandler : class, ICommandHandler | ||
| { | ||
| var services = new ServiceCollection(); | ||
| services.AddLogging(b => b.SetMinimumLevel(LogLevel.Warning)); | ||
| services.AddMediator(); | ||
| services.AddCommandHandler<THandler>(); | ||
|
|
||
| var provider = services.BuildServiceProvider(); | ||
| var scope = provider.CreateScope(); | ||
| return new CommandSenderFixture(provider, scope); | ||
| } | ||
|
|
||
| private sealed class CommandSenderFixture(ServiceProvider provider, IServiceScope scope) : IDisposable | ||
| { | ||
| public ICommandSender CommandSender => scope.ServiceProvider.GetRequiredService<ICommandSender>(); | ||
|
|
||
| public void Dispose() | ||
| { | ||
| scope.Dispose(); | ||
| provider.Dispose(); | ||
| } | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region Test Commands | ||
|
|
||
| public record EchoCommand(string Message) : ICommand<string>; | ||
| public record SlowCommand : ICommand; | ||
| public record SelfCancellingCommand(CancellationTokenSource Cts) : ICommand; | ||
| public record FailingCommand(string ErrorMessage) : ICommand; | ||
sfmskywalker marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| #endregion | ||
|
|
||
| #region Test Handlers | ||
|
|
||
| public class EchoCommandHandler : ICommandHandler<EchoCommand, string> | ||
| { | ||
| public Task<string> HandleAsync(EchoCommand command, CancellationToken cancellationToken) | ||
| => Task.FromResult(command.Message); | ||
| } | ||
|
|
||
| public class SlowCommandHandler : ICommandHandler<SlowCommand, Unit> | ||
| { | ||
| public async Task<Unit> HandleAsync(SlowCommand command, CancellationToken cancellationToken) | ||
| { | ||
| await Task.Delay(TimeSpan.FromMilliseconds(500), cancellationToken); | ||
| return Unit.Instance; | ||
| } | ||
| } | ||
|
|
||
| public class SelfCancellingCommandHandler : ICommandHandler<SelfCancellingCommand, Unit> | ||
| { | ||
| public async Task<Unit> HandleAsync(SelfCancellingCommand command, CancellationToken cancellationToken) | ||
| { | ||
| await command.Cts.CancelAsync(); | ||
| await Task.Delay(1000, command.Cts.Token); | ||
| return Unit.Instance; | ||
| } | ||
| } | ||
|
|
||
| public class FailingCommandHandler : ICommandHandler<FailingCommand, Unit> | ||
| { | ||
| public Task<Unit> HandleAsync(FailingCommand command, CancellationToken cancellationToken) | ||
| => throw new InvalidOperationException(command.ErrorMessage); | ||
| } | ||
sfmskywalker marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| #endregion | ||
| } | ||
12 changes: 12 additions & 0 deletions
12
test/unit/Elsa.Mediator.UnitTests/Elsa.Mediator.UnitTests.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <Include>[Elsa.Mediator]*</Include> | ||
| <Threshold>0</Threshold> | ||
avin3sh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| </PropertyGroup> | ||
sfmskywalker marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\..\src\common\Elsa.Mediator\Elsa.Mediator.csproj"/> | ||
| </ItemGroup> | ||
|
|
||
| </Project> | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.