Replaced GetOrThrow methods with a fluent GetOr/Throw pattern for handling null or empty MediatR responses. Introduced GetOrContext<TResponse> struct with Throw, ThrowNotFound, ThrowInvalidOperation, and ThrowBadRequest methods. Updated all tests to use the new API and added coverage for new exception types. Improved XML docs and performed minor code cleanup.
309 lines
9.3 KiB
C#
309 lines
9.3 KiB
C#
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<TResponse> : IRequest<TResponse?> { }
|
|
|
|
/// <summary>
|
|
/// Minimal <see cref="ISender"/> stub that returns a pre-configured response for any <see cref="ISender.Send{TResponse}"/> call.
|
|
/// </summary>
|
|
private sealed class StubSender : ISender
|
|
{
|
|
private readonly object? _response;
|
|
|
|
public StubSender(object? response) => _response = response;
|
|
|
|
public Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult((TResponse)_response!);
|
|
}
|
|
|
|
public Task Send<TRequest>(TRequest request, CancellationToken cancellationToken = default) where TRequest : IRequest
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task<object?> Send(object request, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult(_response);
|
|
}
|
|
|
|
public IAsyncEnumerable<TResponse> CreateStream<TResponse>(IStreamRequest<TResponse> request, CancellationToken cancellationToken = default)
|
|
=> throw new NotImplementedException();
|
|
|
|
public IAsyncEnumerable<object?> CreateStream(object request, CancellationToken cancellationToken = default)
|
|
=> throw new NotImplementedException();
|
|
}
|
|
|
|
private static ISender CreateSender<T>(T? response) => new StubSender(response);
|
|
|
|
#endregion
|
|
|
|
#region Throw — non-null scalar
|
|
|
|
[Test]
|
|
public async Task Throw_WithNonNullResponse_ReturnsResponse()
|
|
{
|
|
var sender = CreateSender<string>("hello");
|
|
var request = new StubRequest<string?>();
|
|
|
|
var result = await sender.GetOr(request).Throw(() => new InvalidOperationException());
|
|
|
|
Assert.That(result, Is.EqualTo("hello"));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Throw — null response
|
|
|
|
[Test]
|
|
public void Throw_WithNullResponse_ThrowsCustomException()
|
|
{
|
|
var sender = CreateSender<string>(null);
|
|
var request = new StubRequest<string?>();
|
|
|
|
var ex = Assert.ThrowsAsync<InvalidOperationException>(
|
|
() => sender.GetOr(request).Throw(() => new InvalidOperationException("custom")));
|
|
|
|
Assert.That(ex!.Message, Is.EqualTo("custom"));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Throw — empty collection
|
|
|
|
[Test]
|
|
public void Throw_WithEmptyList_ThrowsCustomException()
|
|
{
|
|
var sender = CreateSender<List<string>>(new List<string>());
|
|
var request = new StubRequest<List<string>?>();
|
|
|
|
Assert.ThrowsAsync<ArgumentException>(
|
|
() => sender.GetOr(request).Throw(() => new ArgumentException("empty")));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Throw — non-empty collection
|
|
|
|
[Test]
|
|
public async Task Throw_WithNonEmptyList_ReturnsResponse()
|
|
{
|
|
var expected = new List<int> { 1, 2 };
|
|
var sender = CreateSender<List<int>>(expected);
|
|
var request = new StubRequest<List<int>?>();
|
|
|
|
var result = await sender.GetOr(request).Throw(() => new InvalidOperationException());
|
|
|
|
Assert.That(result, Is.EqualTo(expected));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Throw — string edge case (string implements IEnumerable)
|
|
|
|
[Test]
|
|
public async Task Throw_WithEmptyString_ReturnsEmptyString()
|
|
{
|
|
var sender = CreateSender<string>("");
|
|
var request = new StubRequest<string?>();
|
|
|
|
var result = await sender.GetOr(request).Throw(() => new InvalidOperationException("should not throw"));
|
|
|
|
Assert.That(result, Is.EqualTo(""));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ThrowNotFound — non-null scalar
|
|
|
|
[Test]
|
|
public async Task ThrowNotFound_WithNonNullResponse_ReturnsResponse()
|
|
{
|
|
var sender = CreateSender<string>("hello");
|
|
var request = new StubRequest<string?>();
|
|
|
|
var result = await sender.GetOr(request).ThrowNotFound();
|
|
|
|
Assert.That(result, Is.EqualTo("hello"));
|
|
}
|
|
|
|
[Test]
|
|
public async Task ThrowNotFound_WithExceptionMessage_AndNonNullResponse_ReturnsResponse()
|
|
{
|
|
var sender = CreateSender<int>(42);
|
|
var request = new StubRequest<int?>();
|
|
|
|
var result = await sender.GetOr(request).ThrowNotFound("not found");
|
|
|
|
Assert.That(result, Is.EqualTo(42));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ThrowNotFound — null response
|
|
|
|
[Test]
|
|
public void ThrowNotFound_WithNullResponse_ThrowsNotFoundException()
|
|
{
|
|
var sender = CreateSender<string>(null);
|
|
var request = new StubRequest<string?>();
|
|
|
|
Assert.ThrowsAsync<NotFoundException>(() => sender.GetOr(request).ThrowNotFound());
|
|
}
|
|
|
|
[Test]
|
|
public void ThrowNotFound_WithNullResponse_AndCustomMessage_ContainsMessage()
|
|
{
|
|
const string message = "Entity not found";
|
|
var sender = CreateSender<string>(null);
|
|
var request = new StubRequest<string?>();
|
|
|
|
var ex = Assert.ThrowsAsync<NotFoundException>(
|
|
() => sender.GetOr(request).ThrowNotFound(message));
|
|
|
|
Assert.That(ex!.Message, Does.Contain(message));
|
|
}
|
|
|
|
[Test]
|
|
public void ThrowNotFound_WithNullResponse_HasDefaultMessageWithTypeName()
|
|
{
|
|
var sender = CreateSender<string>(null);
|
|
var request = new StubRequest<string?>();
|
|
|
|
var ex = Assert.ThrowsAsync<NotFoundException>(() => sender.GetOr(request).ThrowNotFound());
|
|
|
|
Assert.That(ex!.Message, Does.Contain(nameof(String)));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ThrowNotFound — empty collection
|
|
|
|
[Test]
|
|
public void ThrowNotFound_WithEmptyList_ThrowsNotFoundException()
|
|
{
|
|
var sender = CreateSender<List<string>>(new List<string>());
|
|
var request = new StubRequest<List<string>?>();
|
|
|
|
Assert.ThrowsAsync<NotFoundException>(() => sender.GetOr(request).ThrowNotFound());
|
|
}
|
|
|
|
[Test]
|
|
public void ThrowNotFound_WithEmptyArray_ThrowsNotFoundException()
|
|
{
|
|
var sender = CreateSender<int[]>(Array.Empty<int>());
|
|
var request = new StubRequest<int[]?>();
|
|
|
|
Assert.ThrowsAsync<NotFoundException>(() => sender.GetOr(request).ThrowNotFound());
|
|
}
|
|
|
|
[Test]
|
|
public void ThrowNotFound_WithEmptyCollection_AndCustomMessage_ContainsMessage()
|
|
{
|
|
const string message = "No items found";
|
|
var sender = CreateSender<List<int>>(new List<int>());
|
|
var request = new StubRequest<List<int>?>();
|
|
|
|
var ex = Assert.ThrowsAsync<NotFoundException>(
|
|
() => sender.GetOr(request).ThrowNotFound(message));
|
|
|
|
Assert.That(ex!.Message, Does.Contain(message));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ThrowNotFound — non-empty collection
|
|
|
|
[Test]
|
|
public async Task ThrowNotFound_WithNonEmptyList_ReturnsResponse()
|
|
{
|
|
var expected = new List<string> { "a", "b" };
|
|
var sender = CreateSender<List<string>>(expected);
|
|
var request = new StubRequest<List<string>?>();
|
|
|
|
var result = await sender.GetOr(request).ThrowNotFound();
|
|
|
|
Assert.That(result, Is.EqualTo(expected));
|
|
}
|
|
|
|
[Test]
|
|
public async Task ThrowNotFound_WithNonEmptyArray_ReturnsResponse()
|
|
{
|
|
var expected = new[] { 1, 2, 3 };
|
|
var sender = CreateSender<int[]>(expected);
|
|
var request = new StubRequest<int[]?>();
|
|
|
|
var result = await sender.GetOr(request).ThrowNotFound();
|
|
|
|
Assert.That(result, Is.EqualTo(expected));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ThrowInvalidOperation — null response
|
|
|
|
[Test]
|
|
public void ThrowInvalidOperation_WithNullResponse_ThrowsInvalidOperationException()
|
|
{
|
|
var sender = CreateSender<string>(null);
|
|
var request = new StubRequest<string?>();
|
|
|
|
Assert.ThrowsAsync<InvalidOperationException>(
|
|
() => sender.GetOr(request).ThrowInvalidOperation());
|
|
}
|
|
|
|
[Test]
|
|
public void ThrowInvalidOperation_WithNullResponse_AndCustomMessage_ContainsMessage()
|
|
{
|
|
const string message = "Something went wrong";
|
|
var sender = CreateSender<string>(null);
|
|
var request = new StubRequest<string?>();
|
|
|
|
var ex = Assert.ThrowsAsync<InvalidOperationException>(
|
|
() => sender.GetOr(request).ThrowInvalidOperation(message));
|
|
|
|
Assert.That(ex!.Message, Does.Contain(message));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CancellationToken
|
|
|
|
[Test]
|
|
public void Throw_WithCancelledToken_ThrowsOperationCanceledException()
|
|
{
|
|
var sender = CreateSender<string>("value");
|
|
var request = new StubRequest<string?>();
|
|
var cts = new CancellationTokenSource();
|
|
cts.Cancel();
|
|
|
|
Assert.ThrowsAsync<OperationCanceledException>(
|
|
() => sender.GetOr(request, cts.Token).Throw(() => new InvalidOperationException()));
|
|
}
|
|
|
|
[Test]
|
|
public void ThrowNotFound_WithCancelledToken_ThrowsOperationCanceledException()
|
|
{
|
|
var sender = CreateSender<string>("value");
|
|
var request = new StubRequest<string?>();
|
|
var cts = new CancellationTokenSource();
|
|
cts.Cancel();
|
|
|
|
Assert.ThrowsAsync<OperationCanceledException>(
|
|
() => sender.GetOr(request, cts.Token).ThrowNotFound());
|
|
}
|
|
|
|
#endregion
|
|
}
|