From ce9958a8b16a760c40fec2c03ae73bc2c4c068ff Mon Sep 17 00:00:00 2001 From: TekH Date: Wed, 8 Apr 2026 13:46:27 +0200 Subject: [PATCH] Add MediatorExtensions tests and refactor CreateEnvelopeCommand Introduce MediatorExtensionsTests to cover SendOrThrowAsync and SendOrNotFoundAsync extension methods for IMediator, including edge cases and cancellation. Refactor CreateEnvelopeCommand in Fake.cs to use Authorize(userId) instead of setting UserId directly. Add test stubs for IMediator and IRequest to support isolated testing. --- EnvelopeGenerator.Tests/Application/Fake.cs | 16 +- .../Application/MediatorExtensionsTests.cs | 287 ++++++++++++++++++ 2 files changed, 297 insertions(+), 6 deletions(-) create mode 100644 EnvelopeGenerator.Tests/Application/MediatorExtensionsTests.cs diff --git a/EnvelopeGenerator.Tests/Application/Fake.cs b/EnvelopeGenerator.Tests/Application/Fake.cs index 1281e7bb..5eed8052 100644 --- a/EnvelopeGenerator.Tests/Application/Fake.cs +++ b/EnvelopeGenerator.Tests/Application/Fake.cs @@ -193,13 +193,17 @@ public static class Extensions #endregion #region Envelope - public static CreateEnvelopeCommand CreateEnvelopeCommand(this Faker fake, int userId) => new() + public static CreateEnvelopeCommand CreateEnvelopeCommand(this Faker fake, int userId) { - Message = fake.Lorem.Paragraph(fake.Random.Number(2, 5)), - Title = fake.Lorem.Words(fake.Random.Number(3, 4)).Join(" "), - UserId = userId, - UseSQLExecutor = false - }; + var cmd = new CreateEnvelopeCommand + { + Message = fake.Lorem.Paragraph(fake.Random.Number(2, 5)), + Title = fake.Lorem.Words(fake.Random.Number(3, 4)).Join(" "), + UseSQLExecutor = false + }; + cmd.Authorize(userId); + return cmd; + } public static List CreateEnvelopeCommands(this Faker fake, params int[] userIDs) => Enumerable.Range(0, userIDs.Length) diff --git a/EnvelopeGenerator.Tests/Application/MediatorExtensionsTests.cs b/EnvelopeGenerator.Tests/Application/MediatorExtensionsTests.cs new file mode 100644 index 00000000..ca28f61e --- /dev/null +++ b/EnvelopeGenerator.Tests/Application/MediatorExtensionsTests.cs @@ -0,0 +1,287 @@ +using DigitalData.Core.Exceptions; +using EnvelopeGenerator.Application.Common.Extensions; +using MediatR; + +namespace EnvelopeGenerator.Tests.Application; + +[TestFixture] +public class MediatorExtensionsTests +{ + #region Stub infrastructure + + private sealed class StubRequest : IRequest { } + + /// + /// Minimal stub that returns a pre-configured response for any call. + /// + private sealed class StubMediator : IMediator + { + private readonly object? _response; + + public StubMediator(object? response) => _response = response; + + public Task Send(IRequest request, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.FromResult((TResponse)_response!); + } + + public Task Send(TRequest request, CancellationToken cancellationToken = default) where TRequest : IRequest + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public Task Send(object request, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.FromResult(_response); + } + + public IAsyncEnumerable CreateStream(IStreamRequest request, CancellationToken cancellationToken = default) + => throw new NotImplementedException(); + + public IAsyncEnumerable CreateStream(object request, CancellationToken cancellationToken = default) + => throw new NotImplementedException(); + + public Task Publish(object notification, CancellationToken cancellationToken = default) + => Task.CompletedTask; + + public Task Publish(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification + => Task.CompletedTask; + } + + private static IMediator CreateMediator(T? response) => new StubMediator(response); + + #endregion + + #region SendOrThrowAsync — non-null scalar + + [Test] + public async Task SendOrThrowAsync_WithNonNullResponse_ReturnsResponse() + { + var mediator = CreateMediator("hello"); + var request = new StubRequest(); + + var result = await mediator.SendOrThrowAsync(request, () => new InvalidOperationException()); + + Assert.That(result, Is.EqualTo("hello")); + } + + #endregion + + #region SendOrThrowAsync — null response + + [Test] + public void SendOrThrowAsync_WithNullResponse_ThrowsCustomException() + { + var mediator = CreateMediator(null); + var request = new StubRequest(); + + var ex = Assert.ThrowsAsync( + () => mediator.SendOrThrowAsync(request, () => new InvalidOperationException("custom"))); + + Assert.That(ex!.Message, Is.EqualTo("custom")); + } + + #endregion + + #region SendOrThrowAsync — empty collection + + [Test] + public void SendOrThrowAsync_WithEmptyList_ThrowsCustomException() + { + var mediator = CreateMediator>(new List()); + var request = new StubRequest?>(); + + Assert.ThrowsAsync( + () => mediator.SendOrThrowAsync(request, () => new ArgumentException("empty"))); + } + + #endregion + + #region SendOrThrowAsync — non-empty collection + + [Test] + public async Task SendOrThrowAsync_WithNonEmptyList_ReturnsResponse() + { + var expected = new List { 1, 2 }; + var mediator = CreateMediator>(expected); + var request = new StubRequest?>(); + + var result = await mediator.SendOrThrowAsync(request, () => new InvalidOperationException()); + + Assert.That(result, Is.EqualTo(expected)); + } + + #endregion + + #region SendOrThrowAsync — string edge case (string implements IEnumerable) + + [Test] + public async Task SendOrThrowAsync_WithEmptyString_ReturnsEmptyString() + { + var mediator = CreateMediator(""); + var request = new StubRequest(); + + var result = await mediator.SendOrThrowAsync(request, () => new InvalidOperationException("should not throw")); + + Assert.That(result, Is.EqualTo("")); + } + + #endregion + + #region SendOrNotFoundAsync — non-null scalar + + [Test] + public async Task SendOrNotFoundAsync_WithNonNullResponse_ReturnsResponse() + { + var mediator = CreateMediator("hello"); + var request = new StubRequest(); + + var result = await mediator.SendOrNotFoundAsync(request); + + Assert.That(result, Is.EqualTo("hello")); + } + + [Test] + public async Task SendOrNotFoundAsync_WithExceptionMessage_AndNonNullResponse_ReturnsResponse() + { + var mediator = CreateMediator(42); + var request = new StubRequest(); + + var result = await mediator.SendOrNotFoundAsync(request, "not found", CancellationToken.None); + + Assert.That(result, Is.EqualTo(42)); + } + + #endregion + + #region SendOrNotFoundAsync — null response + + [Test] + public void SendOrNotFoundAsync_WithNullResponse_ThrowsNotFoundException() + { + var mediator = CreateMediator(null); + var request = new StubRequest(); + + Assert.ThrowsAsync(() => mediator.SendOrNotFoundAsync(request)); + } + + [Test] + public void SendOrNotFoundAsync_WithNullResponse_AndCustomMessage_ThrowsNotFoundExceptionWithMessage() + { + const string message = "Entity not found"; + var mediator = CreateMediator(null); + var request = new StubRequest(); + + var ex = Assert.ThrowsAsync( + () => mediator.SendOrNotFoundAsync(request, message, CancellationToken.None)); + + Assert.That(ex!.Message, Does.Contain(message)); + } + + [Test] + public void SendOrNotFoundAsync_WithNullResponse_HasDefaultMessage() + { + var mediator = CreateMediator(null); + var request = new StubRequest(); + + var ex = Assert.ThrowsAsync(() => mediator.SendOrNotFoundAsync(request)); + + Assert.That(ex!.Message, Does.Contain(nameof(String))); + } + + #endregion + + #region SendOrNotFoundAsync — empty collection + + [Test] + public void SendOrNotFoundAsync_WithEmptyList_ThrowsNotFoundException() + { + var mediator = CreateMediator>(new List()); + var request = new StubRequest?>(); + + Assert.ThrowsAsync(() => mediator.SendOrNotFoundAsync(request)); + } + + [Test] + public void SendOrNotFoundAsync_WithEmptyArray_ThrowsNotFoundException() + { + var mediator = CreateMediator(Array.Empty()); + var request = new StubRequest(); + + Assert.ThrowsAsync(() => mediator.SendOrNotFoundAsync(request)); + } + + [Test] + public void SendOrNotFoundAsync_WithEmptyCollection_AndCustomMessage_ThrowsNotFoundExceptionWithMessage() + { + const string message = "No items found"; + var mediator = CreateMediator>(new List()); + var request = new StubRequest?>(); + + var ex = Assert.ThrowsAsync( + () => mediator.SendOrNotFoundAsync(request, message, CancellationToken.None)); + + Assert.That(ex!.Message, Does.Contain(message)); + } + + #endregion + + #region SendOrNotFoundAsync — non-empty collection + + [Test] + public async Task SendOrNotFoundAsync_WithNonEmptyList_ReturnsResponse() + { + var expected = new List { "a", "b" }; + var mediator = CreateMediator>(expected); + var request = new StubRequest?>(); + + var result = await mediator.SendOrNotFoundAsync(request); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public async Task SendOrNotFoundAsync_WithNonEmptyArray_ReturnsResponse() + { + var expected = new[] { 1, 2, 3 }; + var mediator = CreateMediator(expected); + var request = new StubRequest(); + + var result = await mediator.SendOrNotFoundAsync(request); + + Assert.That(result, Is.EqualTo(expected)); + } + + #endregion + + #region CancellationToken + + [Test] + public void SendOrThrowAsync_WithCancelledToken_ThrowsOperationCanceledException() + { + var mediator = CreateMediator("value"); + var request = new StubRequest(); + var cts = new CancellationTokenSource(); + cts.Cancel(); + + Assert.ThrowsAsync( + () => mediator.SendOrThrowAsync(request, () => new InvalidOperationException(), cts.Token)); + } + + [Test] + public void SendOrNotFoundAsync_WithCancelledToken_ThrowsOperationCanceledException() + { + var mediator = CreateMediator("value"); + var request = new StubRequest(); + var cts = new CancellationTokenSource(); + cts.Cancel(); + + Assert.ThrowsAsync( + () => mediator.SendOrNotFoundAsync(request, cts.Token)); + } + + #endregion +}