diff --git a/README.md b/README.md index e6f3608ed..e58796eb1 100644 --- a/README.md +++ b/README.md @@ -2,226 +2,75 @@ [![Build status](https://ci.appveyor.com/api/projects/status/xnby6p5v4ur04u76?svg=true)](https://ci.appveyor.com/project/danielgerlag/workflow-core) -Workflow Core is a light weight workflow engine targeting .NET Standard. It supports pluggable persistence and concurrency providers to allow for multi-node clusters. See [Wiki here.](https://github.com/danielgerlag/workflow-core/wiki) +Workflow Core is a light weight workflow engine targeting .NET Standard. Think: long running processes with multiple tasks that need to track state. It supports pluggable persistence and concurrency providers to allow for multi-node clusters. +## Documentation -## Installing +See [Full Documentation here.](https://github.com/danielgerlag/workflow-core/wiki) -Install the NuGet package "WorkflowCore" +## Fluent API -``` -PM> Install-Package WorkflowCore -``` - -## Basic Concepts - -### Steps - -A workflow consists of a series of connected steps. Each step produces an outcome value and subsequent steps are triggered by subscribing to a particular outcome of a preceeding step. The default outcome of *null* can be used for a basic linear workflow. -Steps are usually defined by inheriting from the StepBody abstract class and implementing the Run method. They can also be created inline while defining the workflow structure. - -First we define some steps - -```C# -public class HelloWorld : StepBody -{ - public override ExecutionResult Run(IStepExecutionContext context) - { - Console.WriteLine("Hello world"); - return ExecutionResult.Next(); - } -} -``` -*The StepBody class implementations are constructed by the workflow host which first tries to use IServiceProvider from the built-in dependency injection of .NET Core, if it can't construct it with this method, it will search for a parameterless constructor* - -Then we define the workflow structure by composing a chain of steps. The is done by implementing the IWorkflow interface. - -```C# -public class HelloWorldWorkflow : IWorkflow -{ - public void Build(IWorkflowBuilder builder) - { - builder - .StartWith() - .Then(); - } - ... -} -``` -The *IWorkflow* interface also has a readonly Id property and readonly Version property. These are generally static and are used by the workflow host to identify a workflow definition. - -You can also define your steps inline +Define your workflows with the fluent API. -```C# -public class HelloWorldWorkflow : IWorkflow +```c# +public class MyWorkflow : IWorkflow { - public void Build(IWorkflowBuilder builder) - { + public void Build(IWorkflowBuilder builder) + { builder - .StartWith(context => - { - Console.WriteLine("Hello world"); - return ExecutionResult.Next(); - }) - .Then(context => - { - Console.WriteLine("Goodbye world"); - return ExecutionResult.Next(); - }) + .StartWith() + .Then() + .Then; } - ... } ``` -Each running workflow is persisted to the chosen persistence provider between each step, where it can be picked up at a later point in time to continue execution. The outcome result of your step can instruct the workflow host to defer further execution of the workflow until a future point in time or in response to an external event. - -The first time a particular step within the workflow is called, the PersistenceData property on the context object is *null*. The ExecutionResult produced by the Run method can either cause the workflow to proceed to the next step by providing an outcome value, instruct the workflow to sleep for a defined period or simply not move the workflow forward. If no outcome value is produced, then the step becomes re-entrant by setting PersistenceData, so the workflow host will call this step again in the future buy will populate the PersistenceData with it's previous value. - -For example, this step will initially run with *null* PersistenceData and put the workflow to sleep for 12 hours, while setting the PersistenceData to *new Object()*. 12 hours later, the step will be called again but context.PersistenceData will now contain the object constructed in the previous iteration, and will now produce an outcome value of *null*, causing the workflow to move forward. - -```C# -public class SleepStep : StepBody -{ - public override ExecutionResult Run(IStepExecutionContext context) - { - if (context.PersistenceData == null) - return ExecutionResult.Sleep(Timespan.FromHours(12), new Object()); - else - return ExecutionResult.Next(); - } -} -``` - -### Passing data between steps - -Each step is intended to be a black-box, therefore they support inputs and outputs. These inputs and outputs can be mapped to a data class that defines the custom data relevant to each workflow instance. - -The following sample shows how to define inputs and outputs on a step, it then shows how define a workflow with a typed class for internal data and how to map the inputs and outputs to properties on the custom data class. - -```C# -//Our workflow step with inputs and outputs -public class AddNumbers : StepBody -{ - public int Input1 { get; set; } - - public int Input2 { get; set; } - - public int Output { get; set; } - - public override ExecutionResult Run(IStepExecutionContext context) - { - Output = (Input1 + Input2); - return ExecutionResult.Next(); - } -} +### Sample use cases -//Our class to define the internal data of our workflow -public class MyDataClass +* New user workflow +```c# +public class MyData { - public int Value1 { get; set; } - public int Value2 { get; set; } - public int Value3 { get; set; } + public string Email { get; set; } + public string Password { get; set; } + public string UserId { get; set; } } -//Our workflow definition with strongly typed internal data and mapped inputs & outputs -public class PassingDataWorkflow : IWorkflow -{ - public void Build(IWorkflowBuilder builder) - { - builder - .StartWith() - .Input(step => step.Input1, data => data.Value1) - .Input(step => step.Input2, data => data.Value2) - .Output(data => data.Value3, step => step.Output) - .Then() - .Input(step => step.Message, data => "The answer is " + data.Value3.ToString()); - } - ... -} - -``` - -### Multiple outcomes / forking - -A workflow can take a different path depending on the outcomes of preceeding steps. The following example shows a process where first a random number of 0 or 1 is generated and is the outcome of the first step. Then, depending on the outcome value, the workflow will either fork to (TaskA + TaskB) or (TaskC + TaskD) - -```C# -public class MultipleOutcomeWorkflow : IWorkflow +public class MyWorkflow : IWorkflow { - public void Build(IWorkflowBuilder builder) - { + public void Build(IWorkflowBuilder builder) + { builder - .StartWith(x => x.Name("Random Step")) - .When(0) - .Then() - .Then() - .End("Random Step") - .When(1) - .Then() - .Then() - .End("Random Step"); + .StartWith() + .Input(step => step.Email, data => data.Email) + .Input(step => step.Password, data => data.Password) + .Output(data => data.UserId, step => step.UserId); + .Then() + .WaitFor("confirmation", data => data.UserId) + .Then() + .Input(step => step.UserId, data => data.UserId); } } ``` -### Events +* Resilient service orchestration -A workflow can also wait for an external event before proceeding. In the following example, the workflow will wait for an event called *"MyEvent"* with a key of *0*. Once an external source has fired this event, the workflow will wake up and continue processing, passing the data generated by the event onto the next step. - -```C# -public class EventSampleWorkflow : IWorkflow +```c# +public class MyWorkflow : IWorkflow { - public void Build(IWorkflowBuilder builder) - { + public void Build(IWorkflowBuilder builder) + { builder - .StartWith(context => - { - Console.WriteLine("workflow started"); - return ExecutionResult.Next(); - }) - .WaitFor("MyEvent", data => "0") - .Output(data => data.Value, step => step.EventData) - .Then() - .Input(step => step.Message, data => "The data from the event is " + data.Value); + .StartWith() + .Then() + .OnError(WorkflowErrorHandling.Retry, TimeSpan.FromMinutes(10)) + .Then() + .OnError(WorkflowErrorHandling.Retry, TimeSpan.FromMinutes(10)); } } -... -//External events are published via the host -//All workflows that have subscribed to MyEvent 0, will be passed "hello" -host.PublishEvent("MyEvent", "0", "hello"); -``` - -### Host - -The workflow host is the service responsible for executing workflows. It does this by polling the persistence provider for workflow instances that are ready to run, executes them and then passes them back to the persistence provider to by stored for the next time they are run. It is also responsible for publishing events to any workflows that may be waiting on one. - -#### Setup - -Use the *AddWorkflow* extension method for *IServiceCollection* to configure the workflow host upon startup of your application. -By default, it is configured with *MemoryPersistenceProvider* and *SingleNodeConcurrencyProvider* for testing purposes. You can also configure a DB persistence provider at this point. - -```C# -services.AddWorkflow(); ``` -#### Usage - -When your application starts, grab the workflow host from the built-in dependency injection framework *IServiceProvider*. Make sure you call *RegisterWorkflow*, so that the workflow host knows about all your workflows, and then call *Start()* to fire up the thread pool that executes workflows. Use the *StartWorkflow* method to initiate a new instance of a particular workflow. - - -```C# -var host = serviceProvider.GetService(); -host.RegisterWorkflow(); -host.Start(); - -host.StartWorkflow("HelloWorld", 1, null); - -Console.ReadLine(); -host.Stop(); -``` - - -### Persistence +## Persistence Since workflows are typically long running processes, they will need to be persisted to storage between steps. There are several persistence providers available as separate Nuget packages. @@ -233,40 +82,6 @@ There are several persistence providers available as separate Nuget packages. * [Sqlite](src/providers/WorkflowCore.Persistence.Sqlite) * Redis *(coming soon...)* -### Multi-node clusters - -By default, the WorkflowHost service will run as a single node using the built-in queue and locking providers for a single node configuration. Should you wish to run a multi-node cluster, you will need to configure an external queueing mechanism and a distributed lock manager to co-ordinate the cluster. These are the providers that are currently available. - -#### Queue Providers - -* SingleNodeQueueProvider *(Default built-in provider)* -* [RabbitMQ](src/providers/WorkflowCore.QueueProviders.RabbitMQ) -* [0MQ](src/providers/WorkflowCore.QueueProviders.ZeroMQ) *(experimental)* -* Apache ZooKeeper *(coming soon...)* - -#### Distributed lock managers - -* SingleNodeLockProvider *(Default built-in provider)* -* [Redis Redlock](src/providers/WorkflowCore.LockProviders.Redlock) -* [0MQ](src/providers/WorkflowCore.LockProviders.ZeroMQ) *(experimental)* -* Apache ZooKeeper *(coming soon...)* - -### Error handling - -Each step can be configured with it's own error handling behavior, it can be retried at a later time, suspend the workflow or terminate the workflow. - -```C# -public void Build(IWorkflowBuilder builder) -{ - builder - .StartWith() - .OnError(WorkflowErrorHandling.Retry, TimeSpan.FromMinutes(10)) - .Then(); -} -``` - -The WorkflowHost service also has a .OnStepError event which can be used to intercept exceptions from workflow steps on a more global level. - ## Extensions * [User (human) workflows](src/extensions/WorkflowCore.Users) diff --git a/src/WorkflowCore/Services/EventThread.cs b/src/WorkflowCore/Services/EventThread.cs index bbb3445ee..14bea804f 100644 --- a/src/WorkflowCore/Services/EventThread.cs +++ b/src/WorkflowCore/Services/EventThread.cs @@ -102,7 +102,7 @@ private bool SeedSubscription(Event evt, EventSubscription sub) try { var workflow = _persistenceStore.GetWorkflowInstance(sub.WorkflowId).Result; - var pointers = workflow.ExecutionPointers.Where(p => p.EventName == sub.EventName && p.EventKey == p.EventKey && !p.EventPublished); + var pointers = workflow.ExecutionPointers.Where(p => p.EventName == sub.EventName && p.EventKey == sub.EventKey && !p.EventPublished); foreach (var p in pointers) { p.EventData = evt.EventData; diff --git a/src/WorkflowCore/WorkflowCore.csproj b/src/WorkflowCore/WorkflowCore.csproj index 3f38dc521..00ac3b433 100644 --- a/src/WorkflowCore/WorkflowCore.csproj +++ b/src/WorkflowCore/WorkflowCore.csproj @@ -17,6 +17,8 @@ false false false + Workflow Core is a light weight workflow engine targeting .NET Standard. + 1.1.2 diff --git a/src/extensions/WorkflowCore.Users/WorkflowCore.Users.csproj b/src/extensions/WorkflowCore.Users/WorkflowCore.Users.csproj index f7c439670..d5dacb56a 100644 --- a/src/extensions/WorkflowCore.Users/WorkflowCore.Users.csproj +++ b/src/extensions/WorkflowCore.Users/WorkflowCore.Users.csproj @@ -17,6 +17,8 @@ false false false + Provides extensions for Workflow Core to enable human workflows. + 1.1.1 diff --git a/src/providers/WorkflowCore.Persistence.MongoDB/WorkflowCore.Persistence.MongoDB.csproj b/src/providers/WorkflowCore.Persistence.MongoDB/WorkflowCore.Persistence.MongoDB.csproj index 0b57b1462..b954343e2 100644 --- a/src/providers/WorkflowCore.Persistence.MongoDB/WorkflowCore.Persistence.MongoDB.csproj +++ b/src/providers/WorkflowCore.Persistence.MongoDB/WorkflowCore.Persistence.MongoDB.csproj @@ -17,7 +17,8 @@ false false false - 1.1.0 + 1.1.1 + Provides support to persist workflows running on Workflow Core to a MongoDB database. diff --git a/src/providers/WorkflowCore.Persistence.PostgreSQL/WorkflowCore.Persistence.PostgreSQL.csproj b/src/providers/WorkflowCore.Persistence.PostgreSQL/WorkflowCore.Persistence.PostgreSQL.csproj index 0db5122a5..ca76d63ad 100644 --- a/src/providers/WorkflowCore.Persistence.PostgreSQL/WorkflowCore.Persistence.PostgreSQL.csproj +++ b/src/providers/WorkflowCore.Persistence.PostgreSQL/WorkflowCore.Persistence.PostgreSQL.csproj @@ -17,6 +17,8 @@ false false false + Provides support to persist workflows running on Workflow Core to a PostgreSQL database. + 1.1.1 diff --git a/src/providers/WorkflowCore.Persistence.SqlServer/WorkflowCore.Persistence.SqlServer.csproj b/src/providers/WorkflowCore.Persistence.SqlServer/WorkflowCore.Persistence.SqlServer.csproj index 04d3d6ae5..cdc8ea280 100644 --- a/src/providers/WorkflowCore.Persistence.SqlServer/WorkflowCore.Persistence.SqlServer.csproj +++ b/src/providers/WorkflowCore.Persistence.SqlServer/WorkflowCore.Persistence.SqlServer.csproj @@ -17,6 +17,8 @@ false false false + 1.1.1 + Provides support to persist workflows running on Workflow Core to a SQL Server database. diff --git a/src/providers/WorkflowCore.Persistence.Sqlite/WorkflowCore.Persistence.Sqlite.csproj b/src/providers/WorkflowCore.Persistence.Sqlite/WorkflowCore.Persistence.Sqlite.csproj index 0256a3f91..2708eed95 100644 --- a/src/providers/WorkflowCore.Persistence.Sqlite/WorkflowCore.Persistence.Sqlite.csproj +++ b/src/providers/WorkflowCore.Persistence.Sqlite/WorkflowCore.Persistence.Sqlite.csproj @@ -17,6 +17,8 @@ false false false + Provides support to persist workflows running on Workflow Core to a Sqlite database. + 1.1.1 diff --git a/src/samples/WorkflowCore.Sample08/HumanWorkflow2.cs b/src/samples/WorkflowCore.Sample08/HumanWorkflow2.cs index 97d2c7c31..8426e2de2 100644 --- a/src/samples/WorkflowCore.Sample08/HumanWorkflow2.cs +++ b/src/samples/WorkflowCore.Sample08/HumanWorkflow2.cs @@ -9,21 +9,8 @@ namespace WorkflowCore.Sample08 { public class HumanWorkflow2 : IWorkflow { - public string Id - { - get - { - return "HumanWorkflow"; - } - } - - public int Version - { - get - { - return 1; - } - } + public string Id => "HumanWorkflow"; + public int Version => 1; public void Build(IWorkflowBuilder builder) { diff --git a/test/WorkflowCore.TestAssets/LockProvider/DistributedLockProviderTests.cs b/test/WorkflowCore.TestAssets/LockProvider/DistributedLockProviderTests.cs new file mode 100644 index 000000000..f57f8cd5f --- /dev/null +++ b/test/WorkflowCore.TestAssets/LockProvider/DistributedLockProviderTests.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Text; +using WorkflowCore.Interface; +using NUnit; +using FluentAssertions; +using NUnit.Framework; + +namespace WorkflowCore.TestAssets.LockProvider +{ + public abstract class DistributedLockProviderTests + { + protected IDistributedLockProvider Subject; + + [SetUp] + public void Setup() + { + Subject = CreateProvider(); + Subject.Start(); + } + + protected abstract IDistributedLockProvider CreateProvider(); + + [Test] + public async void AcquiresLock() + { + const string lock1 = "lock1"; + const string lock2 = "lock2"; + await Subject.AcquireLock(lock2); + + var acquired = await Subject.AcquireLock(lock1); + + acquired.Should().Be(true); + } + + [Test] + public async void DoesNotAcquireWhenLocked() + { + const string lock1 = "lock1"; + await Subject.AcquireLock(lock1); + + var acquired = await Subject.AcquireLock(lock1); + + acquired.Should().Be(false); + } + + [Test] + public async void ReleasesLock() + { + const string lock1 = "lock1"; + await Subject.AcquireLock(lock1); + + await Subject.ReleaseLock(lock1); + + var available = await Subject.AcquireLock(lock1); + available.Should().Be(true); + } + + [TearDown] + public virtual void TearDown() + { + Subject.Stop(); + } + } +} diff --git a/test/WorkflowCore.TestAssets/Persistence/CreateNewWorkflow.cs b/test/WorkflowCore.TestAssets/Persistence/CreateNewWorkflow.cs new file mode 100644 index 000000000..150a15266 --- /dev/null +++ b/test/WorkflowCore.TestAssets/Persistence/CreateNewWorkflow.cs @@ -0,0 +1,58 @@ +using Machine.Specifications; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using WorkflowCore.Interface; +using WorkflowCore.Models; + +namespace WorkflowCore.TestAssets.Persistence +{ + [Behaviors] + public class CreateNewWorkflowBehaviors + { + protected static IPersistenceProvider Subject; + protected static WorkflowInstance workflow; + protected static string workflowId; + + It should_return_a_generated_id = () => workflowId.ShouldNotBeNull(); + It should_set_id_on_object = () => workflow.Id.ShouldNotBeNull(); + } + + public abstract class CreateNewWorkflow + { + protected static IPersistenceProvider Subject; + protected static WorkflowInstance workflow; + protected static string workflowId; + + protected abstract IPersistenceProvider Provider { get; } + Establish context; + + public CreateNewWorkflow() + { + context = EstablishContext; + } + + protected void EstablishContext() + { + Subject = Provider; + workflow = new WorkflowInstance() + { + Data = new { Value1 = 7 }, + Description = "My Description", + Status = WorkflowStatus.Runnable, + NextExecution = 0, + Version = 1, + WorkflowDefinitionId = "My Workflow" + }; + workflow.ExecutionPointers.Add(new ExecutionPointer() + { + Id = Guid.NewGuid().ToString(), + Active = true, + StepId = 0 + }); + } + + Because of = () => workflowId = Subject.CreateNewWorkflow(workflow).Result; + } +} diff --git a/test/WorkflowCore.TestAssets/Persistence/GetWorkflowInstance.cs b/test/WorkflowCore.TestAssets/Persistence/GetWorkflowInstance.cs new file mode 100644 index 000000000..73a58513e --- /dev/null +++ b/test/WorkflowCore.TestAssets/Persistence/GetWorkflowInstance.cs @@ -0,0 +1,80 @@ +using Machine.Specifications; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using WorkflowCore.Interface; +using WorkflowCore.Models; +using FluentAssertions; + +namespace WorkflowCore.TestAssets.Persistence +{ + [Behaviors] + public class GetWorkflowInstanceBehaviors + { + protected static IPersistenceProvider Subject; + protected static WorkflowInstance workflow; + protected static WorkflowInstance retrievedWorkflow; + protected static string workflowId; + + It should_match_the_original = () => + { + retrievedWorkflow.ShouldBeEquivalentTo(workflow); + }; + } + + public abstract class GetWorkflowInstance + { + protected static IPersistenceProvider Subject; + protected static WorkflowInstance workflow; + protected static string workflowId; + protected static WorkflowInstance retrievedWorkflow; + + protected abstract IPersistenceProvider Provider { get; } + Establish context; + + public GetWorkflowInstance() + { + context = EstablishContext; + } + + protected void EstablishContext() + { + Subject = Provider; + workflow = new WorkflowInstance() + { + Data = new { Value1 = 7 }, + Description = "My Description", + Status = WorkflowStatus.Runnable, + NextExecution = 0, + Version = 1, + WorkflowDefinitionId = "My Workflow", + CreateTime = new DateTime(2000, 1, 1).ToUniversalTime() + }; + + var ep = new ExecutionPointer() + { + Id = Guid.NewGuid().ToString(), + Active = true, + StepId = 0 + }; + + ep.ExtensionAttributes["Attr1"] = "test"; + ep.ExtensionAttributes["Attr2"] = 5; + workflow.ExecutionPointers.Add(ep); + + workflowId = Subject.CreateNewWorkflow(workflow).Result; + } + + Because of = () => retrievedWorkflow = Subject.GetWorkflowInstance(workflowId).Result; + + Cleanup after = () => + { + Subject = null; + workflow = null; + retrievedWorkflow = null; + workflowId = null; + }; + + } +} diff --git a/test/WorkflowCore.TestAssets/Persistence/PersistWorkflow.cs b/test/WorkflowCore.TestAssets/Persistence/PersistWorkflow.cs new file mode 100644 index 000000000..a82a23d1d --- /dev/null +++ b/test/WorkflowCore.TestAssets/Persistence/PersistWorkflow.cs @@ -0,0 +1,76 @@ +using Machine.Specifications; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using WorkflowCore.Interface; +using WorkflowCore.Models; + +namespace WorkflowCore.TestAssets.Persistence +{ + [Behaviors] + public class PersistWorkflowBehaviors + { + protected static IPersistenceProvider Subject; + protected static WorkflowInstance newWorkflow; + protected static string workflowId; + + It should_store_the_difference = () => + { + var oldWorkflow = Subject.GetWorkflowInstance(workflowId).Result; + Utils.CompareObjects(oldWorkflow, newWorkflow).ShouldBeTrue(); + }; + } + + public abstract class PersistWorkflow + { + protected static IPersistenceProvider Subject; + protected static WorkflowInstance newWorkflow; + protected static string workflowId; + + protected abstract IPersistenceProvider Provider { get; } + Establish context; + + public PersistWorkflow() + { + context = EstablishContext; + } + + protected void EstablishContext() + { + Subject = Provider; + + var oldWorkflow = new WorkflowInstance() + { + Data = new { Value1 = 7 }, + Description = "My Description", + Status = WorkflowStatus.Runnable, + NextExecution = 0, + Version = 1, + WorkflowDefinitionId = "My Workflow", + CreateTime = new DateTime(2000, 1, 1).ToUniversalTime() + }; + oldWorkflow.ExecutionPointers.Add(new ExecutionPointer() + { + Id = Guid.NewGuid().ToString(), + Active = true, + StepId = 0 + }); + + workflowId = Subject.CreateNewWorkflow(oldWorkflow).Result; + + newWorkflow = Utils.DeepCopy(oldWorkflow); + newWorkflow.NextExecution = 7; + newWorkflow.ExecutionPointers.Add(new ExecutionPointer() { Id = Guid.NewGuid().ToString(), Active = true, StepId = 1 }); + } + + Because of = () => Subject.PersistWorkflow(newWorkflow).Wait(); + + Cleanup after = () => + { + Subject = null; + newWorkflow = null; + workflowId = null; + }; + } +} diff --git a/test/WorkflowCore.TestAssets/WorkflowCore.TestAssets.csproj b/test/WorkflowCore.TestAssets/WorkflowCore.TestAssets.csproj index deb32ea7d..80b61ff17 100644 --- a/test/WorkflowCore.TestAssets/WorkflowCore.TestAssets.csproj +++ b/test/WorkflowCore.TestAssets/WorkflowCore.TestAssets.csproj @@ -17,12 +17,13 @@ - - + + + diff --git a/test/WorkflowCore.Tests.MongoDB/Scenarios/BasicWorkflow.cs b/test/WorkflowCore.Tests.MongoDB/Scenarios/BasicWorkflow.cs new file mode 100644 index 000000000..16f91e079 --- /dev/null +++ b/test/WorkflowCore.Tests.MongoDB/Scenarios/BasicWorkflow.cs @@ -0,0 +1,22 @@ +using Machine.Specifications; +using System; +using System.Collections.Generic; +using System.Text; +using WorkflowCore.IntegrationTests.Scenarios; +using WorkflowCore.Services; +using Microsoft.Extensions.DependencyInjection; +using WorkflowCore.Models; + +namespace WorkflowCore.Tests.MongoDB.Scenarios +{ + [Subject(typeof(WorkflowHost))] + public class Mongo_BasicWorkflow : BasicWorkflow + { + protected override void ConfigureWorkflow(IServiceCollection services) + { + services.AddWorkflow(x => x.UseMongoDB($"mongodb://localhost:{DockerSetup.Port}", "workflow-tests")); + } + + Behaves_like a_basic_workflow; + } +} diff --git a/test/WorkflowCore.Tests.MongoDB/Scenarios/DataIO.cs b/test/WorkflowCore.Tests.MongoDB/Scenarios/DataIO.cs new file mode 100644 index 000000000..6c5e25748 --- /dev/null +++ b/test/WorkflowCore.Tests.MongoDB/Scenarios/DataIO.cs @@ -0,0 +1,22 @@ +using Machine.Specifications; +using System; +using System.Collections.Generic; +using System.Text; +using WorkflowCore.IntegrationTests.Scenarios; +using WorkflowCore.Services; +using Microsoft.Extensions.DependencyInjection; +using WorkflowCore.Models; + +namespace WorkflowCore.Tests.MongoDB.Scenarios +{ + [Subject(typeof(WorkflowHost))] + public class Mongo_DataIO : DataIO + { + protected override void ConfigureWorkflow(IServiceCollection services) + { + services.AddWorkflow(x => x.UseMongoDB($"mongodb://localhost:{DockerSetup.Port}", "workflow-tests")); + } + + Behaves_like a_data_io_workflow; + } +} diff --git a/test/WorkflowCore.Tests.MongoDB/Scenarios/ExternalEvents.cs b/test/WorkflowCore.Tests.MongoDB/Scenarios/ExternalEvents.cs new file mode 100644 index 000000000..6c5d1ff05 --- /dev/null +++ b/test/WorkflowCore.Tests.MongoDB/Scenarios/ExternalEvents.cs @@ -0,0 +1,22 @@ +using Machine.Specifications; +using System; +using System.Collections.Generic; +using System.Text; +using WorkflowCore.IntegrationTests.Scenarios; +using WorkflowCore.Services; +using Microsoft.Extensions.DependencyInjection; +using WorkflowCore.Models; + +namespace WorkflowCore.Tests.MongoDB.Scenarios +{ + [Subject(typeof(WorkflowHost))] + public class Mongo_ExternalEvents : ExternalEventsTest + { + protected override void ConfigureWorkflow(IServiceCollection services) + { + services.AddWorkflow(x => x.UseMongoDB($"mongodb://localhost:{DockerSetup.Port}", "workflow-tests")); + } + + Behaves_like a_external_events_workflow; + } +} diff --git a/test/WorkflowCore.Tests.MongoDB/Scenarios/OutcomeFork.cs b/test/WorkflowCore.Tests.MongoDB/Scenarios/OutcomeFork.cs new file mode 100644 index 000000000..df9ae0831 --- /dev/null +++ b/test/WorkflowCore.Tests.MongoDB/Scenarios/OutcomeFork.cs @@ -0,0 +1,22 @@ +using Machine.Specifications; +using System; +using System.Collections.Generic; +using System.Text; +using WorkflowCore.IntegrationTests.Scenarios; +using WorkflowCore.Services; +using Microsoft.Extensions.DependencyInjection; +using WorkflowCore.Models; + +namespace WorkflowCore.Tests.MongoDB.Scenarios +{ + [Subject(typeof(WorkflowHost))] + public class Mongo_OutcomeFork : OutcomeForkTest + { + protected override void ConfigureWorkflow(IServiceCollection services) + { + services.AddWorkflow(x => x.UseMongoDB($"mongodb://localhost:{DockerSetup.Port}", "workflow-tests")); + } + + Behaves_like outcome_workflow; + } +} diff --git a/test/WorkflowCore.Tests.MongoDB/Scenarios/UserSteps.cs b/test/WorkflowCore.Tests.MongoDB/Scenarios/UserSteps.cs new file mode 100644 index 000000000..5a12ee3d6 --- /dev/null +++ b/test/WorkflowCore.Tests.MongoDB/Scenarios/UserSteps.cs @@ -0,0 +1,22 @@ +using Machine.Specifications; +using System; +using System.Collections.Generic; +using System.Text; +using WorkflowCore.IntegrationTests.Scenarios; +using WorkflowCore.Services; +using Microsoft.Extensions.DependencyInjection; +using WorkflowCore.Models; + +namespace WorkflowCore.Tests.MongoDB.Scenarios +{ + [Subject(typeof(WorkflowHost))] + public class Mongo_UserSteps : UserStepsTest + { + protected override void ConfigureWorkflow(IServiceCollection services) + { + services.AddWorkflow(x => x.UseMongoDB($"mongodb://localhost:{DockerSetup.Port}", "workflow-tests")); + } + + Behaves_like human_workflow; + } +} diff --git a/test/WorkflowCore.Tests.PostgreSQL/Scenarios/BasicWorkflow.cs b/test/WorkflowCore.Tests.PostgreSQL/Scenarios/BasicWorkflow.cs new file mode 100644 index 000000000..f1639091a --- /dev/null +++ b/test/WorkflowCore.Tests.PostgreSQL/Scenarios/BasicWorkflow.cs @@ -0,0 +1,22 @@ +using Machine.Specifications; +using System; +using System.Collections.Generic; +using System.Text; +using WorkflowCore.IntegrationTests.Scenarios; +using WorkflowCore.Services; +using Microsoft.Extensions.DependencyInjection; +using WorkflowCore.Models; + +namespace WorkflowCore.Tests.PostgreSQL.Scenarios +{ + [Subject(typeof(WorkflowHost))] + public class PostgreSQL_BasicWorkflow : BasicWorkflow + { + protected override void ConfigureWorkflow(IServiceCollection services) + { + services.AddWorkflow(x => x.UsePostgreSQL($"Server=127.0.0.1;Port={DockerSetup.Port};Database=workflow;User Id=postgres;", true, true)); + } + + Behaves_like a_basic_workflow; + } +} diff --git a/test/WorkflowCore.Tests.PostgreSQL/Scenarios/DataIO.cs b/test/WorkflowCore.Tests.PostgreSQL/Scenarios/DataIO.cs new file mode 100644 index 000000000..e8f89f2ac --- /dev/null +++ b/test/WorkflowCore.Tests.PostgreSQL/Scenarios/DataIO.cs @@ -0,0 +1,22 @@ +using Machine.Specifications; +using System; +using System.Collections.Generic; +using System.Text; +using WorkflowCore.IntegrationTests.Scenarios; +using WorkflowCore.Services; +using Microsoft.Extensions.DependencyInjection; +using WorkflowCore.Models; + +namespace WorkflowCore.Tests.PostgreSQL.Scenarios +{ + [Subject(typeof(WorkflowHost))] + public class PostgreSQL_DataIO : DataIO + { + protected override void ConfigureWorkflow(IServiceCollection services) + { + services.AddWorkflow(x => x.UsePostgreSQL($"Server=127.0.0.1;Port={DockerSetup.Port};Database=workflow;User Id=postgres;", true, true)); + } + + Behaves_like a_data_io_workflow; + } +} diff --git a/test/WorkflowCore.Tests.PostgreSQL/Scenarios/ExternalEvents.cs b/test/WorkflowCore.Tests.PostgreSQL/Scenarios/ExternalEvents.cs new file mode 100644 index 000000000..573a6cc17 --- /dev/null +++ b/test/WorkflowCore.Tests.PostgreSQL/Scenarios/ExternalEvents.cs @@ -0,0 +1,22 @@ +using Machine.Specifications; +using System; +using System.Collections.Generic; +using System.Text; +using WorkflowCore.IntegrationTests.Scenarios; +using WorkflowCore.Services; +using Microsoft.Extensions.DependencyInjection; +using WorkflowCore.Models; + +namespace WorkflowCore.Tests.PostgreSQL.Scenarios +{ + [Subject(typeof(WorkflowHost))] + public class PostgreSQL_ExternalEvents : ExternalEventsTest + { + protected override void ConfigureWorkflow(IServiceCollection services) + { + services.AddWorkflow(x => x.UsePostgreSQL($"Server=127.0.0.1;Port={DockerSetup.Port};Database=workflow;User Id=postgres;", true, true)); + } + + Behaves_like a_external_events_workflow; + } +} diff --git a/test/WorkflowCore.Tests.PostgreSQL/Scenarios/OutcomeFork.cs b/test/WorkflowCore.Tests.PostgreSQL/Scenarios/OutcomeFork.cs new file mode 100644 index 000000000..a623717f6 --- /dev/null +++ b/test/WorkflowCore.Tests.PostgreSQL/Scenarios/OutcomeFork.cs @@ -0,0 +1,22 @@ +using Machine.Specifications; +using System; +using System.Collections.Generic; +using System.Text; +using WorkflowCore.IntegrationTests.Scenarios; +using WorkflowCore.Services; +using Microsoft.Extensions.DependencyInjection; +using WorkflowCore.Models; + +namespace WorkflowCore.Tests.PostgreSQL.Scenarios +{ + [Subject(typeof(WorkflowHost))] + public class PostgreSQL_OutcomeFork : OutcomeForkTest + { + protected override void ConfigureWorkflow(IServiceCollection services) + { + services.AddWorkflow(x => x.UsePostgreSQL($"Server=127.0.0.1;Port={DockerSetup.Port};Database=workflow;User Id=postgres;", true, true)); + } + + Behaves_like outcome_workflow; + } +} diff --git a/test/WorkflowCore.Tests.PostgreSQL/Scenarios/UserSteps.cs b/test/WorkflowCore.Tests.PostgreSQL/Scenarios/UserSteps.cs new file mode 100644 index 000000000..8d88c8fda --- /dev/null +++ b/test/WorkflowCore.Tests.PostgreSQL/Scenarios/UserSteps.cs @@ -0,0 +1,22 @@ +using Machine.Specifications; +using System; +using System.Collections.Generic; +using System.Text; +using WorkflowCore.IntegrationTests.Scenarios; +using WorkflowCore.Services; +using Microsoft.Extensions.DependencyInjection; +using WorkflowCore.Models; + +namespace WorkflowCore.Tests.PostgreSQL.Scenarios +{ + [Subject(typeof(WorkflowHost))] + public class PostgreSQL_UserSteps : UserStepsTest + { + protected override void ConfigureWorkflow(IServiceCollection services) + { + services.AddWorkflow(x => x.UsePostgreSQL($"Server=127.0.0.1;Port={DockerSetup.Port};Database=workflow;User Id=postgres;", true, true)); + } + + Behaves_like human_workflow; + } +} diff --git a/test/WorkflowCore.UnitTests/SingleNodeLockProviderTests/SingleNodeLockProviderTests.cs b/test/WorkflowCore.UnitTests/SingleNodeLockProviderTests/SingleNodeLockProviderTests.cs new file mode 100644 index 000000000..d2cf041a5 --- /dev/null +++ b/test/WorkflowCore.UnitTests/SingleNodeLockProviderTests/SingleNodeLockProviderTests.cs @@ -0,0 +1,20 @@ +using FluentAssertions; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Text; +using WorkflowCore.Interface; +using WorkflowCore.Services; +using WorkflowCore.TestAssets.LockProvider; + +namespace WorkflowCore.UnitTests.SingleNodeLockProviderTests +{ + [TestFixture] + public class SingleNodeLockProviderTests : DistributedLockProviderTests + { + protected override IDistributedLockProvider CreateProvider() + { + return new SingleNodeLockProvider(); + } + } +}