request/src/request.dispatcher.tests/ScalarDispatcherTests.cs

251 lines
9.2 KiB
C#

// Copyright (c) The Geekeey Authors
// SPDX-License-Identifier: EUPL-1.2
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Geekeey.Request.Dispatcher.Tests;
internal sealed class ScalarDispatcherTests
{
[Test]
public async Task I_can_dispatch_a_request_async_with_an_open_generic_handler()
{
var sc = new ServiceCollection();
sc.AddRequestDispatcher(builder => builder
.Add(typeof(OpenScalarHandler<>)));
var provider = sc.BuildServiceProvider();
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
var request = new OpenScalarRequest { Data = "Hello" };
var result = await dispatcher.DispatchAsync(request);
await Assert.That(result).IsEquivalentTo("Hello-Handled");
}
[Test]
public async Task I_can_dispatch_a_request_async_with_an_open_generic_handler_that_has_constraints()
{
var sc = new ServiceCollection();
sc.AddRequestDispatcher(builder => builder
.Add(typeof(ConstrainedScalarHandler<>)));
var provider = sc.BuildServiceProvider();
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
var request = new ConstrainedScalarRequest { Value = 123 };
var result = await dispatcher.DispatchAsync(request);
await Assert.That(result).IsEquivalentTo("123-Constrained");
}
[Test]
public async Task I_can_see_it_fail_if_no_handler_is_found_even_with_an_open_generic_available()
{
var sc = new ServiceCollection();
sc.AddRequestDispatcher(builder => builder
.Add(typeof(OpenScalarHandler<>)));
var provider = sc.BuildServiceProvider();
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
var request = new UnhandledScalarRequest();
await Assert.ThrowsAsync<InvalidOperationException>(async () => await dispatcher.DispatchAsync(request));
}
[Test]
public async Task I_can_dispatch_a_request_async_with_an_inherited_request()
{
var sc = new ServiceCollection();
sc.AddRequestDispatcher(builder => builder
.Add(typeof(OpenScalarHandler<>)));
var provider = sc.BuildServiceProvider();
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
// InheritedScalarRequest : OpenScalarRequest.
// There is no Handler<InheritedScalarRequest> but there is OpenScalarHandler<TRequest> where TRequest : OpenScalarRequest.
// It should be able to handle InheritedScalarRequest.
var request = new InheritedScalarRequest { Data = "Sub" };
var result = await dispatcher.DispatchAsync(request);
await Assert.That(result).IsEquivalentTo("Sub-Handled");
}
[Test]
public async Task I_can_dispatch_a_request_async_with_an_inherited_handler()
{
var sc = new ServiceCollection();
sc.AddRequestDispatcher(builder => builder
.Add(typeof(DerivedScalarHandler)));
var provider = sc.BuildServiceProvider();
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
var request = new DerivedScalarRequest { Value = 42 };
var result = await dispatcher.DispatchAsync(request);
await Assert.That(result).IsEquivalentTo("Derived: 42");
}
[Test]
public async Task I_can_dispatch_a_request_async_with_an_interface_inherited_request()
{
var sc = new ServiceCollection();
sc.AddRequestDispatcher(builder => builder
.Add(typeof(InterfaceInheritedScalarHandler)));
var provider = sc.BuildServiceProvider();
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
var request = new InterfaceInheritedScalarRequest { Name = "InterfaceTest" };
var result = await dispatcher.DispatchAsync(request);
await Assert.That(result).IsEquivalentTo("InterfaceTest-InterfaceHandled");
}
[Test]
public async Task I_can_dispatch_a_request_async_with_deep_inheritance_in_the_request()
{
var sc = new ServiceCollection();
sc.AddRequestDispatcher(builder => builder
.Add(typeof(OpenScalarHandler<>)));
var provider = sc.BuildServiceProvider();
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
var request = new DeepDerivedScalarRequest { Data = "Deep", DeepValue = 99 };
var result = await dispatcher.DispatchAsync(request);
// OpenScalarHandler<TRequest> where TRequest : OpenScalarRequest should handle this
await Assert.That(result).IsEquivalentTo("Deep-Handled");
}
[Test]
public async Task I_can_dispatch_a_request_async_with_an_interface_constrained_handler()
{
var sc = new ServiceCollection();
sc.AddRequestDispatcher(builder => builder
.Add(typeof(InterfaceInheritedScalarHandler))
.Add(typeof(InterfaceConstrainedScalarHandler<>)));
var provider = sc.BuildServiceProvider();
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
var request = new InterfaceInheritedScalarRequest { Name = "Constrained" };
var result = await dispatcher.DispatchAsync(request);
// Both InterfaceInheritedScalarHandler and InterfaceConstrainedScalarHandler could match.
// InterfaceInheritedScalarHandler is a concrete match for InterfaceInheritedScalarRequest.
// InterfaceConstrainedScalarHandler is an open generic match.
// Currently Dispatcher.SendAsync checks concrete handlers first.
await Assert.That(result).IsEquivalentTo("Constrained-InterfaceHandled");
}
[Test]
public async Task I_can_dispatch_a_request_async_with_an_interface_only_match()
{
var sc = new ServiceCollection();
sc.AddRequestDispatcher(builder => builder
.Add(typeof(InterfaceConstrainedScalarHandler<>)));
var provider = sc.BuildServiceProvider();
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
var request = new AnotherNamedScalarRequest { Name = "InterfaceOnly" };
var result = await dispatcher.DispatchAsync(request);
// No concrete handler for AnotherNamedScalarRequest, but InterfaceConstrainedScalarHandler<T> where T : INamedScalarRequest matches.
await Assert.That(result).IsEquivalentTo("InterfaceOnly-ConstrainedByInterface");
}
[Test]
public async Task I_can_dispatch_a_request_async_with_a_nested_generic_request()
{
var sc = new ServiceCollection();
sc.AddRequestDispatcher(builder => builder
.Add(typeof(WrapperScalarHandler<>)));
var provider = sc.BuildServiceProvider();
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
var request = new WrapperScalarRequest<int> { Item = 42 };
var result = await dispatcher.DispatchAsync(request);
await Assert.That(result).IsEquivalentTo("Handled-42");
}
[Test]
public async Task I_can_handle_multiple_interface_implementations()
{
var sc = new ServiceCollection();
sc.AddRequestDispatcher(builder => builder
.Add(typeof(MultiInterfaceScalarHandler)));
var provider = sc.BuildServiceProvider();
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
var request = new MultiInterfaceScalarRequest();
var result1 = await dispatcher.DispatchAsync<int>(request);
var result2 = await dispatcher.DispatchAsync<string>(request);
await Assert.That(result1).IsEqualTo(1);
await Assert.That(result2).IsEquivalentTo("One");
}
[Test]
public async Task I_can_see_it_fail_if_there_are_ambiguous_handle_methods()
{
var sc = new ServiceCollection();
sc.AddRequestDispatcher(builder => builder
.Add(typeof(AmbiguousScalarHandler)));
var provider = sc.BuildServiceProvider();
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
var request = new AmbiguousScalarRequest();
var result = await dispatcher.DispatchAsync(request);
await Assert.That(result).IsEquivalentTo("Interface-Handled");
}
[Test]
public async Task I_can_dispatch_a_request_async_with_a_generic_interface_explicit_implementation()
{
var sc = new ServiceCollection();
sc.AddRequestDispatcher(builder => builder
.Add(typeof(ExplicitGenericScalarHandler<>)));
var provider = sc.BuildServiceProvider();
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
var request = new ExplicitGenericScalarRequest { Value = "Explicit" };
var result = await dispatcher.DispatchAsync(request);
await Assert.That(result).IsEquivalentTo("Explicit-ExplicitHandled");
}
[Test]
public async Task I_can_see_it_throw_the_original_exception()
{
var sc = new ServiceCollection();
sc.AddRequestDispatcher(builder => builder
.Add(typeof(FailingScalarHandler)));
var provider = sc.BuildServiceProvider();
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
var request = new FailingScalarRequest();
var ex = await Assert.That(async () => await dispatcher.DispatchAsync(request)).Throws<InvalidOperationException>();
using (Assert.Multiple())
{
await Assert.That(ex?.Message).IsEquivalentTo("Handler failed");
// Assert that the stack trace contains the handler's method name,
// which proves the exception was rethrown while preserving its origin.
await Assert.That(ex?.StackTrace).Contains(nameof(FailingScalarHandler.HandleAsync));
}
}
[Test]
public async Task I_can_see_it_throw_if_dispatcher_options_are_modified_after_build()
{
var sc = new ServiceCollection();
sc.AddRequestDispatcher(builder => builder
.Add(typeof(FailingScalarHandler)));
var provider = sc.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<RequestDispatcherOptions>>().Value;
options.GetRequestBehaviors<IScalarRequestHandler<FailingScalarRequest, string>>(default!);
await Assert.That(() => options.Inspect([])).Throws<InvalidOperationException>();
}
}
// Moved to _fixtures