diff --git a/test/integration/Elsa.Core.IntegrationTests/Autofixture/HostBuilderWithDuplicateActivitiesWorkflowAttributes.cs b/test/integration/Elsa.Core.IntegrationTests/Autofixture/HostBuilderWithDuplicateActivitiesWorkflowAttributes.cs deleted file mode 100644 index fce26c910..000000000 --- a/test/integration/Elsa.Core.IntegrationTests/Autofixture/HostBuilderWithDuplicateActivitiesWorkflowAttributes.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Reflection; -using AutoFixture; -using AutoFixture.Xunit2; -using Elsa.Core.IntegrationTests.Workflows; -using Elsa.Persistence.MongoDb.Extensions; -using Elsa.Testing.Shared.AutoFixture.Customizations; -using Microsoft.Extensions.DependencyInjection; -using Elsa.Persistence.EntityFramework.Core.Extensions; -using Microsoft.EntityFrameworkCore; -using Elsa.Persistence.EntityFramework.Sqlite; -using Elsa.Persistence.YesSql; -using YesSql.Provider.Sqlite; -using System.Data; -using Elsa.Testing.Shared.Helpers; - -namespace Elsa.Core.IntegrationTests.Autofixture -{ - public class HostBuilderWithDuplicateActivitiesWorkflowAttribute : CustomizeAttribute - { - public override ICustomization GetCustomization(ParameterInfo parameter) - { - return new HostBuilderUsingServicesCustomization(services => { - services - .AddElsa(elsa => { - elsa.AddWorkflow(); - }); - }, parameter); - } - } - - public class HostBuilderWithDuplicateActivitiesWorkflowAndMongoDbAttribute : CustomizeAttribute - { - public override ICustomization GetCustomization(ParameterInfo parameter) - { - return new HostBuilderUsingServicesCustomization(services => { - services - .AddElsa(elsa => { - elsa.AddWorkflow(); - elsa.UseMongoDbPersistence(opts => { - opts.ConnectionString = "mongodb://localhost:27017"; - opts.DatabaseName = "IntegrationTests"; - }); - }); - }, parameter); - } - } - - public class HostBuilderWithDuplicateActivitiesWorkflowAndEntityFrameworkAttribute : CustomizeAttribute - { - public override ICustomization GetCustomization(ParameterInfo parameter) - { - var tempFolder = new TemporaryFolder(); - - return new HostBuilderUsingServicesCustomization(services => { - services - .AddElsa(elsa => { - elsa - .AddWorkflow() - .UseEntityFrameworkPersistence(opts => { - opts.UseSqlite($"Data Source={tempFolder.Folder}elsa.db;", db => db.MigrationsAssembly(typeof(SqliteElsaContextFactory).Assembly.GetName().Name)); - }); - }); - }, parameter); - } - } - - public class HostBuilderWithDuplicateActivitiesWorkflowAndYesSqlAttribute : CustomizeAttribute - { - public override ICustomization GetCustomization(ParameterInfo parameter) - { - return new HostBuilderUsingServicesCustomization(services => { - services - .AddElsa(elsa => { - elsa - .AddWorkflow() - .UseYesSqlPersistence(config => { - config.UseSqLite("Data Source=elsa-sqlite.db;", IsolationLevel.ReadUncommitted); - }); - }); - }, parameter); - } - } -} \ No newline at end of file diff --git a/test/integration/Elsa.Core.IntegrationTests/Autofixture/WithDuplicateActivitiesWorkflowAttribute.cs b/test/integration/Elsa.Core.IntegrationTests/Autofixture/WithDuplicateActivitiesWorkflowAttribute.cs new file mode 100644 index 000000000..2945e3570 --- /dev/null +++ b/test/integration/Elsa.Core.IntegrationTests/Autofixture/WithDuplicateActivitiesWorkflowAttribute.cs @@ -0,0 +1,30 @@ +using System.Reflection; +using AutoFixture; +using AutoFixture.Xunit2; +using Elsa.Core.IntegrationTests.Workflows; +using Elsa.Persistence.MongoDb.Extensions; +using Elsa.Testing.Shared.AutoFixture.Customizations; +using Microsoft.Extensions.DependencyInjection; +using Elsa.Persistence.EntityFramework.Core.Extensions; +using Microsoft.EntityFrameworkCore; +using Elsa.Persistence.EntityFramework.Sqlite; +using Elsa.Persistence.YesSql; +using YesSql.Provider.Sqlite; +using Elsa.Testing.Shared.Helpers; +using Elsa.Testing.Shared.AutoFixture.Attributes; +using System; + +namespace Elsa.Core.IntegrationTests.Autofixture +{ + public class WithDuplicateActivitiesWorkflowAttribute : ElsaHostBuilderBuilderCustomizeAttributeBase + { + public override Action GetBuilderCustomizer() + { + return builder => { + builder.ElsaCallbacks.Add(elsa => { + elsa.AddWorkflow(); + }); + }; + } + } +} \ No newline at end of file diff --git a/test/integration/Elsa.Core.IntegrationTests/Autofixture/WithEntityFrameworkAttribute.cs b/test/integration/Elsa.Core.IntegrationTests/Autofixture/WithEntityFrameworkAttribute.cs new file mode 100644 index 000000000..4adaef5fd --- /dev/null +++ b/test/integration/Elsa.Core.IntegrationTests/Autofixture/WithEntityFrameworkAttribute.cs @@ -0,0 +1,25 @@ +using System; +using Elsa.Testing.Shared.AutoFixture.Attributes; +using Elsa.Testing.Shared.Helpers; +using Elsa.Persistence.EntityFramework.Core.Extensions; +using Microsoft.EntityFrameworkCore; +using Elsa.Persistence.EntityFramework.Sqlite; + +namespace Elsa.Core.IntegrationTests.Autofixture +{ + public class WithEntityFrameworkAttribute : ElsaHostBuilderBuilderCustomizeAttributeBase + { + public override Action GetBuilderCustomizer() + { + var tempFolder = new TemporaryFolder(); + + return builder => { + builder.ElsaCallbacks.Add(elsa => { + elsa.UseEntityFrameworkPersistence(opts => { + opts.UseSqlite($"Data Source={tempFolder.GetContainedPath("elsa.db")};", db => db.MigrationsAssembly(typeof(SqliteElsaContextFactory).Assembly.GetName().Name)); + }); + }); + }; + } + } +} \ No newline at end of file diff --git a/test/integration/Elsa.Core.IntegrationTests/Autofixture/WithMongoDbAttribute.cs b/test/integration/Elsa.Core.IntegrationTests/Autofixture/WithMongoDbAttribute.cs new file mode 100644 index 000000000..1160b25e6 --- /dev/null +++ b/test/integration/Elsa.Core.IntegrationTests/Autofixture/WithMongoDbAttribute.cs @@ -0,0 +1,22 @@ +using System; +using Elsa.Persistence.MongoDb.Extensions; +using Elsa.Testing.Shared.AutoFixture.Attributes; +using Elsa.Testing.Shared.Helpers; + +namespace Elsa.Core.IntegrationTests.Autofixture +{ + public class WithMongoDbAttribute : ElsaHostBuilderBuilderCustomizeAttributeBase + { + public override Action GetBuilderCustomizer() + { + return builder => { + builder.ElsaCallbacks.Add(elsa => { + elsa.UseMongoDbPersistence(opts => { + opts.ConnectionString = "mongodb://localhost:27017"; + opts.DatabaseName = "IntegrationTests"; + }); + }); + }; + } + } +} \ No newline at end of file diff --git a/test/integration/Elsa.Core.IntegrationTests/Autofixture/WithSqliteYesSqlAttribute.cs b/test/integration/Elsa.Core.IntegrationTests/Autofixture/WithSqliteYesSqlAttribute.cs new file mode 100644 index 000000000..e2db36936 --- /dev/null +++ b/test/integration/Elsa.Core.IntegrationTests/Autofixture/WithSqliteYesSqlAttribute.cs @@ -0,0 +1,25 @@ +using Elsa.Testing.Shared.Helpers; +using Elsa.Testing.Shared.AutoFixture.Attributes; +using System; +using Elsa.Persistence.YesSql; +using YesSql.Provider.Sqlite; +using System.Data; + +namespace Elsa.Core.IntegrationTests.Autofixture +{ + public class WithSqliteYesSqlAttribute : ElsaHostBuilderBuilderCustomizeAttributeBase + { + public override Action GetBuilderCustomizer() + { + var tempFolder = new TemporaryFolder(); + + return builder => { + builder.ElsaCallbacks.Add(elsa => { + elsa.UseYesSqlPersistence(config => { + config.UseSqLite($"Data Source={tempFolder.GetContainedPath("elsa.db")};", IsolationLevel.ReadUncommitted); + }); + }); + }; + } + } +} \ No newline at end of file diff --git a/test/integration/Elsa.Core.IntegrationTests/Persistence/WorkflowMayContainDuplicateActivitiesIntegrationTests.cs b/test/integration/Elsa.Core.IntegrationTests/Persistence/WorkflowMayContainDuplicateActivitiesIntegrationTests.cs index 498565d53..1bedfd1ea 100644 --- a/test/integration/Elsa.Core.IntegrationTests/Persistence/WorkflowMayContainDuplicateActivitiesIntegrationTests.cs +++ b/test/integration/Elsa.Core.IntegrationTests/Persistence/WorkflowMayContainDuplicateActivitiesIntegrationTests.cs @@ -7,6 +7,7 @@ using System.Threading; using Elsa.Services; using Elsa.Core.IntegrationTests.Workflows; using Elsa.Persistence; +using Elsa.Testing.Shared.Helpers; namespace Elsa.Core.IntegrationTests.Persistence { @@ -20,35 +21,33 @@ namespace Elsa.Core.IntegrationTests.Persistence */ [Theory(DisplayName = "A workflow that contains duplicate activities may be run & persisted to an in-memory store"), AutoMoqData] - public async Task ADuplicateActivitiesWorkflowInstanceShouldBeRoundTrippableInMemory([HostBuilderWithDuplicateActivitiesWorkflow] IHostBuilder hostBuilder) + public async Task ADuplicateActivitiesWorkflowInstanceShouldBeRoundTrippableInMemory([WithDuplicateActivitiesWorkflow] ElsaHostBuilderBuilder hostBuilderBuilder) { - hostBuilder.ConfigureServices((ctx, services) => { - services.AddHostedService>(); - }); - var host = await hostBuilder.StartAsync(); + await PerformTest(hostBuilderBuilder); } [Theory(DisplayName = "A workflow that contains duplicate activities may be run & persisted to an EF Sqlite store"), AutoMoqData] - public async Task ADuplicateActivitiesWorkflowInstanceShouldBeRoundTrippableWithEntityFramework([HostBuilderWithDuplicateActivitiesWorkflowAndEntityFramework] IHostBuilder hostBuilder) + public async Task ADuplicateActivitiesWorkflowInstanceShouldBeRoundTrippableWithEntityFramework([WithDuplicateActivitiesWorkflow,WithEntityFramework] ElsaHostBuilderBuilder hostBuilderBuilder) { - hostBuilder.ConfigureServices((ctx, services) => { - services.AddHostedService>(); - }); - var host = await hostBuilder.StartAsync(); + await PerformTest(hostBuilderBuilder); } [Theory(DisplayName = "A workflow that contains duplicate activities may be run & persisted to a MongoDb store"), AutoMoqData] - public async Task ADuplicateActivitiesWorkflowInstanceShouldBeRoundTrippableWithMongoDb([HostBuilderWithDuplicateActivitiesWorkflowAndMongoDb] IHostBuilder hostBuilder) + public async Task ADuplicateActivitiesWorkflowInstanceShouldBeRoundTrippableWithMongoDb([WithDuplicateActivitiesWorkflow,WithMongoDb] ElsaHostBuilderBuilder hostBuilderBuilder) { - hostBuilder.ConfigureServices((ctx, services) => { - services.AddHostedService>(); - }); - var host = await hostBuilder.StartAsync(); + await PerformTest(hostBuilderBuilder); } [Theory(DisplayName = "A workflow that contains duplicate activities may be run & persisted to a YesSQL store"), AutoMoqData] - public async Task ADuplicateActivitiesWorkflowInstanceShouldBeRoundTrippableWithYesSql([HostBuilderWithDuplicateActivitiesWorkflowAndYesSql] IHostBuilder hostBuilder) + public async Task ADuplicateActivitiesWorkflowInstanceShouldBeRoundTrippableWithYesSql([WithDuplicateActivitiesWorkflow,WithSqliteYesSql] ElsaHostBuilderBuilder hostBuilderBuilder) { + await PerformTest(hostBuilderBuilder); + } + + async Task PerformTest(ElsaHostBuilderBuilder hostBuilderBuilder) + { + var hostBuilder = hostBuilderBuilder.GetHostBuilder(); + hostBuilder.ConfigureServices((ctx, services) => { services.AddHostedService>(); }); diff --git a/test/shared/Elsa.Testing.Shared/AutoFixture/Attributes/ElsaHostBuilderBuilderCustomizeAttributeBase.cs b/test/shared/Elsa.Testing.Shared/AutoFixture/Attributes/ElsaHostBuilderBuilderCustomizeAttributeBase.cs new file mode 100644 index 000000000..4d6ca511f --- /dev/null +++ b/test/shared/Elsa.Testing.Shared/AutoFixture/Attributes/ElsaHostBuilderBuilderCustomizeAttributeBase.cs @@ -0,0 +1,64 @@ +using System; +using System.Reflection; +using AutoFixture; +using AutoFixture.Kernel; +using AutoFixture.Xunit2; +using Elsa.Testing.Shared.AutoFixture.Specifications; +using Elsa.Testing.Shared.Helpers; + +namespace Elsa.Testing.Shared.AutoFixture.Attributes +{ + public abstract class ElsaHostBuilderBuilderCustomizeAttributeBase : CustomizeAttribute + { + public abstract Action GetBuilderCustomizer(); + + public override ICustomization GetCustomization(ParameterInfo parameter) + => new ElsaHostBuilderBuilderCustomization(GetBuilderCustomizer()); + + class ElsaHostBuilderBuilderCustomization : ICustomization + { + readonly Action builderCustomizer; + + public void Customize(IFixture fixture) + { + fixture.Behaviors.Add(new ElsaHostBuilderBuilderTransformation(builderCustomizer)); + } + + public ElsaHostBuilderBuilderCustomization(Action builderCustomizer) + { + this.builderCustomizer = builderCustomizer ?? throw new ArgumentNullException(nameof(builderCustomizer)); + } + } + + class ElsaHostBuilderBuilderTransformation : ISpecimenBuilderTransformation + { + readonly Action builderCustomizer; + + public ISpecimenBuilderNode Transform(ISpecimenBuilder builder) + { + return new Postprocessor(builder, new ElsaHostBuilderBuilderCommand(builderCustomizer), new IsAnElsaHostBuilderBuilderSpec()); + } + + public ElsaHostBuilderBuilderTransformation(Action builderCustomizer) + { + this.builderCustomizer = builderCustomizer ?? throw new ArgumentNullException(nameof(builderCustomizer)); + } + } + + class ElsaHostBuilderBuilderCommand : ISpecimenCommand + { + readonly Action builderCustomizer; + + public void Execute(object specimen, ISpecimenContext context) + { + var builder = (ElsaHostBuilderBuilder) specimen; + builderCustomizer(builder); + } + + public ElsaHostBuilderBuilderCommand(Action builderCustomizer) + { + this.builderCustomizer = builderCustomizer ?? throw new ArgumentNullException(nameof(builderCustomizer)); + } + } + } +} \ No newline at end of file diff --git a/test/shared/Elsa.Testing.Shared/AutoFixture/Specifications/IsAnElsaHostBuilderBuilderSpec.cs b/test/shared/Elsa.Testing.Shared/AutoFixture/Specifications/IsAnElsaHostBuilderBuilderSpec.cs new file mode 100644 index 000000000..dada67a6b --- /dev/null +++ b/test/shared/Elsa.Testing.Shared/AutoFixture/Specifications/IsAnElsaHostBuilderBuilderSpec.cs @@ -0,0 +1,10 @@ +using AutoFixture.Kernel; +using Elsa.Testing.Shared.Helpers; + +namespace Elsa.Testing.Shared.AutoFixture.Specifications +{ + public class IsAnElsaHostBuilderBuilderSpec : IRequestSpecification + { + public bool IsSatisfiedBy(object request) => request is ElsaHostBuilderBuilder; + } +} \ No newline at end of file diff --git a/test/shared/Elsa.Testing.Shared/Helpers/ElsaHostBuilderBuilder.cs b/test/shared/Elsa.Testing.Shared/Helpers/ElsaHostBuilderBuilder.cs new file mode 100644 index 000000000..ba022f609 --- /dev/null +++ b/test/shared/Elsa.Testing.Shared/Helpers/ElsaHostBuilderBuilder.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Elsa.Testing.Shared.Helpers +{ + /// + /// A helper class which 'builds' an for testing purposes. + /// The reason this is useful is that it also allows building-up callbacks to occur inside + /// the callback (which cannot be + /// done with a normal host builder alone). + /// + public class ElsaHostBuilderBuilder + { + /// + /// Gets a collection of the callbacks to be executed upon the of + /// the host builder as it is created. + /// + /// The services callbacks + public IList> ServiceCallbacks { get; } = new List>(); + + /// + /// Gets a collection of the callbacks to be executed upon the of + /// the host builder's services as it is created. + /// + /// The Elsa callbacks + public IList> ElsaCallbacks { get; } = new List>(); + + Action ElsaConfiguration + => ElsaCallbacks + .Where(x => !(x is null)) + .Aggregate(EmptyElsaAction, (acc, next) => o => { acc(o); next(o); }); + + Action ServiceConfiguration + => ServiceCallbacks + .Where(x => !(x is null)) + .Aggregate(EmptyServicesAction, (acc, next) => o => { acc(o); next(o); }); + + static Action EmptyElsaAction => o => {}; + + static Action EmptyServicesAction => s => {}; + + /// + /// Gets an configured using the + /// and . + /// + /// A host builder. + public IHostBuilder GetHostBuilder() + { + return Host.CreateDefaultBuilder() + .ConfigureServices((hostBuilder, services) => { + services.AddElsa(ElsaConfiguration); + ServiceConfiguration(services); + }); + } + } +} \ No newline at end of file diff --git a/test/shared/Elsa.Testing.Shared/Helpers/TemporaryFolder.cs b/test/shared/Elsa.Testing.Shared/Helpers/TemporaryFolder.cs index 092527faf..9bf48bd8e 100644 --- a/test/shared/Elsa.Testing.Shared/Helpers/TemporaryFolder.cs +++ b/test/shared/Elsa.Testing.Shared/Helpers/TemporaryFolder.cs @@ -15,6 +15,13 @@ namespace Elsa.Testing.Shared.Helpers public string Folder { get; } + /// + /// Gets a path string for a file or folder which is contained within the . + /// + /// The relative path, within the temporary folder. + /// A path for content within the temporary folder. + public string GetContainedPath(string relativePath) => Path.Combine(Folder, relativePath); + public void Dispose() { if (_deleteOnDispose)