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(); // 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(); using var cts = new CancellationTokenSource(); cts.Cancel(); // Act & Assert await Assert.ThrowsAnyAsync( () => fixture.CommandSender.SendAsync(new SlowCommand(), cts.Token)); } [Fact] public async Task SendAsync_WithTimeout_ThrowsOperationCanceledException() { // Arrange using var fixture = CreateCommandSender(); using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(100)); // Act & Assert await Assert.ThrowsAnyAsync( () => fixture.CommandSender.SendAsync(new SlowCommand(), cts.Token)); } [Fact] public async Task SendAsync_WithSelfCancellingHandler_ThrowsTaskCanceledException() { // Arrange using var fixture = CreateCommandSender(); using var cts = new CancellationTokenSource(); // Act & Assert await Assert.ThrowsAsync( () => fixture.CommandSender.SendAsync(new SelfCancellingCommand(cts))); } [Fact] public async Task SendAsync_WithFailingHandler_ThrowsOriginalException() { // Arrange using var fixture = CreateCommandSender(); // Act & Assert var ex = await Assert.ThrowsAsync( () => fixture.CommandSender.SendAsync(new FailingCommand("Test error"))); Assert.Equal("Test error", ex.Message); } #region Helpers private static CommandSenderFixture CreateCommandSender() where THandler : class, ICommandHandler { var services = new ServiceCollection(); services.AddLogging(b => b.SetMinimumLevel(LogLevel.Warning)); services.AddMediator(); services.AddCommandHandler(); 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(); public void Dispose() { scope.Dispose(); provider.Dispose(); } } #endregion #region Test Commands public record EchoCommand(string Message) : ICommand; public record SlowCommand : ICommand; public record SelfCancellingCommand(CancellationTokenSource Cts) : ICommand; public record FailingCommand(string ErrorMessage) : ICommand; #endregion #region Test Handlers public class EchoCommandHandler : ICommandHandler { public Task HandleAsync(EchoCommand command, CancellationToken cancellationToken) => Task.FromResult(command.Message); } public class SlowCommandHandler : ICommandHandler { public async Task HandleAsync(SlowCommand command, CancellationToken cancellationToken) { await Task.Delay(TimeSpan.FromMilliseconds(500), cancellationToken); return Unit.Instance; } } public class SelfCancellingCommandHandler : ICommandHandler { public async Task HandleAsync(SelfCancellingCommand command, CancellationToken cancellationToken) { await command.Cts.CancelAsync(); await Task.Delay(1000, command.Cts.Token); return Unit.Instance; } } public class FailingCommandHandler : ICommandHandler { public Task HandleAsync(FailingCommand command, CancellationToken cancellationToken) => throw new InvalidOperationException(command.ErrorMessage); } #endregion }