251 lines
9.2 KiB
C#
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
|