From b7006b2e354e2d4de229726b714eacbeb441836f Mon Sep 17 00:00:00 2001 From: Renato Golia Date: Thu, 28 Feb 2019 01:37:47 +0100 Subject: [PATCH] Add Nybus.Extensions.Hosting.Lambda package (#84) --- Nybus.sln | 30 ++++++ build.cake | 20 +++- .../Nybus.Extensions.Hosting.Lambda.csproj | 20 ++++ .../NybusFunction.cs | 50 +++++++++ .../NybusFunctionTests.cs | 102 ++++++++++++++++++ .../TestFunction.cs | 83 ++++++++++++++ ...sts.Nybus.Extensions.Hosting.Lambda.csproj | 20 ++++ 7 files changed, 321 insertions(+), 4 deletions(-) create mode 100644 src/extensions/hosting/Nybus.Extensions.Hosting.Lambda/Nybus.Extensions.Hosting.Lambda.csproj create mode 100644 src/extensions/hosting/Nybus.Extensions.Hosting.Lambda/NybusFunction.cs create mode 100644 tests/Tests.Nybus.Extensions.Hosting.Lambda/NybusFunctionTests.cs create mode 100644 tests/Tests.Nybus.Extensions.Hosting.Lambda/TestFunction.cs create mode 100644 tests/Tests.Nybus.Extensions.Hosting.Lambda/Tests.Nybus.Extensions.Hosting.Lambda.csproj diff --git a/Nybus.sln b/Nybus.sln index 2a83d56..3447f8a 100644 --- a/Nybus.sln +++ b/Nybus.sln @@ -67,6 +67,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestUtils", "tests\TestUtil EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Nybus.Extensions.Hosting.TopShelf", "tests\Tests.Nybus.Extensions.Hosting.TopShelf\Tests.Nybus.Extensions.Hosting.TopShelf.csproj", "{0ADB6705-0DC9-4C01-ACB5-2DCB5C153789}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nybus.Extensions.Hosting.Lambda", "src\extensions\hosting\Nybus.Extensions.Hosting.Lambda\Nybus.Extensions.Hosting.Lambda.csproj", "{D970A97C-44CB-45C5-B233-8086BE9FBC18}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Nybus.Extensions.Hosting.Lambda", "tests\Tests.Nybus.Extensions.Hosting.Lambda\Tests.Nybus.Extensions.Hosting.Lambda.csproj", "{DF6EDE99-73F0-4B8A-B43F-3CD75E0058AE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -356,6 +360,30 @@ Global {0ADB6705-0DC9-4C01-ACB5-2DCB5C153789}.Release|x64.Build.0 = Release|Any CPU {0ADB6705-0DC9-4C01-ACB5-2DCB5C153789}.Release|x86.ActiveCfg = Release|Any CPU {0ADB6705-0DC9-4C01-ACB5-2DCB5C153789}.Release|x86.Build.0 = Release|Any CPU + {D970A97C-44CB-45C5-B233-8086BE9FBC18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D970A97C-44CB-45C5-B233-8086BE9FBC18}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D970A97C-44CB-45C5-B233-8086BE9FBC18}.Debug|x64.ActiveCfg = Debug|Any CPU + {D970A97C-44CB-45C5-B233-8086BE9FBC18}.Debug|x64.Build.0 = Debug|Any CPU + {D970A97C-44CB-45C5-B233-8086BE9FBC18}.Debug|x86.ActiveCfg = Debug|Any CPU + {D970A97C-44CB-45C5-B233-8086BE9FBC18}.Debug|x86.Build.0 = Debug|Any CPU + {D970A97C-44CB-45C5-B233-8086BE9FBC18}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D970A97C-44CB-45C5-B233-8086BE9FBC18}.Release|Any CPU.Build.0 = Release|Any CPU + {D970A97C-44CB-45C5-B233-8086BE9FBC18}.Release|x64.ActiveCfg = Release|Any CPU + {D970A97C-44CB-45C5-B233-8086BE9FBC18}.Release|x64.Build.0 = Release|Any CPU + {D970A97C-44CB-45C5-B233-8086BE9FBC18}.Release|x86.ActiveCfg = Release|Any CPU + {D970A97C-44CB-45C5-B233-8086BE9FBC18}.Release|x86.Build.0 = Release|Any CPU + {DF6EDE99-73F0-4B8A-B43F-3CD75E0058AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF6EDE99-73F0-4B8A-B43F-3CD75E0058AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF6EDE99-73F0-4B8A-B43F-3CD75E0058AE}.Debug|x64.ActiveCfg = Debug|Any CPU + {DF6EDE99-73F0-4B8A-B43F-3CD75E0058AE}.Debug|x64.Build.0 = Debug|Any CPU + {DF6EDE99-73F0-4B8A-B43F-3CD75E0058AE}.Debug|x86.ActiveCfg = Debug|Any CPU + {DF6EDE99-73F0-4B8A-B43F-3CD75E0058AE}.Debug|x86.Build.0 = Debug|Any CPU + {DF6EDE99-73F0-4B8A-B43F-3CD75E0058AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF6EDE99-73F0-4B8A-B43F-3CD75E0058AE}.Release|Any CPU.Build.0 = Release|Any CPU + {DF6EDE99-73F0-4B8A-B43F-3CD75E0058AE}.Release|x64.ActiveCfg = Release|Any CPU + {DF6EDE99-73F0-4B8A-B43F-3CD75E0058AE}.Release|x64.Build.0 = Release|Any CPU + {DF6EDE99-73F0-4B8A-B43F-3CD75E0058AE}.Release|x86.ActiveCfg = Release|Any CPU + {DF6EDE99-73F0-4B8A-B43F-3CD75E0058AE}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {48BAF0EF-F200-4809-9ED7-E2B67CE8DD8A} = {BFE4A74D-7641-4C6B-8D39-14173BB82320} @@ -387,5 +415,7 @@ Global {25F804E9-63D1-48F2-AFB6-B9DD065A2786} = {AF1E016D-895D-48F1-8690-C49AB8F926B2} {78F69BA5-F7F0-45AB-A268-CDFCCAF93FA1} = {AF1E016D-895D-48F1-8690-C49AB8F926B2} {0ADB6705-0DC9-4C01-ACB5-2DCB5C153789} = {AF1E016D-895D-48F1-8690-C49AB8F926B2} + {D970A97C-44CB-45C5-B233-8086BE9FBC18} = {51A43FF2-AD6A-44CB-AE93-7FC832BFA57E} + {DF6EDE99-73F0-4B8A-B43F-3CD75E0058AE} = {AF1E016D-895D-48F1-8690-C49AB8F926B2} EndGlobalSection EndGlobal diff --git a/build.cake b/build.cake index bc548e4..7264254 100644 --- a/build.cake +++ b/build.cake @@ -80,15 +80,17 @@ Task("RunTests") bool success = true; - foreach (var framework in frameworks) + foreach (var file in projectFiles) { - var frameworkFriendlyName = framework.Replace(".", "-"); + var targetFrameworks = GetTargetFrameworks(file); - foreach (var file in projectFiles) + foreach (var framework in targetFrameworks) { + var frameworkFriendlyName = framework.Replace(".", "-"); + try { - Information($"Testing {file.GetFilenameWithoutExtension()}"); + Information($"Testing {file.GetFilenameWithoutExtension()} ({framework})"); var testResultFile = state.Paths.TestOutputFolder.CombineWithFilePath($"{file.GetFilenameWithoutExtension()}-{frameworkFriendlyName}.trx"); var coverageResultFile = state.Paths.TestOutputFolder.CombineWithFilePath($"{file.GetFilenameWithoutExtension()}-{frameworkFriendlyName}.dcvr"); @@ -125,6 +127,16 @@ Task("RunTests") { throw new CakeException("There was an error while executing the tests"); } + + string[] GetTargetFrameworks(FilePath file) + { + XmlPeekSettings settings = new XmlPeekSettings + { + SuppressWarning = true + }; + + return (XmlPeek(file, "/Project/PropertyGroup/TargetFrameworks", settings) ?? XmlPeek(file, "/Project/PropertyGroup/TargetFramework", settings)).Split(";"); + } }); Task("MergeCoverageResults") diff --git a/src/extensions/hosting/Nybus.Extensions.Hosting.Lambda/Nybus.Extensions.Hosting.Lambda.csproj b/src/extensions/hosting/Nybus.Extensions.Hosting.Lambda/Nybus.Extensions.Hosting.Lambda.csproj new file mode 100644 index 0000000..a021fa1 --- /dev/null +++ b/src/extensions/hosting/Nybus.Extensions.Hosting.Lambda/Nybus.Extensions.Hosting.Lambda.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.0 + Nybus + Nybus.Extensions.Hosting.Lambda + + Nybus hosting in AWS Lambda + dotnet-standard;queue;rx;nybus;aws-lambda;lambda + + + + + + + + + + + diff --git a/src/extensions/hosting/Nybus.Extensions.Hosting.Lambda/NybusFunction.cs b/src/extensions/hosting/Nybus.Extensions.Hosting.Lambda/NybusFunction.cs new file mode 100644 index 0000000..c6509fb --- /dev/null +++ b/src/extensions/hosting/Nybus.Extensions.Hosting.Lambda/NybusFunction.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading.Tasks; +using Amazon.Lambda.Core; +using Kralizek.Lambda; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Nybus +{ + public abstract class NybusFunction : Function + { + protected NybusFunction() + { + _busHost = ServiceProvider.GetRequiredService(); + } + + private readonly IBusHost _busHost; + + public async Task FunctionHandlerAsync(TInput input, ILambdaContext context) + { + try + { + await _busHost.StartAsync().ConfigureAwait(false); + + using (var scope = ServiceProvider.CreateScope()) + { + var handler = scope.ServiceProvider.GetService>(); + + if (handler == null) + { + Logger.LogCritical($"No IEventHandler<{typeof(TInput).Name}> could be found."); + throw new InvalidOperationException($"No IEventHandler<{typeof(TInput).Name}> could be found."); + } + + Logger.LogInformation("Invoking handler"); + await handler.HandleAsync(input, context).ConfigureAwait(false); + } + } + finally + { + await _busHost.StopAsync().ConfigureAwait(false); + } + } + + protected void RegisterHandler(IServiceCollection services) where THandler : class, Kralizek.Lambda.IEventHandler + { + services.AddTransient, THandler>(); + } + } +} diff --git a/tests/Tests.Nybus.Extensions.Hosting.Lambda/NybusFunctionTests.cs b/tests/Tests.Nybus.Extensions.Hosting.Lambda/NybusFunctionTests.cs new file mode 100644 index 0000000..5742ef2 --- /dev/null +++ b/tests/Tests.Nybus.Extensions.Hosting.Lambda/NybusFunctionTests.cs @@ -0,0 +1,102 @@ +using System; +using System.Threading.Tasks; +using Amazon.Lambda.Core; +using Moq; +using NUnit.Framework; + +namespace Tests +{ + [TestFixture] + public class NybusFunctionTests + { + [Test, AutoMoqData] + public void TestFunction_is_set_up(TestFunction sut) + { + Assert.That(sut.BusHost, Is.Not.Null); + + Assert.That(sut.Handler, Is.Not.Null); + } + + [Test, AutoMoqData] + public void TestFunctionWithHandler_is_set_up(TestFunctionWithHandler sut) + { + Assert.That(sut.BusHost, Is.Not.Null); + } + + [Test, AutoMoqData] + public void TestFunctionWithNoHandler_is_set_up(TestFunctionWithNoHandler sut) + { + Assert.That(sut.BusHost, Is.Not.Null); + } + + [Test, AutoMoqData] + public async Task FunctionHandlerAsync_starts_bus(TestFunction sut, string payload, ILambdaContext context) + { + await sut.FunctionHandlerAsync(payload, context); + + Mock.Get(sut.BusHost).Verify(p => p.StartAsync()); + } + + [Test, AutoMoqData] + public async Task FunctionHandlerAsync_stops_bus(TestFunction sut, string payload, ILambdaContext context) + { + await sut.FunctionHandlerAsync(payload, context); + + Mock.Get(sut.BusHost).Verify(p => p.StopAsync()); + } + + [Test, AutoMoqData] + public async Task FunctionHandlerAsync_starts_bus(TestFunctionWithHandler sut, string payload, ILambdaContext context) + { + await sut.FunctionHandlerAsync(payload, context); + + Mock.Get(sut.BusHost).Verify(p => p.StartAsync()); + } + + [Test, AutoMoqData] + public async Task FunctionHandlerAsync_stops_bus(TestFunctionWithHandler sut, string payload, ILambdaContext context) + { + await sut.FunctionHandlerAsync(payload, context); + + Mock.Get(sut.BusHost).Verify(p => p.StopAsync()); + } + + [Test, AutoMoqData] + public void FunctionHandlerAsync_starts_bus(TestFunctionWithNoHandler sut, string payload, ILambdaContext context) + { + Assert.ThrowsAsync(() => sut.FunctionHandlerAsync(payload, context)); + + Mock.Get(sut.BusHost).Verify(p => p.StartAsync()); + } + + [Test, AutoMoqData] + public void FunctionHandlerAsync_stops_bus(TestFunctionWithNoHandler sut, string payload, ILambdaContext context) + { + Assert.ThrowsAsync(() => sut.FunctionHandlerAsync(payload, context)); + + Mock.Get(sut.BusHost).Verify(p => p.StopAsync()); + } + + [Test, AutoMoqData] + public async Task FunctionHandlerAsync_invokes_handler(TestFunction sut, string payload, ILambdaContext context) + { + await sut.FunctionHandlerAsync(payload, context); + + Mock.Get(sut.Handler).Verify(p => p.HandleAsync(payload, context)); + } + + [Test, AutoMoqData] + public async Task FunctionHandlerAsync_invokes_registered_handler(TestFunctionWithHandler sut, string payload, ILambdaContext context) + { + await sut.FunctionHandlerAsync(payload, context); + + Mock.Get(TestHandler.InnerHandler).Verify(p => p.HandleAsync(payload, context)); + } + + [Test, AutoMoqData] + public void FunctionHandlerAsync_throws_if_no_handler_is_registered(TestFunctionWithNoHandler sut, string payload, ILambdaContext context) + { + Assert.ThrowsAsync(() => sut.FunctionHandlerAsync(payload, context)); + } + } +} \ No newline at end of file diff --git a/tests/Tests.Nybus.Extensions.Hosting.Lambda/TestFunction.cs b/tests/Tests.Nybus.Extensions.Hosting.Lambda/TestFunction.cs new file mode 100644 index 0000000..e5b143d --- /dev/null +++ b/tests/Tests.Nybus.Extensions.Hosting.Lambda/TestFunction.cs @@ -0,0 +1,83 @@ +using System; +using System.Threading.Tasks; +using Amazon.Lambda.Core; +using AutoFixture; +using AutoFixture.AutoMoq; +using Kralizek.Lambda; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Nybus; + +namespace Tests +{ + public class TestFunction : NybusFunction + { + protected override void ConfigureServices(IServiceCollection services, IExecutionEnvironment executionEnvironment) + { + IFixture fixture = new Fixture(); + fixture.Customize(new AutoMoqCustomization + { + GenerateDelegates = true, + ConfigureMembers = true + }); + + BusHost = fixture.Create(); + services.AddSingleton(BusHost); + + Handler = fixture.Create>(); + services.AddSingleton(Handler); + } + + public IBusHost BusHost { get; private set; } + + public Kralizek.Lambda.IEventHandler Handler { get; private set; } + } + + public class TestFunctionWithHandler : NybusFunction + { + protected override void ConfigureServices(IServiceCollection services, IExecutionEnvironment executionEnvironment) + { + IFixture fixture = new Fixture(); + fixture.Customize(new AutoMoqCustomization + { + GenerateDelegates = true, + ConfigureMembers = true + }); + + BusHost = fixture.Create(); + services.AddSingleton(BusHost); + + RegisterHandler(services); + } + + public IBusHost BusHost { get; private set; } + } + + public class TestFunctionWithNoHandler : NybusFunction + { + protected override void ConfigureServices(IServiceCollection services, IExecutionEnvironment executionEnvironment) + { + IFixture fixture = new Fixture(); + fixture.Customize(new AutoMoqCustomization + { + GenerateDelegates = true, + ConfigureMembers = true + }); + + BusHost = fixture.Create(); + services.AddSingleton(BusHost); + } + + public IBusHost BusHost { get; private set; } + } + + public class TestHandler : Kralizek.Lambda.IEventHandler + { + public Task HandleAsync(string input, ILambdaContext context) + { + return InnerHandler.HandleAsync(input, context); + } + + public static Kralizek.Lambda.IEventHandler InnerHandler { get; } = Mock.Of>(); + } +} \ No newline at end of file diff --git a/tests/Tests.Nybus.Extensions.Hosting.Lambda/Tests.Nybus.Extensions.Hosting.Lambda.csproj b/tests/Tests.Nybus.Extensions.Hosting.Lambda/Tests.Nybus.Extensions.Hosting.Lambda.csproj new file mode 100644 index 0000000..ff8137b --- /dev/null +++ b/tests/Tests.Nybus.Extensions.Hosting.Lambda/Tests.Nybus.Extensions.Hosting.Lambda.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.1 + + false + + + + + + + + + + + + + +