// Copyright (c) The Geekeey Authors // SPDX-License-Identifier: EUPL-1.2 using Microsoft.Extensions.DependencyInjection; namespace Geekeey.Request.Dispatcher.Tests; public class StreamDispatcherTests { [Test] public async Task I_can_see_it_throw_on_ambiguous_stream_handlers() { var sc = new ServiceCollection(); sc.AddRequestDispatcher(builder => builder .Add(typeof(DuplicateStreamHandlerA)) .Add(typeof(DuplicateStreamHandlerB))); var provider = sc.BuildServiceProvider(); var dispatcher = provider.GetRequiredService(); var request = new DuplicateStreamRequest(); var ex = await Assert.That(async () => await dispatcher.DispatchAsync(request).ToListAsync()).Throws(); using (Assert.Multiple()) { await Assert.That(ex?.Message).Contains(nameof(DuplicateStreamHandlerA)); await Assert.That(ex?.Message).Contains(nameof(DuplicateStreamHandlerB)); } } [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(OpenStreamHandler<>))); var provider = sc.BuildServiceProvider(); var dispatcher = provider.GetRequiredService(); var request = new OpenStreamRequest { Data = "Hello" }; var results = await dispatcher.DispatchAsync(request).ToListAsync(); await Assert.That(results).IsEquivalentTo(["Hello-Stream-0", "Hello-Stream-1"]); } [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(ConstrainedStreamHandler<>))); var provider = sc.BuildServiceProvider(); var dispatcher = provider.GetRequiredService(); var request = new ConstrainedStreamRequest { Value = 123 }; var results = await dispatcher.DispatchAsync(request).ToListAsync(); await Assert.That(results).IsEquivalentTo(["123-Constrained-0", "123-Constrained-1"]); } [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(OpenStreamHandler<>))); var provider = sc.BuildServiceProvider(); var dispatcher = provider.GetRequiredService(); var request = new UnhandledStreamRequest(); await Assert.ThrowsAsync(async () => await dispatcher.DispatchAsync(request).FirstOrDefaultAsync().AsTask()); } [Test] public async Task I_can_dispatch_a_request_async_with_an_inherited_request() { var sc = new ServiceCollection(); sc.AddRequestDispatcher(builder => builder .Add(typeof(OpenStreamHandler<>))); var provider = sc.BuildServiceProvider(); var dispatcher = provider.GetRequiredService(); var request = new InheritedStreamRequest { Data = "Sub" }; var results = await dispatcher.DispatchAsync(request).ToListAsync(); await Assert.That(results).IsEquivalentTo(["Sub-Stream-0", "Sub-Stream-1"]); } [Test] public async Task I_can_dispatch_a_request_async_with_an_inherited_handler() { var sc = new ServiceCollection(); sc.AddRequestDispatcher(builder => builder .Add(typeof(DerivedStreamHandler))); var provider = sc.BuildServiceProvider(); var dispatcher = provider.GetRequiredService(); var request = new DerivedStreamRequest { Value = 42 }; var results = await dispatcher.DispatchAsync(request).ToListAsync(); await Assert.That(results).IsEquivalentTo(["Derived: 42-0", "Derived: 42-1"]); } [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(InterfaceInheritedStreamHandler))); var provider = sc.BuildServiceProvider(); var dispatcher = provider.GetRequiredService(); var request = new InterfaceInheritedStreamRequest { Name = "InterfaceTest" }; var results = await dispatcher.DispatchAsync(request).ToListAsync(); await Assert.That(results).IsEquivalentTo(["InterfaceTest-InterfaceHandled-0", "InterfaceTest-InterfaceHandled-1"]); } [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(OpenStreamHandler<>))); var provider = sc.BuildServiceProvider(); var dispatcher = provider.GetRequiredService(); var request = new DeepDerivedStreamRequest { Data = "Deep", DeepValue = 99 }; var results = await dispatcher.DispatchAsync(request).ToListAsync(); await Assert.That(results).IsEquivalentTo(["Deep-Stream-0", "Deep-Stream-1"]); } [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(InterfaceInheritedStreamHandler)) .Add(typeof(InterfaceConstrainedStreamHandler<>))); var provider = sc.BuildServiceProvider(); var dispatcher = provider.GetRequiredService(); var request = new InterfaceInheritedStreamRequest { Name = "Constrained" }; // Both InterfaceInheritedStreamHandler and InterfaceConstrainedStreamHandler could match. // InterfaceInheritedStreamHandler is a concrete match for InterfaceInheritedStreamRequest. // InterfaceConstrainedStreamHandler is an open generic match. // Ambiguity is no longer resolved silently with .First(); it throws instead. var ex = await Assert.That(async () => await dispatcher.DispatchAsync(request).ToListAsync()).Throws(); using (Assert.Multiple()) { await Assert.That(ex?.Message).Contains(nameof(InterfaceInheritedStreamHandler)); await Assert.That(ex?.Message).Contains("InterfaceConstrainedStreamHandler"); } } [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(InterfaceConstrainedStreamHandler<>))); var provider = sc.BuildServiceProvider(); var dispatcher = provider.GetRequiredService(); var request = new AnotherNamedStreamRequest { Name = "InterfaceOnly" }; var results = await dispatcher.DispatchAsync(request).ToListAsync(); await Assert.That(results).IsEquivalentTo(["InterfaceOnly-ConstrainedByInterface-0", "InterfaceOnly-ConstrainedByInterface-1"]); } [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(WrapperStreamHandler<>))); var provider = sc.BuildServiceProvider(); var dispatcher = provider.GetRequiredService(); var request = new WrapperStreamRequest { Item = 42 }; var results = await dispatcher.DispatchAsync(request).ToListAsync(); await Assert.That(results).IsEquivalentTo(["Handled-42-0", "Handled-42-1"]); } [Test] public async Task I_can_handle_multiple_interface_implementations() { var sc = new ServiceCollection(); sc.AddRequestDispatcher(builder => builder .Add(typeof(MultiInterfaceStreamHandler))); var provider = sc.BuildServiceProvider(); var dispatcher = provider.GetRequiredService(); var request = new MultiInterfaceStreamRequest(); var results1 = await dispatcher.DispatchAsync(request).ToListAsync(); var results2 = await dispatcher.DispatchAsync(request).ToListAsync(); await Assert.That(results1).IsEquivalentTo([1, 2]); await Assert.That(results2).IsEquivalentTo(["One", "Two"]); } [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(AmbiguousStreamHandler))); var provider = sc.BuildServiceProvider(); var dispatcher = provider.GetRequiredService(); var request = new AmbiguousStreamRequest(); var results = await dispatcher.DispatchAsync(request).ToListAsync(); await Assert.That(results).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(ExplicitGenericStreamHandler<>))); var provider = sc.BuildServiceProvider(); var dispatcher = provider.GetRequiredService(); var request = new ExplicitGenericStreamRequest { Value = "Explicit" }; var results = await dispatcher.DispatchAsync(request).ToListAsync(); await Assert.That(results).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(FailingStreamHandler))); var provider = sc.BuildServiceProvider(); var dispatcher = provider.GetRequiredService(); var request = new FailingStreamRequest(); var enumerable = dispatcher.DispatchAsync(request); var ex = await Assert.ThrowsAsync(async () => await enumerable.ToListAsync().AsTask()); using (Assert.Multiple()) { await Assert.That(ex?.Message).IsEquivalentTo("Handler failed"); await Assert.That(ex?.StackTrace).Contains(nameof(FailingStreamHandler.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(FailingStreamHandler))); var provider = sc.BuildServiceProvider(); var options = provider.GetRequiredService>().Value; options.GetRequestHandlers>(default!); await Assert.That(() => options.Inspect([])).Throws(); } }